kimi-reviewer-andresmgsl is temporarily unavailable, and with a three-identity panel it sits in the required set for every possible author — so no new PR could converge while it is down. glm-reviewer-andresmgsl takes the seat. The roster guard from #195 is bidirectional, so three files move together: the conf, CONTRIBUTING's table, and the test's table-side mutation, which must name an identity the table carries or it stops testing anything. Refs #222
8.6 KiB
Contributing
This repo defines how the heavy-duty repos work — the release ceremony, the label state machine, and the agent team flow — and it runs entirely on its own rules. If something here contradicts how this repo actually operates, one of the two is a bug.
The line
Work moves through one pipeline, and every stage has an owner:
discussion ──▶ triage ──▶ issue ──▶ build ──▶ review ──▶ human merge ──▶ release
(anyone) (agent) (queue) (agent) (agents) (human) (ceremony)
- Discussions are where intent lives. Anyone — human or agent — who has an idea, a bug, a question, or a "we should…" opens a discussion, not an issue. Discussions are allowed to be vague; that is what they are for.
- Issues are minted only by triage. Nobody else writes issues — not
humans, not builders, not reviewers. An issue is a work order with a quality
bar (the issue contract in TRIAGE.md), and the bar holds
because exactly one role is accountable for it. An issue that appears
through any other door gets
needs-triageand is normalized or converted back into a discussion. - Builders turn one issue into one PR. BUILDER.md.
- Reviewers converge on a verdict. REVIEWER.md.
- Humans decide twice: in the discussion (what is worth doing, and any call triage escalates back) and at the merge (whether it ships). Everything between those two points is agent work by default.
- Merging a release PR ships it — the release ceremony this repo's workflows implement (README, issue #1).
Who may set which label is LABELS.md's contract.
The PR flow
PRs move through review rounds that builders answer whole, and only a human merges. BUILDER.md is the shared flow contract; this file names only ceremony-specific facts such as the roster and code conventions.
Roster
Three identities share the work (org team agents), each living in its own
box — one box per credential, because
the box is the blast-radius boundary; roles are what a session is told, and
AGENTS.md routes from there:
| identity | box (rig tenant) | standing work |
|---|---|---|
cluade-reviewer-andresmgsl |
triage (claude-box) |
triage — the only door issues come through; this identity mints issues and nothing else writes them (#18's triage-actors) — and review. It does not build. |
codex-reviewer-andresmgsl |
codex-box | build + review |
glm-reviewer-andresmgsl |
glm-box | review |
The review panel for any PR is every bench identity except its author —
recusal by construction, enforced by the reconciler (#10): the required
verdicts are the panel minus the PR's author. On this roster that resolves
to two cross-vendor approvals of the current head, because the only
builder is itself on the panel and recuses from its own PRs; the rule is
panel-minus-author, and two is what it currently comes to, not a second
rule. Builders and triage default to different models so the issue contract
is honestly exercised — a spec gap should surface as a question on the
issue, not be silently filled by shared priors. Humans (andres) decide in
discussions and merge; the roster is config, not doctrine — swapping a
vendor is an edit to this table (and to panel= in
.github/labels.conf once #10 lands), nothing more.
The identities named here must be the identities .github/labels.conf
names, and both must exist on the forge the repo lives on. A roster that
agrees with itself and disagrees with the instance is the failure #195
records: panel= naming absent users cannot converge and
triage-actors= naming an absent user makes every issue a stray mint that
nobody can normalize. test/labels.test.sh holds this table and the conf
to the same set, in both directions.
Each governed repo names its own roster in its CONTRIBUTING; this one is
ceremony's. Its scope:* set is the same kind of repo-specific fact:
ceremony's scopes are defined in .github/labels.conf
— one name|color|description row each, with PR path mapping in
.github/labeler.yml. The conf is the set; no prose
table repeats it (#104).
Code conventions
- Bash:
set -euo pipefailin executables,set -uin test files (the test harness asserts on failing commands, so no-ethere). - mawk-compatible awk — CI runners ship mawk, not gawk; no
\xescapes. - Every piece of logic is a file of its own so a test can drive it. Workflows and actions gather facts; scripts decide. If a decision lives inline in YAML, it is in the wrong place.
- Comments carry the why — the incident that bought the rule, with its
issue number (
box#108,rig#66, …). When porting from a sibling repo, the war stories come along; they are the documentation. - Whole-version matching everywhere:
0.7.0never matches0.7.0-rc1. - Shellcheck- and actionlint-clean is a CI gate, not a suggestion.
Doctrine conventions
The vendored role files — AGENTS.md, TRIAGE.md, BUILDER.md,
REVIEWER.md, LABELS.md, and RELEASES.md — state each normative rule
completely, keep at most one sentence of why, and cite its record only with a
bare parenthetical such as (#N), (#N D3), or (#N, #M). Incident
narrative — timestamps, actors, quoted comments, measured counts, and links to
specific comments — belongs in that record. If a rule cannot be followed
without chasing its cite, the rule is under-stated: fix the statement, not the
citation. (#280)
Normative text in those files does not cite issues from other repositories. Consumers read the vendored bytes outside this organization's context, and a cited repository may not be public. A repo-boundary deferral remains allowed: it names another component as the owner of a fact rather than citing one of that component's issues. (#280)
This is distinct from the code-comment convention above: a code comment is read by a maintainer inside the organization while standing in the file, whereas vendored doctrine is read by any agent in any governed repository on every session. (#280)
How the other repos use this
Two consumption modes, split by what has a runtime:
- Machinery is consumed by reference. Workflows and actions are fetched by GitHub at run time from the ref the caller pins — no copy exists in the consumer.
- Doctrine is consumed as a machine-verified mirror. A document's only
"runtime" is an agent reading the working tree of the repo it stands in —
a doc that requires a cross-repo fetch before it governs is a doc that
sometimes goes unread. So the agent-facing set — AGENTS.md, TRIAGE.md,
BUILDER.md, REVIEWER.md, LABELS.md, RELEASES.md — is vendored into each governed
repo at
.ceremony/, byte-identical to this repo at the pinned ref, by the sync tool (issue #19). A CI guard diffs the mirror against the pin on every PR: hand-editing a vendored file, or bumping the pin without re-syncing, goes red. It is a copy that cannot drift — which is the only kind of copy this org allows.
A governed repo (box, rig, cast, incubator, …) therefore carries:
.ceremony/— the vendored doctrine (machine-written; never edited by hand; agents read it from the checkout, no network, no other repo);- a thin root
AGENTS.mdstub — a few lines: "governed by heavy-duty/ceremony; read.ceremony/AGENTS.mdfirst; repo specifics in CONTRIBUTING". The stub is what makes "you are a reviewer here" a sufficient launch prompt: agent harnesses auto-load root AGENTS.md (the cross-agent convention), and the vendored router takes it from there. Tool-specific files (CLAUDE.md, …) reduce to one pointer line at it; - the thin workflow callers (release, labels) pinned to a ceremony tag, plus
the
docs-sync --checkguard step in CI; - a short header in its own CONTRIBUTING pointing agents at
.ceremony/, followed by only what is genuinely per-repo:- the review panel roster,
- the
scope:*label set (.github/labels.conf+.github/labeler.yml), - the drill meaning (
drills/README.md), - the repo's own code conventions;
- Discussions enabled, so the triage door exists.
One pin governs both the machinery and the doctrine: the ref a repo's
workflows call is the ref its .ceremony/ mirror is verified against.
Bumping the pin is one PR — the pin line plus the re-synced mirror, checked
by the same guard — and is how a process change rolls out: deliberately, per
repo, reviewed. The full adoption checklist lives in
docs/CONSUMERS.md (issue #12).