Changelog
Enforce and validate changelogs following the Common Changelog specification with an Unreleased section extension for in-progress work.
When to Use This Skill
-
Setting up changelog workflow in a new repository
-
Retrofitting changelog from git history
-
Validating changelog format in pre-commit/pre-push hooks
-
Ensuring PR coverage in changelog during CI
-
Understanding Common Changelog format requirements
Skill Contents
📚 references/ - common changelog spec, unreleased extension, migration guide, hallmark analysis
🔧 scripts/ - validate, check updated, coverage, retrofit, parse, migrate, import releases
📦 assets/ - changelog template, exclusion patterns
Sections
Quick Start | Format Specification | Validation Rules | Scripts | Migration | Integration | Exclusions | Troubleshooting
Quick Start
Creating a new CHANGELOG.md
Copy the template and customize:
cp .skills/changelog/assets/changelog-template.md CHANGELOG.md
Validating an existing changelog
pnpm run skills:changelog
or
node .scripts/skills-cli.ts changelog validate
Checking PR coverage
pnpm run skills:changelog:coverage
or
node .scripts/skills-cli.ts changelog coverage
Retrofitting from git history
pnpm run skills:changelog:retrofit
or
node .scripts/skills-cli.ts changelog retrofit
Format Specification
File Structure
Changelog
Optional description or note about the changelog format.
Unreleased
Added
- Add new feature (#123)
2.0.0 - 2026-01-19
Optional notice about this release.
Changed
- Breaking: Major refactor (#100)
Added
- Add feature X (#95)
Fixed
- Fix bug Y (#90)
Category Order
Categories MUST appear in this order (only include those with changes):
-
Changed - modifications to existing functionality
-
Added - new features
-
Removed - removed features
-
Fixed - bug fixes
Change Format
Each change must:
-
Start with imperative verb (Add, Fix, Remove, Update, etc.)
-
Include at least one reference (PR number or commit hash)
-
Use Breaking: prefix for breaking changes
-
Add user authentication (#45)
-
Breaking: Remove deprecated API (#50)
-
Fix memory leak in worker pool (
abc1234)
Validation Rules
Format Validation
Rule Description
File exists CHANGELOG.md must exist
Header Must start with # Changelog
Version format Semver without 'v' prefix (e.g., 2.0.0 )
Date format ISO 8601 (YYYY-MM-DD)
Category names Only: Changed, Added, Removed, Fixed
Category order Changed > Added > Removed > Fixed
References Each change must have at least one
Pre-commit Check
When committing, the changelog check:
-
Gets list of staged files
-
Filters out excluded paths (tests, config, generated files)
-
If significant files are staged, requires CHANGELOG.md to also be staged
Coverage Validation
Ensures every merged PR is documented:
-
Extracts PR numbers from git history
-
Extracts PR numbers from CHANGELOG.md
-
Reports any PRs missing from changelog
Scripts
parse.js
Parse CHANGELOG.md into structured data:
import { parseChangelog, extractReferences, parseVersion } from './scripts/parse.ts';
const content = fs.readFileSync('CHANGELOG.md', 'utf-8'); const parsed = parseChangelog(content);
// parsed = { // title: 'Changelog', // releases: [ // { version: 'Unreleased', date: null, groups: [...] }, // { version: '2.0.0', date: '2026-01-19', notice: '...', groups: [...] } // ] // }
validate.js
Validate changelog format:
import { validateChangelog } from './scripts/validate.ts';
const result = await validateChangelog('/path/to/repo'); // result = { passed: true/false, errors: [], warnings: [] }
check-updated.js
Pre-commit hook integration:
import { checkChangelogUpdated } from './scripts/check-updated.ts';
const result = await checkChangelogUpdated('/path/to/repo'); // result = { passed: true/false, message: '...', errors: [] }
retrofit.js
Generate changelog from git history:
import { retrofitChangelog } from './scripts/retrofit.ts';
const result = await retrofitChangelog({ rootDir: '/path/to/repo', version: '2.0.0', date: '2026-01-19', repoUrl: 'https://github.com/org/repo' }); // result = { markdown: '...', prCount: 106, categorized: {...} }
coverage.js
Validate PR coverage:
import { validateCoverage } from './scripts/coverage.ts';
const result = await validateCoverage('/path/to/repo'); // result = { passed: true/false, missingPRs: [], extraPRs: [] }
migrate.js
Convert existing changelogs to Common Changelog format:
node .skills/changelog/scripts/migrate.ts --input CHANGELOG.md --dry-run node .skills/changelog/scripts/migrate.ts --input CHANGELOG.md --output CHANGELOG.new.md
import-releases.js
Import GitHub releases as changelog entries:
node .skills/changelog/scripts/import-releases.ts --repo bitsoex/my-repo --dry-run node .skills/changelog/scripts/import-releases.ts --repo bitsoex/my-repo --output CHANGELOG.md
Migration
For repositories with existing changelogs or release history, see the Migration Guide.
Migration Scenarios
Current State Approach
Keep a Changelog format Use migrate.js to convert
GitHub releases Use import-releases.js to import
Git tags only Use retrofit.js to generate
No versioning Copy template, start fresh
Converting from Keep a Changelog
Preview changes
node .skills/changelog/scripts/migrate.ts --input CHANGELOG.md --dry-run
Convert in place
node .skills/changelog/scripts/migrate.ts --input CHANGELOG.md
Importing GitHub Releases
Preview import
node .skills/changelog/scripts/import-releases.ts --repo bitsoex/my-repo --dry-run
Generate changelog from releases
node .skills/changelog/scripts/import-releases.ts --repo bitsoex/my-repo --output CHANGELOG.md
Integration
Pre-commit Hook
Add to .scripts/lib/validation.ts :
{ name: 'Changelog updated', command: null, customCheck: 'checkChangelogUpdated', scope: 'pre-commit', blocking: true, required: true }
Pre-push Hook
Add to .scripts/lib/validation.ts :
{ name: 'Changelog format', command: ['node', '.scripts/skills-cli.ts', 'changelog', 'validate'], scope: 'pre-push', blocking: true, required: true }
CI Workflow
Add to .github/workflows/ci.yaml :
-
name: Validate Changelog Format if: github.event_name == 'pull_request' run: pnpm run skills:changelog
-
name: Validate PR Coverage if: github.event_name == 'pull_request' run: pnpm run skills:changelog:coverage
Package.json Scripts
{ "scripts": { "skills:changelog": "node .scripts/skills-cli.ts changelog validate", "skills:changelog:check": "node .scripts/skills-cli.ts changelog check-updated", "skills:changelog:coverage": "node .scripts/skills-cli.ts changelog coverage", "skills:changelog:retrofit": "node .scripts/skills-cli.ts changelog retrofit" } }
Exclusions
Files matching these patterns do NOT require changelog updates.
See assets/exclusion-patterns.json for the full list.
Infrastructure
-
.github/
-
CI/CD workflows
-
.gitignore , .nvmrc
-
config files
-
pnpm-lock.yaml
-
lock files
Tests
- tests/
- test files only
Generated Content
-
.tmp/ , coverage/ , output/
-
build outputs
-
.claude/skills/
-
distributed content
Configuration
-
vitest.config.js , eslint.config.js
-
tooling config
-
.coderabbit.yaml , .doclinterrc.yml
-
linter config
Related Skills
Skill Purpose
quality-gateway
Orchestrates quality checks including changelog
git-hooks
Hook infrastructure for pre-commit/pre-push
pr-lifecycle
PR creation and management
Troubleshooting
"CHANGELOG.md must be updated"
Your commit includes significant files but CHANGELOG.md is not staged.
Fix: Add an entry to the Unreleased section:
Unreleased
Added
- Describe your change (#YOUR_PR)
"Category X is out of order"
Categories must appear in order: Changed > Added > Removed > Fixed.
Fix: Reorder your categories to match the expected order.
"Change has no reference"
Every change must reference a PR or commit.
Fix: Add a reference in parentheses:
- Your change description (#123)
"Invalid version format"
Version must be valid semver without 'v' prefix.
Good: ## 2.0.0 - 2026-01-19
Bad: ## [v2.0.0] - 2026-01-19