---
name: changelog-quality-pass
description: |-
  Use when writing or auditing changelogs and release notes for user-facing, semver-aware clarity.
  Triggers:
practices:
- pragmatic-programmer
skill_api_version: 1
user-invocable: false
hexagonal_role: supporting
context:
  window: inherit
  intent:
    mode: task
metadata:
  tier: library
  stability: stable
  category: engineering-quality
  maturity: source
  clean_room: true
  owner: agentops
  dependencies: []
  primary_artifacts:
  - CHANGELOG.md
  - release notes
output_contract: 'workmanship_report with sections: verdict, findings, changes, verification, residual_risk.'
---

# Changelog Workmanship

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`
- **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.
- **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

1. Locate or create `CHANGELOG.md` at the repo root.
2. Reconstruct history from tags + the commits/PRs/issues between them:
   ```bash
   git tag --sort=-creatordate
   git log --oneline v1.1.0..v1.2.0
   ```
3. For each release, group changes under the standard headings (below).
4. Translate each entry from commit language into user-facing language.
5. Confirm version order, dates, and semver alignment.
6. Run `bash scripts/validate.sh` and report the verdict.

## The Keep-a-Changelog Structure

A conforming file has this shape:

```markdown
# 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)

1. Confirm the structural skeleton: title, `[Unreleased]`, newest-first
   releases, dated headings, comparison links.
2. Read each entry as a user: does it describe an outcome, or an implementation
   detail / bare commit subject? Rewrite the latter.
3. Check grouping: is every entry under a correct standard heading? Are empty
   groups omitted?
4. Cross-check each version heading against the real tag and the bump type the
   entries imply.
5. Verify comparison/diff links resolve and the `[Unreleased]` link points at
   the latest tag.
6. 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.Z` is 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:
  ```markdown
  ## [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 deps` is 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.
