forked from heavy-duty/ceremony
474 lines
23 KiB
Markdown
474 lines
23 KiB
Markdown
# Consumer setup
|
|
|
|
How a repo adopts the ceremony — bootstrap for a greenfield repo, a
|
|
conversion checklist for a repo carrying its own copy of the machinery,
|
|
and the policies that keep either honest afterwards. The doctrine (what a
|
|
release *is*, the doors, the guards, the drill) lives in
|
|
[../README.md](../README.md); this guide is the how-to. It is meant to be
|
|
sufficient on its own: a conversion executed from this guide should need
|
|
zero out-of-band knowledge, and gaps found while converting are filed as
|
|
edits to this guide (#12).
|
|
|
|
## Prerequisites
|
|
|
|
- **Repo shape**: work lands on a `main` default branch by PR; fork PRs
|
|
are fine — the merge door rides `push` to `main`, never `pull_request`
|
|
([release.yml](../.github/workflows/release.yml#L70-L74), box#97), and
|
|
the label read goes through the API
|
|
([lib/facts.sh](../lib/facts.sh#L88-L101)), so the ceremony never needs
|
|
the PR's own context. No PAT, no secrets: every permission the flow uses
|
|
is the caller-declared `GITHUB_TOKEN` grant.
|
|
- **Pick the version backend**: `file` (a `VERSION` file — box, rig,
|
|
incubator) or `package-json` (the `version` field, lockfile kept in sync
|
|
on the post-release bump — cast). This is the workflow's one input; the
|
|
full configuration surface of the ceremony is enumerated in
|
|
[#1](https://github.com/heavy-duty/ceremony/issues/1) ("The
|
|
configuration axes").
|
|
- **The `release` label must exist** before the first ceremony PR — it is
|
|
the merge door's declared-intent read
|
|
([lib/facts.sh](../lib/facts.sh#L88-L101)). Bootstrap it via the labels
|
|
workflow's `workflow_dispatch`
|
|
([Labels automation](#labels-automation)), or create it by hand,
|
|
matching the core table
|
|
([actions/labels-reconcile/labels-reconcile.sh](../actions/labels-reconcile/labels-reconcile.sh#L369)):
|
|
|
|
```sh
|
|
gh label create release --color 0E8A16 \
|
|
--description "Release flow and version/packaging work"
|
|
```
|
|
|
|
## Bootstrap a new repo
|
|
|
|
The greenfield path (incubator's, #16) — the repo never owns a copy of
|
|
the machinery at all:
|
|
|
|
1. **`VERSION` at `X.Y.Z-dev` — never bare.** A first version that never
|
|
carried `-dev` hits the decide table's refuse row and has to ship by
|
|
the tag door (the known first-release edge, cast#111;
|
|
[lib/decide.sh](../lib/decide.sh#L70-L74)). Bootstrapping at `-dev`
|
|
keeps the repo clear of it entirely. (`package-json` backend: the
|
|
`version` field, same rule.)
|
|
2. **An armed `CHANGELOG.md`**: a preamble plus an empty `## Unreleased`
|
|
section for the first entries to land under. If the repo groups entries,
|
|
seed the shape the ceremony PR will restore at every re-arm:
|
|
|
|
```markdown
|
|
## Unreleased
|
|
|
|
### Added
|
|
|
|
### Changed
|
|
|
|
### Fixed
|
|
```
|
|
3. **`drills/README.md`** defining what a drill *means* in this repo —
|
|
each repo names its own
|
|
([the drill doctrine](../README.md#the-drill-doctrine)). Plain
|
|
`drills`, not a dot-directory
|
|
([drill-recorded.sh](../actions/drill-recorded/drill-recorded.sh#L49-L52)).
|
|
4. **`.github/workflows/release.yml`** — the caller, verbatim from
|
|
[Release workflow](#release-workflow) below.
|
|
5. **CI guard steps** in the repo's `ci.yml`:
|
|
|
|
```yaml
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
# changelog-monotonic compares HEAD against the merge base; a
|
|
# checkout that cannot resolve it is a hard failure in CI, not
|
|
# a skip (a guard that can quietly stop guarding is the failure
|
|
# shape these checks exist to refuse).
|
|
fetch-depth: 0
|
|
- uses: heavy-duty/ceremony/actions/changelog-armed@<pinned-tag>
|
|
- uses: heavy-duty/ceremony/actions/changelog-monotonic@<pinned-tag>
|
|
- uses: heavy-duty/ceremony/actions/drill-recorded@<pinned-tag>
|
|
# Unreleased: runner-isolated is not in 0.1.0. Adopt this step with
|
|
# the pin bump to the first tag that carries it; never mix refs.
|
|
- uses: heavy-duty/ceremony/actions/runner-isolated@<pinned-tag>
|
|
```
|
|
|
|
`changelog-armed` and `drill-recorded` take
|
|
`version-source: package-json` where that is the backend; every guard's
|
|
inputs and defaults are in its `action.yml`
|
|
([actions/](../actions/)). Adopting the agent team flow adds the
|
|
`docs-sync` step ([below](#adopting-the-agent-team-flow)).
|
|
|
|
`runner-isolated` asserts that no `pull_request`-triggered workflow
|
|
names a self-hosted runner — a PR workflow runs the branch's code, and
|
|
unreviewed fork code must never execute on your own hardware
|
|
([#58](https://github.com/heavy-duty/ceremony/issues/58)). It fires on
|
|
the PR that first mixes a PR trigger and a self-hosted `runs-on` in
|
|
one file; the unblock is splitting the workflow. A repo with **no**
|
|
self-hosted runner still wants it: the guard's value is the day
|
|
somebody adds one.
|
|
|
|
This guide documents `main`. New machinery is marked **unreleased**
|
|
here until a release tag ships it. If an action does not exist at the
|
|
consumer's pinned tag, adopt it with the pin bump to the first tag that
|
|
carries it; never mix a moving or newer ref into an otherwise exact-pin
|
|
consumer. In particular, `0.1.0` carries the three release guards above
|
|
plus `docs-sync`, but not `runner-isolated`.
|
|
6. **Labels automation** (optional but recommended): the caller from
|
|
[Labels automation](#labels-automation), plus `.github/labels.conf`
|
|
(panel + the repo's `scope:*` rows) and `.github/labeler.yml` (the
|
|
path→scope globs). Run `workflow_dispatch` once — **this bootstraps
|
|
the taxonomy, `release` label included**.
|
|
7. **The artifact hook** (optional): `.github/actions/release-artifact/`
|
|
per [The artifact hook](#the-artifact-hook). No hook → the source
|
|
tarball is the package.
|
|
|
|
From there the flow is the doctrine: ordinary PRs add their changelog
|
|
line, the ceremony PR makes
|
|
[the three stamps](../README.md#what-a-release-is), a human merges, the
|
|
machine transcribes.
|
|
|
|
## Convert an existing repo
|
|
|
|
The box/rig/cast path — the repo carries its own copy of the machinery
|
|
and hands it over. The conversion PR is release-flow work: label it
|
|
`release` if the repo's conventions ask for that, and either way it lands
|
|
as a green `NOTICE` no-op on main — the decide table's green rows exist
|
|
precisely so the machinery is safe to work on
|
|
([lib/decide.sh](../lib/decide.sh#L6-L12)).
|
|
|
|
- [ ] Replace `.github/workflows/release.yml` with the caller from
|
|
[Release workflow](#release-workflow) — **whole file**, keeping its
|
|
load-bearing comments. Check the result has **one** `push:` key
|
|
carrying both filters: YAML maps are last-key-wins, and a second
|
|
sibling `push:` silently kills a door (rig's review catch).
|
|
- [ ] Swap the guard *script* steps in `ci.yml` for the `uses:` steps in
|
|
the bootstrap list above (with `fetch-depth: 0` on the checkout).
|
|
- [ ] Replace `labels.yml` with the caller from
|
|
[Labels automation](#labels-automation); extract
|
|
`.github/labels.conf` from the old reconciler's embedded config —
|
|
the `panel=` roster line and the repo's `scope:*` rows
|
|
([the format](#labels-automation)). `.github/labeler.yml` stays as
|
|
it is (path globs are inherently repo-specific).
|
|
- [ ] Delete the now-shadowed copies — zero shared scripts remain:
|
|
`.github/scripts/release-notes.sh` (box, cast) or
|
|
`release-lib.sh` (rig), `changelog-armed.sh` (box),
|
|
`changelog-monotonic.sh`, `drill-recorded.sh`,
|
|
`labels-reconcile.sh`.
|
|
- [ ] Trim the repo's test suite to repo-specific tests: the machinery
|
|
tests go — they live in this repo's `test/` now, run by its CI —
|
|
while the repo's own surfaces stay (box/rig's install-channel halves
|
|
of `test/release.sh`, cast's `install-sh` tests). A machinery test
|
|
*file* goes whole when its subject moved (rig's
|
|
`test/labels-reconcile.sh` sourced the deleted reconciler), and so
|
|
do tests that pin the old workflow's shape — a grep or awk against
|
|
`release.yml`/`ci.yml` internals fails against the caller stub, not
|
|
because the stub is wrong (rig #13's conversion).
|
|
- [ ] Sweep the repo's other docs for pointers at the deleted paths —
|
|
`drills/README.md` and any labels doc typically cite the old
|
|
`.github/scripts/*.sh` by path; repoint them at the pinned actions.
|
|
A repo carrying its own copy of a doc the mirror vendors (rig's
|
|
root `LABELS.md`) retires it in the same PR: a hand-maintained
|
|
copy beside a machine-verified mirror is the drift the mirror
|
|
exists to end.
|
|
- [ ] Shrink CONTRIBUTING's release section to a pointer at
|
|
[this repo's README](../README.md) plus what is genuinely per-repo:
|
|
the drill meaning (`drills/README.md`), artifact notes, the
|
|
changelog house style if it differs from
|
|
[the portable rule](#the-changelog-rule).
|
|
- [ ] What stays, per repo, forever: `VERSION` (or the `package.json`
|
|
version), `CHANGELOG.md`, `drills/`, `.github/labeler.yml`,
|
|
`.github/labels.conf`, the optional
|
|
`.github/actions/release-artifact/` — the full kept-vs-moved table
|
|
is in [#1](https://github.com/heavy-duty/ceremony/issues/1).
|
|
|
|
## Release workflow
|
|
|
|
The reusable release workflow implements both doors of the ceremony — the
|
|
merge door (merging the `release`-labeled ceremony PR ships it) and the tag
|
|
door (a bare `X.Y.Z` tag push as the manual fallback and backfill). The
|
|
design essay lives in the workflow's own header comment; the doctrine in
|
|
issue #1.
|
|
|
|
The consumer's **entire** `release.yml`:
|
|
|
|
```yaml
|
|
name: release
|
|
# Triggers and permissions MUST live here (a called workflow cannot define them):
|
|
on:
|
|
# ONE push key, both filters — YAML maps are last-key-wins; a second sibling
|
|
# `push:` silently replaces the first and kills a door (rig's review catch).
|
|
push:
|
|
tags: ["**"] # every tag — a wrong tag must FAIL the assert loudly,
|
|
# never be skipped by a shape filter that didn't match
|
|
branches: [main]
|
|
permissions:
|
|
contents: write # tag ref create + release create + the bump push
|
|
pull-requests: write # decide's label read; the bump-fallback `gh pr create`
|
|
issues: write # --label on that fallback PR rides the issues API
|
|
jobs:
|
|
release:
|
|
uses: heavy-duty/ceremony/.github/workflows/release.yml@<pinned-tag>
|
|
with:
|
|
version-source: file # or: package-json
|
|
```
|
|
|
|
`version-source` is the only input: `file` (a `VERSION` file — box, rig,
|
|
incubator) or `package-json` (the version field, lockfile kept in sync on
|
|
the post-release bump — cast). Everything else a repo might vary is a change
|
|
to the ceremony itself, made in this repo, once.
|
|
|
|
Keep the merge door on `push` to `main` — never `pull_request`: a
|
|
`pull_request` run from a public fork gets a read-only `GITHUB_TOKEN` that
|
|
`permissions:` cannot raise (box#97), and every ceremony PR in this org is
|
|
cross-repo from a bot fork.
|
|
|
|
Bootstrap the version at `X.Y.Z-dev`, not bare: a first version that never
|
|
carried `-dev` hits the decide table's refuse row and has to ship by the
|
|
tag door instead (the known first-release edge, cast#111).
|
|
|
|
### The artifact hook
|
|
|
|
If the repository contains `.github/actions/release-artifact/action.yml`,
|
|
both doors invoke it — after the tag exists, before `gh release create` —
|
|
with the release `version` as input and `RELEASE_ASSETS_DIR` exported.
|
|
Contract for hook authors:
|
|
|
|
- Drop finished files into `$RELEASE_ASSETS_DIR`; every file there is
|
|
uploaded as a release asset.
|
|
- Exit non-zero to abort the release.
|
|
- The hook owns its own toolchain (checkout is done; install node, docker,
|
|
whatever it needs, itself).
|
|
|
|
A failed hook leaves the tag created but no release published. Recovery is
|
|
the tag door's semantics: fix the cause, then delete and re-push the same
|
|
tag (the tag door publishes for it), or run `gh release create` by hand from
|
|
a fixed tree. The merge door's nothing-exists assert will refuse a re-run of
|
|
the completed merge, by design.
|
|
|
|
No hook → no assets: for a pure-bash tree, GitHub's source tarball for the
|
|
tag IS the package. Worked examples land with the conversions: cast's tgz
|
|
build (#15) and incubator's GHCR image push (#16).
|
|
|
|
## Labels automation
|
|
|
|
The reusable labels workflow owns two independent jobs: additive path-based
|
|
`scope:*` labels and reconciliation of PR state, blockers, handoff, stale
|
|
status, and the `needs-ruling` invariants on both surfaces — the bare-flag
|
|
check and the 7-day comment-only nudge (#52; the sweep reads that flag and
|
|
never writes it). The consumer keeps its path mapping in
|
|
`.github/labeler.yml` and its review panel plus scope taxonomy in
|
|
`.github/labels.conf`.
|
|
|
|
The complete caller is:
|
|
|
|
```yaml
|
|
name: labels
|
|
on:
|
|
schedule: [{cron: "*/15 * * * *"}] # advisory; the handoff label is the real wake
|
|
workflow_dispatch: # bootstraps missing labels on a fresh repo
|
|
pull_request_target:
|
|
types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled]
|
|
# Unreleased — not in 0.1.0; add only with the first tag carrying ceremony#32.
|
|
issues:
|
|
types: [opened, labeled, unlabeled, assigned, unassigned, closed]
|
|
permissions:
|
|
contents: read
|
|
checks: read # mergeability/check-rollup read for PR state
|
|
statuses: read # commit-status rollup read for PR state
|
|
issues: write
|
|
pull-requests: write
|
|
jobs:
|
|
labels:
|
|
uses: heavy-duty/ceremony/.github/workflows/labels.yml@<pinned-tag>
|
|
```
|
|
|
|
Naming any permission sets every unnamed permission to `none`. Public
|
|
repositories allow check data to be read regardless, but a private consumer
|
|
needs both explicit reads above; without them the failure appears as an empty
|
|
`state:*` axis on the board rather than a red workflow run.
|
|
|
|
The `issues:` trigger is **unreleased** and is not in `0.1.0`. A consumer
|
|
pinned to `0.1.0` omits it. Add it only when bumping every ceremony reference
|
|
to the first tag carrying ceremony#32; never mix refs to adopt it early.
|
|
|
|
`pull_request_target` is intentional: fork PRs need the base repository's
|
|
token to write labels. The reusable workflow executes no PR code. It checks
|
|
out only the consumer's base branch and the pinned ceremony implementation.
|
|
The #52 ruling invariants ride exactly these triggers — the caller above is
|
|
unchanged since #18, so adopting them is a pin bump, not a stub edit.
|
|
|
|
`.github/labels.conf` has one mandatory panel setting, one mandatory
|
|
`triage-actors` setting, and then zero or more scope rows:
|
|
|
|
```text
|
|
panel=claude-bot example-codex-bot example-grok-bot
|
|
triage-actors=example-triage-bot
|
|
scope:cli|C5DEF5|The command-line surface
|
|
scope:docs|C5DEF5|Documentation
|
|
```
|
|
|
|
The mandatory `triage-actors=` setting is also **unreleased** and is not
|
|
accepted by `0.1.0`. At that tag the file contains `panel=` plus scope rows
|
|
only; adding `triage-actors=` is a parse failure, not an ignored setting. Add
|
|
it at the same pin bump as the `issues:` trigger, to the first tag carrying
|
|
ceremony#32 — never before it and never through mixed refs.
|
|
|
|
Both actor lists are whitespace-separated. `triage-actors` names the identities
|
|
allowed to mint issues without the sweep applying `needs-triage`. Label rows use exactly
|
|
`name|color|description`; blank lines are ignored and extra pipes are refused.
|
|
There are no comment lines: every non-blank line must be the `panel=`
|
|
setting, the `triage-actors=` setting, or a label row, so `#`-prefixed prose
|
|
is a parse failure, not a comment (rig #13's conversion found this the hard
|
|
way — keep the file data only).
|
|
Core state, blocker, work-queue, and release labels come from ceremony. Scope
|
|
rows remain consumer-owned because paths and surfaces differ by repository.
|
|
|
|
After adding the caller and configuration, run `workflow_dispatch` once to
|
|
bootstrap labels on a fresh repository. Scheduled and PR-triggered runs only
|
|
reconcile; they do not repeatedly upsert the taxonomy. When a ceremony pin
|
|
bump adds a core label, bump the pin first and then re-dispatch
|
|
`workflow_dispatch`; the scheduled sweep warns when the pinned taxonomy
|
|
declares a core label the repository lacks.
|
|
|
|
## Doctrine mirror
|
|
|
|
Machinery is consumed by reference — GitHub fetches the workflows and
|
|
actions above from the pin at run time — but documents have no runtime: an
|
|
agent reads the working tree it stands in. So the agent-facing doc set
|
|
(ceremony's `docs/VENDORED.txt`: AGENTS.md, TRIAGE.md, BUILDER.md,
|
|
REVIEWER.md, LABELS.md) is vendored into each consumer at **`.ceremony/`**,
|
|
byte-identical to ceremony at the pin, plus a generated `.ceremony/README.md`
|
|
marking the directory machine-managed. `actions/docs-sync` owns the copy:
|
|
`--fix` writes it (and deletes what the manifest dropped — mirror means
|
|
mirror), `--check` re-diffs it in CI on every PR, so a hand edit or a stale
|
|
pin goes red instead of quietly governing.
|
|
|
|
The consumer's ci.yml gains the guard alongside the others:
|
|
|
|
```yaml
|
|
- uses: actions/checkout@v4
|
|
- uses: heavy-duty/ceremony/actions/docs-sync@<pinned-tag>
|
|
```
|
|
|
|
`mode` defaults to `check`. There is no ref input: the action reads the pin
|
|
from the consumer's own `.github/workflows/release.yml` — the same single
|
|
`uses: …/release.yml@<ref>` line that pins the machinery, so one pin governs
|
|
machinery and doctrine alike, and a second pin cannot fall out of sync.
|
|
|
|
**Bootstrap on adoption**: add the release and labels callers first (the pin
|
|
must exist — the mirror is verified against it), then run `--fix` once from
|
|
the repo root and commit `.ceremony/` together with the callers:
|
|
|
|
```sh
|
|
curl -fsSL "https://raw.githubusercontent.com/heavy-duty/ceremony/<pinned-tag>/actions/docs-sync/docs-sync.sh" \
|
|
| bash -s -- --fix
|
|
```
|
|
|
|
If the repo has no root `AGENTS.md`, `--fix` also scaffolds the thin stub
|
|
that routes agents to `.ceremony/AGENTS.md` — created once, never
|
|
overwritten; it is per-repo content the moment you edit it, so `--check`
|
|
asserts only that it exists.
|
|
|
|
Bumping the pin re-syncs the mirror in the same PR —
|
|
[the pin-bump procedure](#the-pin-bump-procedure).
|
|
|
|
## Version pinning
|
|
|
|
- **Pin an exact ceremony release tag** — `@0.1.0`, never a branch and
|
|
never a moving major pointer: the family pins things and reviews
|
|
updates ([#1 D2](https://github.com/heavy-duty/ceremony/issues/1)).
|
|
Every `uses:` of this repo in the consumer — the two workflow callers
|
|
and the guard steps — names the same tag.
|
|
- **Bump by PR, every reference together.** Before bumping, read the
|
|
ceremony's own `CHANGELOG.md` section for the new version (the release
|
|
body on its
|
|
[releases page](https://github.com/heavy-duty/ceremony/releases) is
|
|
that section, verbatim). One bump PR updates **every** ceremony `uses:`
|
|
reference in the repo to the new tag — the workflow callers *and* each
|
|
guard step. The exact count is tag-dependent: it is the workflow caller
|
|
or callers plus the guards that the pinned tag carries. Changing only
|
|
one line leaves the consumer split across ceremony versions, which the
|
|
same-tag rule above forbids. A repo that has adopted the agent team flow
|
|
additionally bumps the mirror in the same PR —
|
|
[the pin-bump procedure](#the-pin-bump-procedure).
|
|
- **One pin governs machinery and doctrine.** The ref in the consumer's
|
|
`release.yml` `uses:` line is the single pin: `docs-sync` reads it from
|
|
exactly there and verifies the `.ceremony/` mirror against it — there
|
|
is no second pin to fall out of sync (#19).
|
|
|
|
## The changelog rule
|
|
|
|
The portable version of the family's contributor rule — the repo's own
|
|
CONTRIBUTING may sharpen it, but this is the floor the guards assume:
|
|
|
|
- **Every PR that changes behavior adds one line** under `## Unreleased`.
|
|
- **Grouped changelogs keep three standing headings:** `### Added`,
|
|
`### Changed`, and `### Fixed`. The ceremony PR's hand-edited re-arm seeds
|
|
all three; append under one instead of creating a heading at the top
|
|
anchor. Create `Deprecated`, `Removed`, or `Security` only when a change
|
|
genuinely needs that rarer kind.
|
|
- **Insert above the heading below — never type over it.** Replacing a
|
|
shipped `## X.Y.Z` heading with your entry deletes that release's
|
|
section, silently; this exact edit is why the
|
|
[monotonic guard](../README.md#changelog-monotonic--shipped-headings-are-append-only)
|
|
exists (box#122).
|
|
- **One line: say what changed, and stop.** Lead with the surface, not
|
|
the mechanism — "`state:needs-human` is set at handoff" beats "the
|
|
labels workflow now also wakes on `labeled`". The why and the how
|
|
belong in the PR body, where anyone chasing the reasoning already goes.
|
|
- **Cite the issue or PR** — `(#141)`.
|
|
- **Mark a breaking change** with a leading `BREAKING:`.
|
|
- Flat changelogs remain flat; the standing-heading rule applies only when a
|
|
repo already groups its entries.
|
|
|
|
## Adopting the agent team flow
|
|
|
|
The team flow (discussion → triage → issue → build → review → human
|
|
merge) is **optional per repo and separable from the release ceremony**:
|
|
a repo can adopt release-only and take the team flow later — incubator's
|
|
initial posture (#16). The model is this repo's own
|
|
[CONTRIBUTING](../CONTRIBUTING.md) ("How the other repos use this");
|
|
this is the checklist:
|
|
|
|
- [ ] **Enable Discussions** — the triage door exists or the pipeline
|
|
has no intake.
|
|
- [ ] **Vendor the doctrine**: run `docs-sync --fix` (#19) to materialize
|
|
`.ceremony/{AGENTS,TRIAGE,BUILDER,REVIEWER,LABELS}.md` —
|
|
byte-identical to this repo at the pinned ref — plus the generated
|
|
`.ceremony/README.md` (machine-managed marker) and, if the repo has
|
|
none, the thin root `AGENTS.md` stub ("governed by
|
|
heavy-duty/ceremony; read `.ceremony/AGENTS.md` first; repo
|
|
specifics in CONTRIBUTING"). The stub is scaffolded once and never
|
|
overwritten; the mirror is machine-written and never hand-edited.
|
|
Commit `.ceremony/` together with the workflow callers.
|
|
- [ ] **Guard the mirror in CI**: add the `docs-sync` check step
|
|
alongside the other guards —
|
|
|
|
```yaml
|
|
- uses: heavy-duty/ceremony/actions/docs-sync@<pinned-tag>
|
|
```
|
|
|
|
(`mode: check` is the default.) Hand-editing a vendored file, or
|
|
bumping the pin without re-syncing, goes red (#19).
|
|
- [ ] **Reduce tool-specific files** (`CLAUDE.md`, …) to one pointer line
|
|
at the root `AGENTS.md`, so every harness converges on the same
|
|
router.
|
|
- [ ] **Point CONTRIBUTING at the mirror**: a short header telling agents
|
|
to read `.ceremony/` first — agents never leave the working tree to
|
|
read the rules — followed by only what is genuinely per-repo: the
|
|
review panel roster, the `scope:*` set, the drill meaning, the
|
|
repo's code conventions.
|
|
- [ ] **Name the review panel**: the roster table in CONTRIBUTING and the
|
|
`panel=` line in `.github/labels.conf` — the required verdicts for
|
|
any PR are the panel minus its author (#10).
|
|
- [ ] **Bootstrap the issue-flow labels**: the labels
|
|
`workflow_dispatch` once ([above](#labels-automation)), or the hand
|
|
commands in [LABELS.md](../LABELS.md).
|
|
- [ ] **State the single-writer rule** in the repo's own docs: only
|
|
triage mints issues; everyone else opens discussions.
|
|
|
|
### The pin-bump procedure
|
|
|
|
Bumping the ceremony pin is **one PR carrying both halves**: every
|
|
ceremony `uses:` reference — the workflow callers *and* each guard step,
|
|
[all to the same new tag](#version-pinning) — and the re-synced
|
|
`.ceremony/` mirror —
|
|
run `docs-sync --fix` locally, or let the red `--check` on the bump PR
|
|
tell you what is stale. The CI guard is what makes a half-done bump —
|
|
pin without mirror, or mirror without pin — unmergeable (#19). This is
|
|
how a process change rolls out to a governed repo: deliberately, per
|
|
repo, reviewed.
|