## Summary
- The `ag-d0f` "kill the silent fallback" change made
`scripts/extract-release-notes.sh` hard-error when no curated
`docs/releases/*-notes.md` exists (lines 50–60), but the file header
still advertised *"Falls back to CHANGELOG.md extraction if no curated
notes exist."*
- That comment contradicted the implemented behavior and could mislead a
maintainer about release-pipeline behavior (the exact class of
silent-fallback confusion that shipped raw CHANGELOG dumps as the
3.0.0/3.0.1 release bodies).
- Rewrites the header docstring to state the no-fallback contract:
curated notes + matching CHANGELOG section are required; a missing file
is a hard error; structure is enforced by
`scripts/validate-release-notes.sh`.
Comment-only change — no behavior change.
## Test plan
- [x] `shellcheck scripts/extract-release-notes.sh` — clean
- [x] `bats tests/scripts/validate-release-notes.bats` — 14/14 green
- [x] `git diff --stat` — 7/-4, comment lines only
Closes-scenario: ag-d0f#fallback-comment
Bounded-context: BC5-Runtime
Evidence: scripts/extract-release-notes.sh; bats
tests/scripts/validate-release-notes.bats (14/14)
Fixes two compounding misses on the v3.0.x release notes and makes the
failure mechanically impossible.
**Root cause:** extract-release-notes.sh silently fell back to dumping
the raw CHANGELOG when no curated docs/releases/*-notes.md existed — so
3.0.0/3.0.1 shipped raw dumps, and the curated notes I then wrote did
not follow skills/release/references/release-notes.md (invented
headings, bold-lead-in bullets instead of action labels). The template
also had no major-release variant.
**Standard (template + codex twin):** add a major-release (X.0.0)
variant — required ## Breaking Changes section + migration link;
document the hard gate.
**Conform:** rewrite both 3.0.x curated notes to the standard (major
shape for 3.0.0 w/ Breaking Changes + canonical taxonomy + action
labels; patch shape for 3.0.1).
**Enforce:** scripts/validate-release-notes.sh (existence + sections +
taxonomy + action labels + Breaking-Changes-for-major) + 8 bats cases;
wired as a hard gate in release.yml before notes extraction;
extract-release-notes.sh now fails instead of silently dumping raw
CHANGELOG.
Local: both notes PASS the validator, 8/8 bats, shellcheck clean, codex
parity passes.
Closes-scenario: ag-d0f#release-notes-standard
Bounded-context: BC5-Runtime
Evidence: scripts/validate-release-notes.sh
The awk extractor in extract-release-notes.sh captured everything after
the `## [VERSION]` heading line — including the blank line that always
follows the heading in CHANGELOG.md. When the script then wrapped that
section inside the `<details><summary>...</summary>\n\n$SECTION\n\n</details>`
boilerplate, the leading blank from $SECTION collided with the explicit
`echo ""` before it, producing a double-blank line between
`<summary>Full changelog</summary>` and the first subsection heading.
Same pattern at the trailing edge before `</details>`.
Caught when operator inspected the published v2.41.0 release body.
Fix: strip leading and trailing empty lines from CHANGELOG_SECTION
before any downstream `echo` wraps it. Two short awk passes — first
drops leading blanks (once a non-blank line is seen, flip a sticky
flag), second drops trailing blanks (track the last non-blank line
number, replay up to it).
Already applied retroactively to the live v2.41.0 GitHub Release body
via `gh release edit v2.41.0 --notes-file <regenerated>`.
The release workflow failed for v2.16.0 because
extract-release-notes.sh looked for docs/CHANGELOG.md, which was
stale. The root CHANGELOG.md is the source of truth.
Declutter repo root — keep only AGENTS.md, CLAUDE.md, README.md, LICENSE,
GOALS.yaml, PRODUCT.md at top level. All cross-references updated across
README, .github templates, docs/, scripts/, skills/, and tests/.