test/release.sh demanded a literal '## Unreleased' heading extracting
non-empty with '#32' in it — all false by construction on the very tree
the release PR produces, so the first real 'release: 0.1.0' PR turned CI
red and the ceremony blocked itself. Both fork rehearsals missed it: they
tag a branch, which runs release.yml and never ci.yml. The guard now
asserts its actual purpose — the TOP section, whatever its name, extracts
non-empty via the exact function release.yml runs — and passes on both
legitimate tree states (verified on main's shape and on a stamped copy).
Fixes#44
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The PR-then-tag half of box#83's flow, rig's side. CHANGELOG.md starts
with an Unreleased section (feature PRs land their entry as part of the
PR — box's convention, now written into CONTRIBUTING alongside the
release ritual). On a tag push, release.yml asserts the bare tag equals
the tree's own VERSION — a mismatch fails loudly and creates nothing —
then creates the GitHub release with that version's changelog section as
the body, extracted by changelog_section in .github/scripts/release-lib.sh:
one function, sourced by the workflow and driven by test/release.sh
against fixtures and the shipped CHANGELOG.md itself. No assets — for a
pure-bash tree, the tag's source tarball IS the package.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>