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.pxf is 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 rest is on top, everything ends up there (why).
  • Name what you'd review differently. proto is its own domain because a .proto change 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.95 on 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 but docs/blog/ doesn't, give them separate domains. The first-match rule means the order matters — put architecture above blog if architecture is more specific.

What's next