fix(release): promote the curated [Unreleased] instead of regenerating - #154
Conversation
Ports the core library's fix (livetemplate/livetemplate#511) to the client, which has the same defect. release.sh rebuilt CHANGELOG.md from a raw git log dump on every release, so a hand-written [Unreleased] section never became the release notes. It survived only as a stranded section below the new version, still titled [Unreleased], while the notes that shipped were bare commit subjects — the release-script chore billed equally with the change users see. Hit on both v0.19.1 and v0.20.0, each needing a manual curation commit plus a gh release edit, since publish_github extracts its notes from the clobbered file. Promote instead: retitle [Unreleased] as the release heading and leave its content alone. The commit-subject dump stays as the fallback for releases nobody curated, so nothing regresses when the section is absent or empty. Also refuse to start when the file has more than one [Unreleased] heading, since release-note extraction stops at the first and a duplicate means one section can never ship. scripts/test_release_changelog.sh is shared with core, asserting on which branch was taken rather than on the resulting file — this repo's fallback writes an "Initial release" block when no tag exists, so the end state differs while the promote-vs-regenerate decision does not. Refs livetemplate/livetemplate#511 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ui2cwpeGkrUfRt8rh2FgGG
ReviewSolid fix — the core defect (regenerating over a curated Gap: the duplicate-heading guard has no automated test coverage.
Minor / non-blocking:
No security or correctness issues found in the |
Ports the same two follow-ups made in the core library's PR. The took_fallback helper piped generate_changelog into `grep -q`. Sourcing release.sh brings `set -o pipefail` with it, and grep -q exits on its first match, so the closed pipe could make generate_changelog exit non-zero and fail the whole pipeline — intermittently, depending on which finished first. Measured at 9 failures in 15 runs in the core repo; capturing to a variable instead is 0 in 30 here and there. The duplicate-[Unreleased] guard now also names the likeliest cause: a release that takes the commit-subject fallback leaves an empty [Unreleased] behind, so a later curated one added above it trips the guard, and the empty section is the one to drop. Refs livetemplate/livetemplate#511 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ui2cwpeGkrUfRt8rh2FgGG
ReviewWhat it does: Ports the core-library fix so Correctness
Test coverage gap
Minor
Overall the change is well-scoped, the reasoning in the comments is clear, and the fallback path is preserved so this shouldn't regress uncurated releases. Main actionable item is the missing automated coverage for the duplicate-heading guard. |
Ports the core library's fix to the client, which has the same defect. Refs livetemplate/livetemplate#511.
The problem
release.shrebuiltCHANGELOG.mdfrom a rawgit logdump on every release, so a hand-written## [Unreleased]section never became the release notes. It survived only as a stranded section below the new version, still titled[Unreleased], while the notes that shipped were bare commit subjects.Hit four times in two days — core v0.19.1 and v0.20.0, client v0.19.1 and v0.20.0 — each needing a manual curation commit plus a
gh release edit, sincepublish_githubextracts its notes from the clobbered file.The fix
Promote instead of regenerate. Retitle
[Unreleased]as the release heading, leave its content alone. This is what the file's own header already claims: Keep a Changelog's workflow is maintain[Unreleased], promote on release.The commit-subject dump stays as the fallback for releases nobody curated, so nothing regresses when the section is absent or empty.
Refuse to start on a duplicate
[Unreleased]. Release-note extraction stops at the first heading, so a duplicate means one section can never ship. That is exactly how the stale v0.8.5 notice sat unnoticed in this file until #509 — this makes the same situation loud:It runs in
check_prerequisites, before anything mutates.Verification
scripts/test_release_changelog.sh— new, sourcingrelease.shwithRELEASE_SH_LIB=1so the functions are testable without running a release:These were run against the previous
release.shto confirm they discriminate. Two fail there, reporting the exact defect:Tests 2/3 assert on which branch was taken rather than the resulting file — the client's fallback writes an "Initial release" block when no tag exists, so the end state differs between repos while the promote-vs-regenerate decision does not. That keeps one test file valid for both.
Duplicate guard verified by injecting two headings and confirming it aborts before the dirty-tree check.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ui2cwpeGkrUfRt8rh2FgGG