changelog
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](https://github.com/org/repo/pull/123))
## [2.0.0] - 2026-01-19
_Optional notice about this release._
### Changed
- **Breaking:** Major refactor ([#100](https://github.com/org/repo/pull/100))
### Added
- Add feature X ([#95](https://github.com/org/repo/pull/95))
### Fixed
- Fix bug Y ([#90](https://github.com/org/repo/pull/90))
[Unreleased]: https://github.com/org/repo/compare/v2.0.0...HEAD
[2.0.0]: https://github.com/org/repo/releases/tag/v2.0.0
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](https://github.com/org/repo/pull/45))
- **Breaking:** Remove deprecated API ([#50](https://github.com/org/repo/pull/50))
- Fix memory leak in worker pool ([`abc1234`](https://github.com/org/repo/commit/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 filespnpm-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](https://github.com/org/repo/pull/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](https://github.com/org/repo/pull/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