Skip to content

Add check_changelog_updated.py: fail PR if substantive changes lack CHANGELOG entry #478

@aallan

Description

@aallan

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

  1. Diff the current branch against origin/main
  2. 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)
  3. If any substantive file changed, verify CHANGELOG.md diff contains either a new +- bullet under [Unreleased] or a new +## [X.Y.Z] heading
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ciCI/CD and GitHub Actions

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions