From 566712c690d9d34ac284540a80569541af669c22 Mon Sep 17 00:00:00 2001 From: claude-bot-andresmgsl Date: Fri, 24 Jul 2026 10:24:31 +0000 Subject: [PATCH] docs: CONSUMERS and drills describe fragment-mode adoption Bootstrap arms with a preamble plus changelog.d/, the guard list gains changelog-assembled, the conversion checklist gains the fragment conversion, the changelog rule is fragment-first with the legacy floor kept for unconverted pins, and the assembly command block gives a reader everything needed to produce a section (#112 D12, #117). --- docs/CONSUMERS.md | 124 +++++++++++++++++++++++++++++++++------------- drills/README.md | 6 ++- 2 files changed, 94 insertions(+), 36 deletions(-) diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index 2b285bc..5ad6ee8 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -48,19 +48,23 @@ the machinery at all: [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: +2. **An armed changelog: a `CHANGELOG.md` preamble plus `changelog.d/`.** + The changelog file starts as preamble only — no section; the first + release writes the first one. The fragments directory beside it is the + arming (#112): it carries a `README.md` marker naming the assembler and + the doctrine — take ceremony's own + [changelog.d/README.md](../changelog.d/README.md) at the pin — which is + what keeps the directory tracked while it holds no fragments and what + `changelog-armed` asserts. Every behavior-change PR then writes + `changelog.d/.md` ([The changelog rule](#the-changelog-rule)); + the release PR assembles the section + ([Assembling a release section](#assembling-a-release-section)). - ```markdown - ## Unreleased - - ### Added - - ### Changed - - ### Fixed - ``` + Fragment mode is **unreleased** and not in `0.1.0`. A consumer pinned + to `0.1.0` bootstraps the legacy shape instead — the preamble plus an + empty `## Unreleased` section for entries to land under — and converts + on the pin bump to the first tag carrying fragment mode; never mix + refs to adopt it early. 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 @@ -73,13 +77,19 @@ the machinery at all: ```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). + # changelog-monotonic and changelog-assembled compare 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@ - uses: heavy-duty/ceremony/actions/changelog-monotonic@ + # Unreleased: changelog-assembled is not in 0.1.0. Adopt this step + # with the pin bump to the first tag that carries it; never mix + # refs. Green NOTICE on every non-release PR; on a release PR it + # asserts the stamped section is exactly the fragments it consumed. + - uses: heavy-duty/ceremony/actions/changelog-assembled@ - uses: heavy-duty/ceremony/actions/drill-recorded@ # 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. @@ -105,8 +115,9 @@ the machinery at all: 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`. + consumer. In particular, `0.1.0` carries `changelog-armed`, + `changelog-monotonic` and `drill-recorded` plus `docs-sync`, but not + `changelog-assembled` or `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 @@ -116,8 +127,8 @@ the machinery at all: 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 +From there the flow is the doctrine: ordinary PRs write their fragment, +the ceremony PR makes [the three stamps](../README.md#what-a-release-is), a human merges, the machine transcribes. @@ -143,6 +154,18 @@ precisely so the machinery is safe to work on 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). +- [ ] Convert the changelog to fragments (requires a pin at the first tag + carrying fragment mode — not `0.1.0`): move every entry under + `## Unreleased` to `changelog.d/.md`, verbatim — the filename + is derivable from the entry's own `(#N)`; an entry citing several + issues goes to the file for the first cited — delete the + `## Unreleased` heading, and add the `changelog.d/README.md` marker + ([bootstrap step 2](#bootstrap-a-new-repo)). Published sections stay + byte-identical; `changelog-monotonic` proves that on the conversion + PR, and `changelog-armed` refuses a surviving `## Unreleased` the + moment the directory exists. Rewrite the repo's own contributor + docs that say "add a line under `## Unreleased`" in the same PR — + split either way, main lies for as long as the split lasts. - [ ] 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), @@ -170,7 +193,7 @@ precisely so the machinery is safe to work on 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`, + version), `CHANGELOG.md`, `changelog.d/`, `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). @@ -395,25 +418,58 @@ Bumping the pin re-syncs the mirror in the same PR — 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). +- **Every PR that changes behavior writes one fragment**: + `changelog.d/.md`, named for the authorizing issue — + `-.md` for cross-repo work carrying `Part of #N` — + so the name is known at claim time and two builders can only collide by + working the same issue (#112 D2). Never an edit to `CHANGELOG.md`: the + release PR assembles the section + ([below](#assembling-a-release-section)). +- **The fragment is the prose, not a description of it** (#112 D3): the + exact lines that will be published — no front-matter, no `## ` heading + (that one is the assembler's to write). `changelog-armed` refuses a + malformed fragment on the PR that wrote it. +- **Grouped repos group inside the fragment**: `### Added`, `### Changed`, + `### Fixed` headings with bullets under them; create `Deprecated`, + `Removed`, or `Security` only when a change genuinely needs that rarer + kind. A repo is grouped or flat, never both (#112 D4). The assembler + merges groups in canonical order — Added, Changed, Fixed, Removed, + Deprecated, Security, then anything else first-seen — and inside a + group entries read newest issue first (#112 D5). - **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. +- A repo not yet on fragment mode — no `changelog.d/` — keeps the legacy + floor until its conversion: one line under `## Unreleased`, inserted + **above** the heading below it, never over it (replacing a shipped + heading deletes that release's section silently — box#122, why the + [monotonic guard](../README.md#changelog-monotonic--shipped-headings-are-append-only) + exists), appended under a standing `### ` heading where the repo groups. + +## Assembling a release section + +The ceremony PR's changelog stamp is one command, run **by hand, never in +CI** — the assembled section must land in the release PR's diff, where +the panel reads it (#112 D12). A consumer runs the tool from a ceremony +checkout at its own pin: + +```sh +git clone --depth 1 --branch https://github.com/heavy-duty/ceremony /tmp/ceremony +/tmp/ceremony/bin/changelog-assemble +``` + +Run it at the repo root. It folds every `changelog.d/` fragment into a +new `## X.Y.Z — DATE` section on top of `CHANGELOG.md` (DATE is today's +UTC date; pass one as a second argument to choose it) and deletes the +fragments it consumed — commit both halves together. `--check` prints the +would-be section body without touching anything; read it before running +the real thing. In CI, `changelog-assembled` replays the run from the +merge base and refuses a stamp that is not byte-for-byte what the +fragments assemble to — a mis-run hand step fails the PR, not the +published release. ## Adopting the agent team flow diff --git a/drills/README.md b/drills/README.md index cbbe0d0..b80ea32 100644 --- a/drills/README.md +++ b/drills/README.md @@ -26,8 +26,10 @@ drill is where they run live *before* a version rests on them. carrying the candidate tree with `CEREMONY_SELF_REF` rewritten to the candidate SHA in every pin carrier, and records that one-line deviation in its record. From the second release on, this paragraph is moot. -3. Give it a fixture `VERSION` / `CHANGELOG.md` / `drills/` in the armed - state (`X.Y.Z-dev`, `## Unreleased` on top). +3. Give it a fixture `VERSION` / `CHANGELOG.md` / `changelog.d/` / + `drills/` in the armed state (`X.Y.Z-dev`, the fragments directory with + its `README.md` marker plus at least one fragment for the ceremony to + consume). 4. Exercise both doors, one probe at a time: 1. a merge-door ceremony publishes exactly one release and re-arms main