lib/changelog.sh + bin/changelog-assemble — read the fragments, assemble one section, consume them #114
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#114
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Part of #112, and the mechanism every other child in it depends on.
lib/changelog.shis the family's single definition of what a changelog section is — the publisher and the guards both go through it, deliberately, so they cannot disagree. The fragment reader and the assembler join it there for the same reason:changelog-armed(#115) checks fragments on every PR andactions/changelog-assembled(#116) checks the release PR's stamp against the fragments it consumed; all three must be one definition of "a well-formed fragment".The shapes in play, both real: ceremony's changelog is flat (bare
-bullets under## Unreleased), and box, rig, cast and incubator are grouped (### Added/### Changed/### Fixed).Spec
Decisions D1–D5 and D9 of #112 are settled; this issue implements them.
lib/changelog.sh— three additionschangelog_fragments <dir>— print fragment paths in publication order, one per line. Considers*.mdonly; skipsREADME.md. Order: by the trailing issue number descending, tie-broken on the filename, so the newest issue reads first exactly as every section in the family reads today. Empty output for an absent or fragment-free directory; that is not an error here (the callers decide).changelog_fragment_problem <file>— print the first reason a fragment cannot publish and return 1; silence returns 0. Same contract and diagnostic style aschangelog_section_problem. The rules, and each one's failure it prevents:^([a-z][a-z0-9-]*-)?[0-9]+\.md$^##line##level belongs to the assembler, and a stray one would split the section^[[:space:]]*[-*][[:space:]]bullet###heading without a bullet before the next heading or EOFchangelog_assemble <dir>— print the assembled section body (no##line) for every fragment in the directory, inchangelog_fragmentsorder. Grouped and flat are both handled:###in a fragment are ungrouped;Added,Changed,Fixed,Removed,Deprecated,Security, then any other group in first-seen order, so a repo inventing a group gets it appended rather than dropped;bin/changelog-assemble— the release PR's one command<date>defaults todate -u +%F.--changelogdefaults toCHANGELOG.md,--dirtochangelog.d.## <version> — <date>, a blank line, the assembled body and a blank line immediately above the first^##line of the changelog — or after the preamble if the file has no section yet — then delete exactly the fragments it consumed.README.mdand anything not matching the fragment name pattern are never deleted.--check: print the section that would be written to stdout, write nothing, delete nothing, exit nonzero on any refusal. This is the mode CI and #116 use.changelog_fragment_problem; mixed grouped/flat; a## <version>section already present in the changelog (the ceremony was already run).0assembled,1refusal,2usage. Same shape asbin/changelog-section.The tool is run by hand in the release PR, from a checkout of ceremony at the consumer's pin (D12 of #112) — it is not a CI step, because the assembled section must land in the PR diff where the panel reads it.
Tasks
lib/changelog.sh—changelog_fragments,changelog_fragment_problem,changelog_assemble, each with the header comment this file's functions carry (what it refuses and why).bin/changelog-assemble— argument parsing, write mode,--check, the four refusals, the exit codes.test/changelog.test.sh— extend for the three lib functions.test/changelog-assemble.test.sh— the CLI, driven against constructed trees like the other guards' suites.test/run.shgreen.Acceptance criteria
changelog.d/README.mdsurvives.--checkis provably read-only: same tree before and after, byte for byte.changelog_section "$CHANGELOG" "$VERSION"on the assembled file returns exactly the body the assembler printed — the publisher and the assembler agree by test, not by inspection.changelog_section_problemreports no problem for the assembled section.Test plan
Constructed trees, no git required (the same discipline as
test/changelog-armed.test.sh):Must pass — flat single fragment; flat many fragments (order asserted,
10.mdbefore9.md, i.e. numeric not lexical); grouped fragments merging into shared groups; a cross-repo fragment name (ceremony-14.md) ordering beside local ones; a fragment with multiple bullets in one group; continuation lines and em dashes; an unnamed group appended last; assembly into a changelog whose only content is the preamble.Must fail, each with a named file in the diagnosis — empty directory; directory with only
README.md; a fragment with no bullet; a fragment with a###heading and no bullet under it; a fragment carrying a##line;notes.txt,12.markdown,Fix-12.md(name pattern); grouped + flat mixed; a changelog that already has the## <version>section.Round trip — assemble, then
bin/changelog-section <version>on the result, and assert the output equals--check's output for the same inputs.Idempotence — a second write-mode run over the consumed directory refuses with "zero fragments" rather than writing an empty section.
Dependencies
Part of #112. Blocks #115 and #116.
Claiming — starting now. Branch
build/114-changelog-assemble; draft PR follows shortly.Draft is built and marked ready for review: PR #120. All acceptance criteria checked (87 new test checks;
test/run.sh16/16, CI shellcheck sweep clean).