6.9 KiB
Contributing
This repo is governed by
heavy-duty/ceremony. Agents:
read .ceremony/AGENTS.md first — it routes you to
your role file (builder, reviewer, triage), vendored beside it,
byte-identical to ceremony at the pin named in
.github/workflows/release.yml and
guarded by the docs-sync step in CI. The review-round doctrine — drafts,
whole-round replies, verdicts, the handoff — lives there and in
.ceremony/LABELS.md; this file keeps only what is
genuinely rig's.
The PR loop, rig specifics
- Fork and branch. Contributors work from forks; upstream branches are
for maintainers. Title the PR conventionally (
feat:,fix:,docs:). - The review panel (
.github/labels.conf'spanel=line):claude-bot-andresmgsl,codex-bot-andresmgsl,grok-bot-andresmgsl,kimi-bot-andresmgsl— the required verdicts for a PR are the panel minus its author. The maintainer (danmt) takes the last word and merges. - Checks must be green:
shellcheck,bash test/cli.shandbash test/release.shlocally mirror what CI runs; the db dump/restore round-trip (test/db-integration.sh) executes in CI where Docker is present. The release guards (changelog-armed,changelog-monotonic,changelog-assembled,drill-recorded,runner-isolated,docs-sync) run as ceremony's pinned actions. - Feature PRs land their changelog entry as part of the PR: write
changelog.d/<issue>.md— the release PR assembles those fragments into the release notes verbatim.
Changelog entries
Every PR that changes behaviour writes one changelog.d/<issue>.md fragment.
The fragment keeps the relevant ### Added / ### Changed / ### Fixed
heading above its entry. One line is the whole rule — if it wraps more than
twice in your editor, cut it down.
- Say what changed, and stop. Why it was wrong, how it was found, what it cost, what it implies — that belongs in the PR body and the commit message, which is where anyone chasing the reasoning already goes. This file answers one question: what is different in this version.
- Any word that can be removed, is removed.
- Lead with the surface, not the mechanism. "
state:needs-humanis set at handoff" beats "the labels workflow now also wakes onlabeled". - Cite the issue or PR —
(#96)— and let the reader follow it for the rest. - Mark a breaking change with a leading
BREAKING:. - Group under
### Added/### Changed/### Fixed/### Removed. - No bold run-in headings, no sub-paragraphs, no code blocks, no prose essays.
Good:
state:needs-humanis set at handoff, not by the cron (#96)- An unreadable check rollup no longer reads as "nothing is failing" (#90)
- BREAKING:
--class human|serveris now--root-door closed|open(#77)
Not an entry — that is a PR body:
state:needs-humanno longer waits on the cron to become true (#96) — the labels workflow now also wakes onpull_request_target: labeledandunlabeled, and the author sets it themselves when handing a PR over. A review landing was never a trigger. There is nopull_request_review_target, and on fork PRs — which is all of them here — ...
Releasing
A release is a PR, and merging it is the release. The ceremony — the two
doors, the decide table, the stamps, the post-release re-arm — is
heavy-duty/ceremony's machinery, consumed by reference:
its README
is the doctrine, .github/workflows/release.yml here is the ≤20-line
caller pinning it, and the guards run in ci.yml from the same pin.
Bare X.Y.Z tags, no v; the tag's source tarball is the package
install.sh downloads — rig ships no other artifact. Each release deliberately
bumps and drills the BOX_RELEASE pin in commands/bootstrap.sh; it must never
float to a moving ref.
What stays rig's is the drill — the real-hardware gate before the
handoff of a release PR, run by drill/drill.sh (#105): rig bootstrap
converging the machine to its role twice with the second run diffed empty,
test/db-integration.sh, the runner lifecycle against a fork, a coolify
install. Rig's drill asserts convergence (a machine reaches its role,
idempotently), it runs --host yes with BOX_REF=release/<box-version> so
it exercises the box that will actually ship, and drills that share a
substrate share one run ID so the per-repo records can be joined after
the fact. The full meaning — the fixed candidate-ref pinning that dissolves
the box↔rig recursion, the per-version record files, the waiver rule — is
drills/README.md; the drill-recorded guard enforces
the record on every release tree.
Labels — who sets what
The taxonomy and state machine are
.ceremony/LABELS.md; rig's scope:* rows live in
.github/labels.conf (reconciled by the labels caller) and their path map
in .github/labeler.yml. What matters day to day is who sets each kind —
most of it is machinery, and hand-moving a machine-owned label just gets
corrected on the next pass:
| Labels | Set by |
|---|---|
state:* |
the labels workflow (.github/workflows/labels.yml) — recomputed from GitHub's own facts on PR events (label changes included) and every 15 minutes. Machine-owned, with one exception: the author sets state:needs-human at handoff and the workflow reconciles it. Otherwise never by hand. Exactly one per PR: whose ball is it. |
blocker:* |
the same workflow, from the same facts — what is in the way. Any number per PR, or none. Never by hand: applying one does not stop a merge, and removing one does not unblock anything. Fix the thing and the next sweep drops the label. |
stale |
the same workflow — 48h without commits, comments, or reviews. blocked PRs are exempt: they are quiet legitimately. |
scope:* on PRs |
actions/labeler, from the changed paths (.github/labeler.yml). Additive — you may add more, the machine won't remove them. |
scope:* on issues |
you, when opening or triaging — issues have no paths to derive from. |
blocked, release |
you — automation never guesses intent. |
merge-next |
you or the agent owning the queue. Which PR lands first is a judgement about how they conflict, so the workflow never sets it — it only clears it, the moment the PR stops being something a human could merge. |
bug / enhancement / documentation |
you, on issues only — a PR's type already lives in its title. |
Issues
Give issues the same care as PR titles: say the surface in the title, apply a
scope: label and a type label (bug / enhancement / documentation) when
you open one, and blocked when it waits on something — that is what keeps
the board navigable as the issue count grows.