Enforce CHANGELOG entries
Require release notes to contain real Added, Changed, and Fixed entries before a release lands.
The jump log can be brief, but it cannot say "TBD" where the coordinates should be.
Put the contract beside the file it guards: CHANGELOG.mdv.md next to CHANGELOG.md. With that convention, local checks and CI can use the zero-config scan or validate the changelog explicitly.
The contract
<!-- mdv: block required heading.level=1 heading.pattern=/^Changelog$/ -->
# Changelog
<!-- mdv: endblock -->
<!-- mdv: block required heading.level=2 heading.pattern=/^[0-9]+\.[0-9]+\.[0-9]+\s-\s[0-9]{4}-[0-9]{2}-[0-9]{2}$/ -->
## 1.2.3 - 2026-06-28
<!-- mdv: endblock -->
<!-- mdv: block required minWords=3 noPlaceholder -->
### Added
- New user-visible capability.
<!-- mdv: endblock -->
<!-- mdv: block required minWords=3 noPlaceholder -->
### Changed
- Changed user-visible behavior.
<!-- mdv: endblock -->
<!-- mdv: block required minWords=3 noPlaceholder -->
### Fixed
- Fixed user-visible bug.
<!-- mdv: endblock -->This requires a # Changelog title, one release heading shaped like 1.2.3 - 2026-06-28, then real Added, Changed, and Fixed sections. It is the heading.pattern regex that's enforced, not the literal sample date, so any conforming release heading passes. noPlaceholder rejects entries like TBD, TODO, and untouched template text.
The GitHub Action
on:
pull_request:
paths:
- CHANGELOG.md
- CHANGELOG.mdv.md
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install -D mediva
- run: npx mediva check CHANGELOG.md --schema CHANGELOG.mdv.mdYou can also run the sibling scan from the repo root:
npx mediva checkWith no file arguments, mediva validates every X.md that has a sibling X.mdv.md schema.
Variations
- If some releases legitimately have no fixed bugs, keep the required
### Fixedheading and write an honest line likeNo bug fixes this release.rather than deleting it (the body must still clearminWords=3). - For a single release note file, keep this same contract beside
release.mdasrelease.mdv.md. - This contract is written for a single-release file. If you keep a full history in one file, the repeated
### Added/### Changed/### Fixedheadings emit duplicate-heading warnings, and validation attaches to the last occurrence of each — so in a newest-at-top changelog the rules land on the oldest release, not the newest. To gate the release you actually care about, generate a one-release fragment in CI (e.g. the section above the second## x.y.zheading) and check that fragment against this contract.