Problem
PR #474 (the calls.py decomposition) merged without a CHANGELOG entry, HISTORY.md update, or KNOWN_ISSUES.md cleanup — documentation that should have been part of the feature PR per the project's release workflow. The pre-commit hooks didn't catch it because none of those files were in the commit: hooks like check_version_sync.py and check_doc_counts.py run only when their target files change, not when substantive code changes without corresponding doc updates.
Proposed solution
A new script scripts/check_changelog_updated.py modeled on the existing check_*.py pattern. Runs at pre-push stage (not per-commit — would be too noisy during feature-branch iteration) and in CI.
The mechanism
- Diff the current branch against
origin/main
- Sort changed files into three buckets:
- Substantive (requires CHANGELOG):
vera/, spec/, SKILL.md
- Exempt:
tests/, scripts/, .github/, docs/, examples/, CHANGELOG.md, HISTORY.md, README.md, ROADMAP.md, KNOWN_ISSUES.md, FAQ.md, CONTRIBUTING.md, pyproject.toml, uv.lock
- Unclassified — treated as substantive (conservative default)
- If any substantive file changed, verify CHANGELOG.md diff contains either a new
+- bullet under [Unreleased] or a new +## [X.Y.Z] heading
- If missing, fail with a helpful message listing the substantive files
Escape hatches
Some PRs legitimately don't need a CHANGELOG entry (typo fix in a code comment, pure refactor with no user-visible impact even at the bug-fix level). Two ways to skip:
- Commit trailer:
Skip-changelog: <reason> in any commit message on the branch
- PR label:
skip-changelog (CI-only)
Integration
.pre-commit-config.yaml — pre-push stage:
- id: check-changelog-updated
name: check CHANGELOG updated for substantive changes
entry: python scripts/check_changelog_updated.py
language: system
pass_filenames: false
stages: [pre-push]
CI lint job — one line so it fires on PRs regardless of local hooks:
- name: Check CHANGELOG updated
run: python scripts/check_changelog_updated.py
Error message format
ERROR: Substantive changes detected but CHANGELOG.md not updated.
Files changed that require a CHANGELOG entry:
vera/wasm/calls.py
vera/wasm/calls_arrays.py
...
Add an entry under [Unreleased] in CHANGELOG.md, or include
'Skip-changelog: <reason>' in a commit message trailer.
Why pre-push, not pre-commit
Feature branches typically have 5–20 commits. The final one (or a dedicated release-prep commit) updates CHANGELOG. Running on every commit would block every intermediate commit with "no CHANGELOG entry" — pure friction. Pre-push gives the whole branch's worth of commits before checking.
Scope
~100 lines of Python + ~4 lines of YAML + a unit test file. Estimated 30 minutes — 1 hour including tests.
Out of scope (natural follow-ons)
- HISTORY.md check on version bumps — if
pyproject.toml's version changed, require a matching HISTORY.md row
- Issue-close check — if the PR body says "Closes #X", require #X be removed from ROADMAP.md (matches the existing "completed items are deleted from ROADMAP.md" convention)
- Spec-change check — if
spec/ changed, require scripts/check_spec_examples.py allowlist is regenerated (addresses a known issue tracked in memory about silent duplicate keys)
These would extend the pattern but can wait until the basic CHANGELOG check proves useful.
Problem
PR #474 (the calls.py decomposition) merged without a CHANGELOG entry, HISTORY.md update, or KNOWN_ISSUES.md cleanup — documentation that should have been part of the feature PR per the project's release workflow. The pre-commit hooks didn't catch it because none of those files were in the commit: hooks like
check_version_sync.pyandcheck_doc_counts.pyrun only when their target files change, not when substantive code changes without corresponding doc updates.Proposed solution
A new script
scripts/check_changelog_updated.pymodeled on the existingcheck_*.pypattern. Runs at pre-push stage (not per-commit — would be too noisy during feature-branch iteration) and in CI.The mechanism
origin/mainvera/,spec/,SKILL.mdtests/,scripts/,.github/,docs/,examples/,CHANGELOG.md,HISTORY.md,README.md,ROADMAP.md,KNOWN_ISSUES.md,FAQ.md,CONTRIBUTING.md,pyproject.toml,uv.lock+-bullet under[Unreleased]or a new+## [X.Y.Z]headingEscape hatches
Some PRs legitimately don't need a CHANGELOG entry (typo fix in a code comment, pure refactor with no user-visible impact even at the bug-fix level). Two ways to skip:
Skip-changelog: <reason>in any commit message on the branchskip-changelog(CI-only)Integration
.pre-commit-config.yaml— pre-push stage:CI
lintjob — one line so it fires on PRs regardless of local hooks:Error message format
Why pre-push, not pre-commit
Feature branches typically have 5–20 commits. The final one (or a dedicated release-prep commit) updates CHANGELOG. Running on every commit would block every intermediate commit with "no CHANGELOG entry" — pure friction. Pre-push gives the whole branch's worth of commits before checking.
Scope
~100 lines of Python + ~4 lines of YAML + a unit test file. Estimated 30 minutes — 1 hour including tests.
Out of scope (natural follow-ons)
pyproject.toml's version changed, require a matching HISTORY.md rowspec/changed, requirescripts/check_spec_examples.pyallowlist is regenerated (addresses a known issue tracked in memory about silent duplicate keys)These would extend the pattern but can wait until the basic CHANGELOG check proves useful.