Changelog Generator
Tier: POWERFUL Category: Engineering / Release Management Maintainer: Claude Skills Team
Overview
Generate consistent, auditable changelogs and release notes from Conventional Commits. Parses commit messages, detects semantic version bumps (major/minor/patch), renders Keep a Changelog sections, supports monorepo scoped changelogs, integrates with CI for automated release notes, and enforces commit format with linting. Separates commit parsing, bump logic, and rendering so teams can automate releases without losing editorial control.
Keywords
changelog, release notes, conventional commits, semantic versioning, semver, Keep a Changelog, commit linting, release automation, monorepo changelog
Core Capabilities
1. Commit Parsing
- Parse Conventional Commit messages into structured data
- Extract type, scope, description, body, and footer
- Detect breaking changes from
!suffix andBREAKING CHANGE:footer - Handle multi-line commit bodies and co-author trailers
2. Semantic Version Detection
- Map commit types to version bump levels
- Breaking changes trigger major bumps
feattriggers minor bumps- All other types trigger patch bumps
- Support for pre-release versions (alpha, beta, rc)
3. Changelog Rendering
- Keep a Changelog format with semantic sections
- GitHub release notes format
- Plain markdown for documentation
- JSON output for automation pipelines
- Grouped by type with user-readable descriptions
4. Quality Enforcement
- Commit message linter for CI and pre-commit hooks
- Strict mode that blocks non-conforming commits
- Scope validation against allowed values
- Breaking change documentation requirements
When to Use
- Before publishing a release tag
- During CI to generate release notes automatically
- In PR checks to enforce commit message standards
- In monorepos where package changelogs need scoped filtering
- When converting raw git history into user-facing notes
- As a pre-release checklist step
Conventional Commit Format
<type>(<scope>)<!>: <description>
[optional body]
[optional footer(s)]
Type to Section Mapping
| Commit Type | Changelog Section | SemVer Bump | User-Facing? | |-------------|------------------|-------------|-------------| | feat | Added | minor | Yes | | fix | Fixed | patch | Yes | | perf | Performance | patch | Yes | | security | Security | patch | Yes | | deprecated | Deprecated | minor | Yes | | remove | Removed | major | Yes | | refactor | Changed | patch | Sometimes | | docs | — | patch | No | | test | — | — | No | | build | — | — | No | | ci | — | — | No | | chore | — | — | No |
Breaking Change Rules
Breaking changes always trigger a major version bump regardless of type:
feat(api)!: remove deprecated v1 endpoints
BREAKING CHANGE: The /api/v1/* endpoints have been removed.
Migrate to /api/v2/* before upgrading. See migration guide at docs/v2-migration.md.
Changelog Rendering
Keep a Changelog Format
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.0] - 2026-03-09
### Added
- User can now export projects as CSV ([#234](https://github.com/org/repo/pull/234))
- Dark mode support for dashboard ([#228](https://github.com/org/repo/pull/228))
### Fixed
- Pagination returning duplicate items on page boundaries ([#231](https://github.com/org/repo/pull/231))
- Login form not showing validation errors on mobile ([#229](https://github.com/org/repo/pull/229))
### Performance
- Reduced dashboard load time by 40% with query optimization ([#232](https://github.com/org/repo/pull/232))
### Security
- Updated jsonwebtoken to 9.0.2 to fix CVE-2024-XXXX ([#233](https://github.com/org/repo/pull/233))
## [1.3.2] - 2026-02-28
### Fixed
- API rate limiter not resetting after window expiry ([#227](https://github.com/org/repo/pull/227))
[1.4.0]: https://github.com/org/repo/compare/v1.3.2...v1.4.0
[1.3.2]: https://github.com/org/repo/compare/v1.3.1...v1.3.2
GitHub Release Notes Format
## What's New
- **CSV Export**: Users can now export project data as CSV files (#234)
- **Dark Mode**: Dashboard fully supports dark mode (#228)
## Bug Fixes
- Fixed pagination returning duplicate items on page boundaries (#231)
- Fixed login form validation on mobile devices (#229)
## Performance
- Dashboard load time reduced by 40% through query optimization (#232)
## Security
- Updated jsonwebtoken to patch CVE-2024-XXXX (#233)
**Full Changelog**: https://github.com/org/repo/compare/v1.3.2...v1.4.0
Generation Workflow
Step 1: Collect Commits
# Get commits between two tags
git log v1.3.2..HEAD --pretty=format:'%H %s' --no-merges
# Get commits with full body (for breaking change detection)
git log v1.3.2..HEAD --pretty=format:'%H%n%s%n%b%n---COMMIT_END---' --no-merges
Step 2: Parse and Classify
import re
from dataclasses import dataclass
from typing import Optional
@dataclass
class ParsedCommit:
hash: str
type: str
scope: Optional[str]
description: str
body: Optional[str]
breaking: bool
breaking_description: Optional[str]
COMMIT_PATTERN = re.compile(
r'^(?P<type>feat|fix|perf|refactor|docs|test|build|ci|chore|security|deprecated|remove)'
r'(?:\((?P<scope>[^)]+)\))?'
r'(?P<breaking>!)?'
r':\s*(?P<description>.+)$'
)
def parse_commit(hash: str, message: str) -> Optional[ParsedCommit]:
lines = message.strip().split('\n')
subject = lines[0]
body = '\n'.join(lines[1:]).strip() if len(lines) > 1 else None
match = COMMIT_PATTERN.match(subject)
if not match:
return None # Non-conventional commit
breaking = bool(match.group('breaking'))
breaking_desc = None
if body and 'BREAKING CHANGE:' in body:
breaking = True
bc_match = re.search(r'BREAKING CHANGE:\s*(.+)', body, re.DOTALL)
if bc_match:
breaking_desc = bc_match.group(1).strip()
return ParsedCommit(
hash=hash,
type=match.group('type'),
scope=match.group('scope'),
description=match.group('description'),
body=body,
breaking=breaking,
breaking_description=breaking_desc,
)
Step 3: Determine Version Bump
def determine_bump(commits: list[ParsedCommit]) -> str:
"""Determine semver bump from parsed commits."""
if any(c.breaking for c in commits):
return 'major'
if any(c.type == 'feat' for c in commits):
return 'minor'
if any(c.type in ('fix', 'perf', 'security', 'refactor') for c in commits):
return 'patch'
return 'none'
def bump_version(current: str, bump: str) -> str:
"""Apply bump to a semver string."""
major, minor, patch = map(int, current.lstrip('v').split('.'))
if bump == 'major':
return f"{major + 1}.0.0"
elif bump == 'minor':
return f"{major}.{minor + 1}.0"
elif bump == 'patch':
return f"{major}.{minor}.{patch + 1}"
return current
Step 4: Render Changelog
SECTION_MAP = {
'feat': 'Added',
'fix': 'Fixed',
'perf': 'Performance',
'security': 'Security',
'deprecated': 'Deprecated',
'remove': 'Removed',
'refactor': 'Changed',
}
def render_changelog(version: str, date: str, commits: list[ParsedCommit], repo_url: str) -> str:
sections: dict[str, list[str]] = {}
# Breaking changes get their own section
breaking = [c for c in commits if c.breaking]
if breaking:
sections['BREAKING CHANGES'] = []
for c in breaking:
desc = c.breaking_description or c.description
scope = f"**{c.scope}**: " if c.scope else ""
sections['BREAKING CHANGES'].append(f"- {scope}{desc}")