rig/CONTRIBUTING.md
claude-bot-andresmgsl 0b3c7cb055 fix: kimi-bot-andresmgsl joins the review panel — the roster predated it joining the bench
The ceremony conversion (#112) extracted the panel= line verbatim from the
pre-ceremony labels-reconcile.sh BOTS array, which predates kimi-bot joining
the family bench. A name missing from that line is a reviewer the machinery
never waits for: the handoff would report a full panel while one verdict
short — the exact defect cast@2612967 fixed after cast#143 shipped it.

labels.conf is the source of truth the reconciler reads; CONTRIBUTING
mirrors it for humans requesting reviewers by hand. Both move together.

Closes #120

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 23:53:30 +00:00

6.6 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

  1. Fork and branch. Contributors work from forks; upstream branches are for maintainers. Title the PR conventionally (feat:, fix:, docs:).
  2. The review panel (.github/labels.conf's panel= 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.
  3. Checks must be green: shellcheck, bash test/cli.sh and bash test/release.sh locally 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, drill-recorded, docs-sync) run as ceremony's pinned actions.
  4. Feature PRs land their changelog entry as part of the PR: add it under CHANGELOG.md's ## Unreleased heading — that section becomes the release notes verbatim when a release is cut.

Changelog entries

Every PR that changes behaviour adds one line to ## Unreleased. 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-human is set at handoff" beats "the labels workflow now also wakes on labeled".
  • 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-human is set at handoff, not by the cron (#96)
  • An unreadable check rollup no longer reads as "nothing is failing" (#90)
  • BREAKING: --class human|server is now --root-door closed|open (#77)

Not an entry — that is a PR body:

  • state:needs-human no longer waits on the cron to become true (#96) — the labels workflow now also wakes on pull_request_target: labeled and unlabeled, and the author sets it themselves when handing a PR over. A review landing was never a trigger. There is no pull_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.

What stays rig's is the drill — the real-hardware gate before the handoff of a release PR: tenant guests minted and converged via box, 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.