Use this skill to write or repair a CHANGELOG.md so a user deciding whether to
upgrade can read one section and know what changed, what broke, and what to do.
A changelog is a product surface, not a git log dump.
⚠️ Critical Constraints
- Write user-facing change, not commit subjects. A changelog answers "what
does this mean for me?", not "what did the author touch."
Why: raw commit messages describe implementation; users need outcomes.
- WRONG:
- refactor auth middleware to use jwt-decode v4 (a1b2c3d) - CORRECT:
- Fixed: expired sessions now log out cleanly instead of looping
- WRONG:
- Never invent or backdate entries. Every entry must trace to real shipped change. Why: a fabricated history is worse than none — it destroys trust and misleads upgraders.
- Keep an
## [Unreleased]section at the top, always. Why: the file stays current only if entries land with the change, not at release time when context is gone.- WRONG: writing the whole changelog the day you cut the release from
git log. - CORRECT: each PR adds its line to
[Unreleased]; release moves the block.
- WRONG: writing the whole changelog the day you cut the release from
- Version headings must match real tags and semver. Why: a user pins a version and expects the heading, the tag, and the breaking-change signal to agree; a Patch bump that hides a breaking change is a workmanship failure.
Why This Exists
Most projects either have no changelog or have an autogenerated commit dump that no human reads. Both fail the one job: helping a person decide whether and how to upgrade. Generation defaults to either over-terse ("bug fixes") or commit-shaped noise. This skill is the forcing function for the disciplined middle: structured, grouped, audience-facing, semver-aligned, and maintained continuously.
Quick Start
- Locate or create
CHANGELOG.mdat the repo root. - Reconstruct history from tags + the commits/PRs/issues between them:
git tag --sort=-creatordate git log --oneline v1.1.0..v1.2.0 - For each release, group changes under the standard headings (below).
- Translate each entry from commit language into user-facing language.
- Confirm version order, dates, and semver alignment.
- Run
bash scripts/validate.shand report the verdict.
The Keep-a-Changelog Structure
A conforming file has this shape:
# Changelog
All notable changes to this project are documented here.
The format is based on Keep a Changelog, and this project adheres to
Semantic Versioning.
## [Unreleased]
### Added
- New thing users can now do.
## [1.2.0] - 2026-06-06
### Added
- ...
### Changed
- ...
### Fixed
- ...
## [1.1.0] - 2026-05-01
### Removed
- ...
[Unreleased]: https://example.com/compare/v1.2.0...HEAD
[1.2.0]: https://example.com/compare/v1.1.0...v1.2.0
Rules that hold every time:
- Releases are listed newest first, each with
## [version] - YYYY-MM-DD. [Unreleased]sits at the top with no date.- Dates are ISO 8601 (
YYYY-MM-DD), unambiguous across locales.
The Six Groups
Group every entry under exactly one of these standard headings, omitting any group with no entries:
- Added — new features or capabilities.
- Changed — changes to existing behavior.
- Deprecated — soon-to-be-removed features (warn before removal).
- Removed — features taken out this release.
- Fixed — bug fixes.
- Security — vulnerability fixes (call these out explicitly).
Semver Alignment
The release type the changelog implies must match the version bump:
- Anything under Removed, or a breaking Changed entry → Major bump.
- Anything under Added (backward-compatible) → Minor bump.
- Only Fixed / Security → Patch bump.
Mark breaking changes loudly — a **BREAKING:** prefix on the entry, plus the
Major bump. A changelog that buries a breaking change under a Patch heading is
the most damaging failure mode.
Audit Flow (for an existing changelog)
- Confirm the structural skeleton: title,
[Unreleased], newest-first releases, dated headings, comparison links. - Read each entry as a user: does it describe an outcome, or an implementation detail / bare commit subject? Rewrite the latter.
- Check grouping: is every entry under a correct standard heading? Are empty groups omitted?
- Cross-check each version heading against the real tag and the bump type the entries imply.
- Verify comparison/diff links resolve and the
[Unreleased]link points at the latest tag. - Confirm the maintenance discipline exists (a CONTRIBUTING note or release
step that adds to
[Unreleased]); if not, recommend one.
Keeping It Current (the release flow)
The changelog stays good only if it is edited continuously, not reconstructed:
- Every change-bearing PR adds a line under the right group in
[Unreleased]. - Cutting release
X.Y.Zis a mechanical move: rename[Unreleased]to[X.Y.Z] - <today>, add a fresh empty[Unreleased], update the diff links, tag the commit. - Never let "write the changelog" become a release-day archaeology task.
Output Specification
Return a workmanship report (also written to CHANGELOG.md when repairing):
- Verdict: pass, needs work, or blocked.
- Findings: ordered by severity, each tied to a changelog property.
- Changes: what was edited and why.
- Verification: commands run and their results.
- Residual risk: history gaps, unverifiable entries, missing tags.
Quality Rubric
- Every entry reads as a user-facing outcome, with zero raw commit hashes or subjects left in prose form.
- Every entry sits under a correct standard group; no empty groups are emitted; releases are newest-first with ISO dates.
- The latest release heading matches a real tag, and the implied bump type (Major/Minor/Patch) matches the actual version, with breaking changes marked.
Examples
Rewriting a commit dump into a release entry:
- Input commits:
fix: npe in retry,chore: bump deps,feat: add --json. - Output:
(## [2.3.0] - 2026-06-06 ### Added - `--json` flag for machine-readable output. ### Fixed - No longer crashes when a request is retried after a timeout.chore: bump depsis dropped — not user-facing unless it changes behavior.)
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Changelog is a commit dump | Generated from git log at release time |
Adopt the [Unreleased] continuous-edit flow; rewrite entries as outcomes. |
| Users surprised by a breaking change | Breaking change under a Patch/Minor bump | Re-bump to Major; add **BREAKING:** prefix and a migration note. |
| Dates ambiguous or missing | Locale-specific or absent dates | Use ISO 8601 YYYY-MM-DD on every dated release. |
| Comparison links 404 | Stale diff links after a tag | Update [version]: <compare-url> and the [Unreleased] link to the latest tag. |
| Empty headings everywhere | Boilerplate template not pruned | Omit any group (Added/Changed/...) with no entries this release. |
See Also
- Keep a Changelog conventions (structure + group names).
- Semantic Versioning (Major/Minor/Patch bump rules).
scripts/validate.sh— structural self-check for this skill.