npm run version:check compares the top dated CHANGELOG heading against the manifest. It never inspects [Unreleased], so work that lands between cuts accumulates there unrecorded and nothing reports it.
This is not hypothetical: [Unreleased] documented 1 of the 21 commits since v0.8.1 until #384 backfilled it by hand. Twenty commits — including the ADR-0031 store-time CSS boundary, the write-path partition enforcement, and both CI changes — were invisible to every gate in the repo.
Why it matters more than it used to
Before #383 this was cosmetic: a thin [Unreleased] meant a thin file in the repo, fixed at cut time by whoever remembered. The release job now generates GitHub Release notes from the tag's CHANGELOG section, so an unreconciled changelog becomes thin public Release notes, permanently, on the surface people see first.
docs/runbooks/release.md currently patches this with a manual step — hand-diff [Unreleased] against git log <last-tag>..main at every cut. That works exactly as well as the person doing it remembers to.
What to decide
The obvious fix is to have check-version-consistency.ts compare [Unreleased] against the commit range since the last tag and fail when it is materially behind. Three things make that a real decision rather than a patch:
- What counts as "behind"? A strict commit-count comparison would fail on any docs-only or chore commit that legitimately does not warrant an entry. Some heuristic is needed — conventional-commit type filtering, or a floor, or an opt-out trailer.
- Where does it run?
version:check is in the pre-commit hook, which already runs well over two minutes. Adding a git log walk is cheap, but failing a routine commit because the changelog is behind would be hostile — this probably belongs in CI only, or gated to release branches.
- Escape hatch. There must be a way to land work without touching the changelog, or the gate gets bypassed with
--no-verify and stops meaning anything.
Worth noting the ERD and OpenAPI freshness gates solve the same shape of problem (generated artifact drifts from source) by regenerating and diffing. The changelog cannot be generated, which is exactly why it rots.
Found during a repo sweep (Phase 9 reconcile).
npm run version:checkcompares the top dated CHANGELOG heading against the manifest. It never inspects[Unreleased], so work that lands between cuts accumulates there unrecorded and nothing reports it.This is not hypothetical:
[Unreleased]documented 1 of the 21 commits since v0.8.1 until #384 backfilled it by hand. Twenty commits — including the ADR-0031 store-time CSS boundary, the write-path partition enforcement, and both CI changes — were invisible to every gate in the repo.Why it matters more than it used to
Before #383 this was cosmetic: a thin
[Unreleased]meant a thin file in the repo, fixed at cut time by whoever remembered. Thereleasejob now generates GitHub Release notes from the tag's CHANGELOG section, so an unreconciled changelog becomes thin public Release notes, permanently, on the surface people see first.docs/runbooks/release.mdcurrently patches this with a manual step — hand-diff[Unreleased]againstgit log <last-tag>..mainat every cut. That works exactly as well as the person doing it remembers to.What to decide
The obvious fix is to have
check-version-consistency.tscompare[Unreleased]against the commit range since the last tag and fail when it is materially behind. Three things make that a real decision rather than a patch:version:checkis in the pre-commit hook, which already runs well over two minutes. Adding agit logwalk is cheap, but failing a routine commit because the changelog is behind would be hostile — this probably belongs in CI only, or gated to release branches.--no-verifyand stops meaning anything.Worth noting the ERD and OpenAPI freshness gates solve the same shape of problem (generated artifact drifts from source) by regenerating and diffing. The changelog cannot be generated, which is exactly why it rots.
Found during a repo sweep (Phase 9 reconcile).