Change Log Generator
Automatically generate comprehensive change logs from git history and code changes.
Core Capabilities
This skill helps create change logs by:
- Analyzing git commits - Parse commit messages for changes
- Processing conventional commits - Extract structured information from standardized commits
- Examining diffs/patches - Understand code changes from git diff output
- Categorizing changes - Automatically group by type (features, fixes, docs, etc.)
- Formatting output - Generate Markdown change logs following best practices
Change Log Generation Workflow
Step 1: Gather Change Information
Collect commits and changes for the release period.
Determine Release Range:
# Changes since last tag
git log $(git describe --tags --abbrev=0)..HEAD --oneline
# Changes between two tags
git log v1.2.0..v1.3.0 --oneline
# Changes in last N commits
git log -n 50 --oneline
# Changes since specific date
git log --since="2024-01-01" --oneline
# Changes in current branch vs main
git log main..HEAD --oneline
Get Detailed Commit Information:
# Full commit messages
git log v1.2.0..HEAD --format="%H|%an|%ad|%s|%b" --date=short
# With file changes
git log v1.2.0..HEAD --name-status
# With diff stats
git log v1.2.0..HEAD --stat
Get Pull Request Information:
# Using GitHub CLI
gh pr list --state merged --base main --limit 50
# Get PR details
gh pr view 123 --json title,body,labels,mergedAt
Step 2: Parse Commit Messages
Extract meaningful information from commits.
Conventional Commit Format:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Common Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, missing semicolons, etc.)refactor: Code refactoringperf: Performance improvementstest: Adding or updating testsbuild: Build system or external dependency changesci: CI configuration changeschore: Other changes that don't modify src or test files
Examples:
feat(auth): add OAuth2 authentication
Implements OAuth2 authentication flow using Google provider.
Users can now sign in with their Google accounts.
Closes #45
fix(api): prevent race condition in user creation
Race condition occurred when multiple requests tried to create
the same user simultaneously. Added database constraint and
retry logic.
Fixes #123
docs: update installation instructions
Added troubleshooting section for Windows users.
BREAKING CHANGE: remove deprecated API endpoints
The /api/v1/users endpoint has been removed. Use /api/v2/users instead.
Parse Commit Messages:
import re
def parse_conventional_commit(message):
"""Parse conventional commit message."""
# Pattern: type(scope): description
pattern = r'^(\w+)(\(([^)]+)\))?:\s*(.+)$'
match = re.match(pattern, message)
if match:
return {
'type': match.group(1),
'scope': match.group(3),
'description': match.group(4),
'breaking': 'BREAKING CHANGE' in message
}
else:
return {
'type': 'other',
'scope': None,
'description': message,
'breaking': 'BREAKING CHANGE' in message
}
# Example
commit = "feat(auth): add OAuth2 authentication"
parsed = parse_conventional_commit(commit)
# Returns: {'type': 'feat', 'scope': 'auth', 'description': 'add OAuth2 authentication', 'breaking': False}
See references/conventional_commits.md for detailed parsing rules.
Step 3: Categorize Changes
Group commits by change type.
Category Mapping:
CATEGORIES = {
'feat': {
'title': 'Features',
'emoji': '✨',
'description': 'New features and capabilities'
},
'fix': {
'title': 'Bug Fixes',
'emoji': '🐛',
'description': 'Bug fixes and corrections'
},
'perf': {
'title': 'Performance',
'emoji': '⚡',
'description': 'Performance improvements'
},
'refactor': {
'title': 'Refactoring',
'emoji': '♻️',
'description': 'Code refactoring'
},
'docs': {
'title': 'Documentation',
'emoji': '📚',
'description': 'Documentation updates'
},
'test': {
'title': 'Testing',
'emoji': '✅',
'description': 'Test additions and updates'
},
'build': {
'title': 'Build System',
'emoji': '🏗️',
'description': 'Build and dependency changes'
},
'ci': {
'title': 'CI/CD',
'emoji': '👷',
'description': 'CI/CD changes'
},
'style': {
'title': 'Code Style',
'emoji': '💄',
'description': 'Code style and formatting'
},
'chore': {
'title': 'Chores',
'emoji': '🔧',
'description': 'Maintenance and chores'
}
}
def categorize_commits(commits):
"""Categorize commits by type."""
categorized = {}
for commit in commits:
parsed = parse_conventional_commit(commit['message'])
commit_type = parsed['type']
if commit_type not in categorized:
categorized[commit_type] = []
categorized[commit_type].append({
'description': parsed['description'],
'scope': parsed['scope'],
'sha': commit['sha'][:7],
'author': commit['author'],
'breaking': parsed['breaking']
})
return categorized
Prioritize Categories:
Order of importance:
- Breaking Changes (always first)
- Features
- Bug Fixes
- Performance
- Security
- Deprecations
- Other categories
Step 4: Format Change Log
Generate Markdown output following conventions.
Basic Template:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added
- New feature A
- New feature B
### Changed
- Updated component X
- Improved performance of Y
### Deprecated
- Old API endpoint will be removed in v2.0
### Removed
- Deprecated feature Z
### Fixed
- Bug in authentication flow
- Memory leak in processor
### Security
- Fixed XSS vulnerability in input validation
Generate Changelog Entry:
def generate_changelog_entry(version, date, categorized_commits):
"""Generate changelog entry for a version."""
lines = []
# Header
lines.append(f"## [{version}] - {date}\n")
# Breaking Changes (if any)
breaking_changes = []
for category, commits in categorized_commits.items():
for commit in commits:
if commit.get('breaking'):
breaking_changes.append(commit)
if breaking_changes:
lines.append("### ⚠️ BREAKING CHANGES\n")
for change in breaking_changes:
scope_str = f"**{change['scope']}**: " if change['scope'] else ""
lines.append(f"- {scope_str}{change['description']} ({change['sha']})")
lines.append("")
# Regular categories
category_order = ['feat', 'fix', 'perf', 'refactor', 'docs', 'test', 'build', 'ci', 'style', 'chore']
for cat_type in category_order:
if cat_type in categorized_commits:
category_info = CATEGORIES.get(cat_type, {'title': cat_type.title()})
lines.append(f"### {category_info['title']}\n")
for commit in categorized_commits[cat_type]:
if commit.get('breaking'):
continue # Already listed in breaking changes
scope_str = f"**{commit['scope']}**: " if commit['scope'] else ""
lines.append(f"- {scope_str}{commit['description']} ([`{commit['sha']}`](link/to/commit/{commit['sha']}))")