Documentation › Maintainer guides
Write your first governance.pxf
A 10-minute walkthrough that takes you from "I just installed Steward" to "my repo has policy that reflects what I actually care about." Background reading: the governance.pxf + domains concept doc — this guide assumes you've skimmed it.
Before you start
You need:
- The Steward GitHub App installed on the repo. (If not, see Install Steward + your first PR.)
- Write access to the default branch —
governance.pxfis committed there.
The end state: a .steward/governance.pxf file on your default
branch, structured to match your repo's actual shape, with a few
intentional gates wired up.
Pass 0: consider the fast path first
Most repos shouldn't write this file from scratch anymore. The
governance editor (/app/governance) opens with a posture
wizard when the file doesn't exist yet: pick how your project
works (welcoming a community, a disciplined internal team, company-backed open source, or a community-governed project),
answer two or three questions, and the wizard writes a file that
extends a version-pinned
preset —
the posture supplies every threshold and toggle, your file
carries only the mission and a couple of choices. The editor
then shows every inherited section, and overriding one starts
from the full effective text so nothing gets dropped.
The rest of this guide is the from-scratch path: worth walking when no posture fits, when you want to understand every line you'll be governed by, or when you're evaluating what the presets actually decide on your behalf. We'll write the file in three passes: minimal, typed, gated.
Pass 1: the minimal file (2 minutes)
Open a terminal at the repo root.
mkdir -p .steward
cat > .steward/governance.pxf <<'PXF'
domains: [
{ name: "everything", path_globs: ["**/*"], max_cyclomatic_complexity: 15 }
]
PXF
Commit and merge to the default branch. Steward will start honouring this on the next PR. The cyclomatic-complexity ceiling of 15 is conservative — most codebases have a handful of functions above it, which is fine; those will flag, not block, until you turn on a stricter setting.
Open a small PR (a typo fix works). Inside ~30 seconds you should
see a Steward verdict comment with one domain — everything —
and a passing report. You now have governance.
Pass 2: map your real domains (4 minutes)
A single everything domain wastes Steward's per-domain
reasoning. Let's split.
Walk the repo's top-level layout. For a typical Go project this might look like:
.
├── cmd/ # CLI entry points
├── internal/ # business logic
├── docs/ # markdown + diagrams
├── proto/ # .proto definitions
└── README.md
Choose domains that match how you mentally divide review authority. A reasonable cut for the above:
domains: [
{ name: "proto", path_globs: ["proto/**"] }
{ name: "core", path_globs: ["internal/**"] }
{ name: "cli", path_globs: ["cmd/**"] }
{ name: "docs", path_globs: ["docs/**", "*.md"] }
{ name: "rest", path_globs: ["**/*"] }
]
Key moves:
- Specific first, catch-all last. Glob matching wins
first-match; if
restis on top, everything ends up there (why). - Name what you'd review differently.
protois its own domain because a.protochange is a wire-format change with different reviewer expectations than business logic. - Don't over-split. Five domains is a lot. Three is usually fine. Add more when a PR pattern keeps slipping through the wrong domain's gates.
Commit, open another PR that touches multiple domains, and check the verdict. Each touched domain should appear in the report with its own breakdown.
Pass 3: turn on the gates that matter (4 minutes)
Now add thresholds. Pick two or three to start — you can always add more later. Here are the highest-leverage ones for most repos:
Coverage floor on the core package
{ name: "core", path_globs: ["internal/**"], min_coverage: 0.70 }
Steward compares the PR's coverage against min_coverage. A PR
that drops internal/** coverage below 70% gets a failing
coverage-gate signal in the verdict. Set this to 5–10 points
below your current coverage so it doesn't fail every PR on day
one; raise it as the codebase improves.
Heads up: coverage requires Static Analysis Results Interchange Format (SARIF) or language-native coverage adapters. Go works out-of-the-box; other languages need the SARIF flow (Gates concept).
Reviewer authority on the API surface
{ name: "core", path_globs: ["internal/**"], approval_threshold: 2 }
approval_threshold is reputation points the author has in
this domain. An author with ≥2 points in core can self-merge a
PR that only touches core. Below that, the gate asks for a
review from someone above the threshold. This is reputation-
weighted, not seat-count — a first-time contributor can't push a
core change without a senior review even if they're a repo
collaborator.
Day-one reasonable values: 2 for active codebases, 1 for
small teams, 5+ for high-trust hot zones (auth, payments).
CLA + DCO
cla: {
dco_required: true
enforcement: "block"
}
dco_required: true requires every commit to carry
Signed-off-by:. enforcement: "block" rejects PRs missing
signatures; "flag" annotates them but lets them through. This
is the lowest-friction compliance setting (most Open Source
Software (OSS) projects use this; many internal-IP-chain
private repos use it too). Real CLAs (with e-signatures,
template paths, etc.) live in the same block — see
Gates: DCO, CLA, complexity.
A complete example
version: "1"
domains: [
{ name: "proto", path_globs: ["proto/**"], approval_threshold: 3 }
{ name: "core", path_globs: ["internal/**"], min_coverage: 0.70, approval_threshold: 2, max_cyclomatic_complexity: 15 }
{ name: "cli", path_globs: ["cmd/**"], min_coverage: 0.50, approval_threshold: 1 }
{ name: "docs", path_globs: ["docs/**", "*.md"], approval_threshold: 1 }
{ name: "rest", path_globs: ["**/*"], approval_threshold: 1 }
]
cla: {
dco_required: true
enforcement: "block"
}
About 15 lines of meaningful policy. Most repos don't outgrow this shape for months.
Common mistakes
- Forgetting the catch-all. Without a final
**/*domain, files outside every specific glob have no domain. Steward treats them as the implicit "rest" with default thresholds — the dashboard will surface a "no explicit domain" badge so you know. - Over-tight day-one thresholds. Setting
min_coverage: 0.95on day one fails every PR and trains your team to ignore the verdict. Start loose, ratchet up. - Mixing read-paths into the same domain. If
docs/architecture/contains diagrams that need expert review butdocs/blog/doesn't, give them separate domains. The first-match rule means the order matters — putarchitectureaboveblogifarchitectureis more specific.
What's next
- Read an evaluation report — what you'll be reading once governance.pxf is in place.
- Debug a failed evaluation — the playbook for when a verdict comes back FAILED.
- Evaluations + dimensions — the model behind the per-PR score that uses these thresholds.
Steward