--- allowed-tools: Bash, Read, Write, Edit, Grep, Glob description: ClaudeForge automated changelog generation with conventional commits, semantic versioning, and release notes. --- # ClaudeForge Changelog Generator ClaudeForge intelligent changelog and release notes generation system that automatically creates comprehensive, well-formatted changelogs from conventional commits, manages semantic versioning, and produces professional release documentation. ## Purpose Transform changelog management from manual documentation to intelligent automation that ensures consistency, completeness, and professional presentation of project changes while maintaining semantic versioning standards. ## Features - **Conventional Commits**: Parse and categorize commits following conventional commit format - **Semantic Versioning**: Automatic version bumping based on commit types - **Changelog Generation**: Generate CHANGELOG.md with proper formatting - **Release Notes**: Create detailed release notes for GitHub/GitLab releases - **Breaking Changes**: Highlight breaking changes prominently - **Multi-Format**: Support Markdown, HTML, JSON output formats - **Customization**: Configurable sections, templates, and formatting - **Integration**: GitHub/GitLab releases, npm version, and CI/CD integration ## Usage ```bash /changelog-generator [action] [options] ``` Target: $ARGUMENTS (if specified, otherwise analyze current scope) ### Changelog Generation **Generate Changelog:** ```bash /changelog-generator generate --from=v1.0.0 --to=HEAD --output=CHANGELOG.md ``` Generates comprehensive changelog with: - Conventional commit parsing (feat, fix, docs, style, refactor, test, chore) - Automatic categorization by commit type - Breaking changes section (BREAKING CHANGE footer) - Scope-based organization (api, ui, auth) - Author attribution and PR references - Commit links to repository - Comparison links between versions - Release date timestamps **Update Changelog:** ```bash /changelog-generator update --version=2.1.0 --prepend=true ``` Updates existing CHANGELOG.md with: - New version section at the top - Preservation of existing content - Proper version header formatting - Date of release - Version comparison links - Unreleased section management - Consistent formatting throughout - Validation of markdown structure **Release Notes:** ```bash /changelog-generator release-notes --version=2.1.0 --highlights=true ``` Creates release notes including: - Version number and date - Summary of key changes - Feature highlights with descriptions - Bug fixes and improvements - Breaking changes with migration guides - Deprecation notices - Security fixes (CVE references) - Contributors acknowledgment - Installation/upgrade instructions ### Semantic Versioning **Version Bump:** ```bash /changelog-generator bump --type=minor --dry-run=false ``` Determines version bump based on: - Major bump: BREAKING CHANGE commits or breaking: type - Minor bump: feat commits (new features) - Patch bump: fix commits (bug fixes) - Pre-release versions (alpha, beta, rc) - Version tagging in git - package.json version update - Changelog version synchronization - Automatic git tag creation **Next Version:** ```bash /changelog-generator next-version --commits=origin/main..HEAD ``` Calculates next version by analyzing: - Commit history since last release - Conventional commit types - Breaking change indicators - Pre-release identifiers - Version constraints - Manual override options - Branching strategy (main, develop, feature) - Release candidate numbering **Version Validation:** ```bash /changelog-generator validate-version --version=2.1.0-beta.3 ``` Validates semantic version format: - Major.Minor.Patch format compliance - Pre-release identifier validity - Build metadata validation - Version comparison and ordering - Backward compatibility checks - Version range satisfaction - npm/yarn version compatibility - Consistency across package files ### Commit Analysis **Parse Commits:** ```bash /changelog-generator parse --range=v1.0.0..HEAD --format=json ``` Parses conventional commits with: - Type extraction (feat, fix, docs, etc.) - Scope identification (api, ui, core) - Subject/description parsing - Body content extraction - Footer parsing (BREAKING CHANGE, Closes, Refs) - Multi-line commit support - Co-authored-by extraction - Sign-off and trailer parsing **Validate Commits:** ```bash /changelog-generator validate-commits --strict=true --from=HEAD~10 ``` Validates commit message format: - Conventional commits specification compliance - Type allowlist enforcement - Scope validation against config - Subject line length limits (72 chars) - Body wrapping at 100 characters - Footer format validation - Breaking change syntax - Issue reference format **Commit Statistics:** ```bash /changelog-generator stats --from=v1.0.0 --group-by=author ``` Generates commit statistics including: - Commit count by type - Top contributors - Commits per day/week/month - Breaking changes count - Average commit frequency - File change statistics - Lines added/removed - Most active areas/scopes ### Customization **Configure Templates:** ```bash /changelog-generator config --template=./changelog-template.hbs ``` Customizes changelog with: - Handlebars template support - Custom section headers - Commit grouping strategies - Formatting preferences (bullets, numbers) - Link format customization - Date format localization - Emoji support for commit types - Custom footer content **Section Configuration:** ```bash /changelog-generator configure-sections --preset=angular ``` Defines changelog sections: - Feature section (feat commits) - Bug Fixes section (fix commits) - Performance Improvements (perf) - Breaking Changes (BREAKING CHANGE) - Deprecations (deprecated) - Documentation (docs) - Chores and maintenance - Custom sections with patterns **Exclusion Rules:** ```bash /changelog-generator exclude --pattern="^chore(release|deps)" --scopes=internal ``` Excludes commits from changelog: - Commit type exclusions (chore, style, test) - Scope-based filtering - Pattern matching for subjects - Bot commits (dependabot, renovate) - Merge commits - Revert commits - WIP commits - Internal changes ## Code Generation Examples ### Conventional Commit Parser (TypeScript) ```typescript interface ConventionalCommit { type: string; scope?: string; breaking: boolean; subject: string; body?: string; footer?: string; references: string[]; mentions: string[]; notes: Note[]; hash: string; author: string; date: Date; } interface Note { title: string; text: string; } class ChangelogGenerator { private commitPattern = /^(\w+)(\(([^\)]+)\))?(!)?:\s(.+)$/; parseCommit(commit: GitCommit): ConventionalCommit | null { const lines = commit.message.split('\n'); const headerMatch = lines[0].match(this.commitPattern); if (!headerMatch) { return null; } const [, type, , scope, breaking, subject] = headerMatch; const body = this.extractBody(lines); const footer = this.extractFooter(lines); const notes = this.extractNotes(footer); const references = this.extractReferences(footer); const mentions = this.extractMentions(commit.message); return { type, scope, breaking: breaking === '!' || notes.some(n => n.title === 'BREAKING CHANGE'), subject, body, footer, references, mentions, notes, hash: commit.hash, author: commit.author, date: commit.date }; } private extractBody(lines: string[]): string | undefined { const bodyStart = 1; const bodyEnd = lines.findIndex((line, i) => i > 0 && /^[A-Z][a-z-]+:/.test(line)); if (bodyEnd === -1) { return lines.slice(bodyStart).join('\n').trim() || undefined; } return lines.slice(bodyStart, bodyEnd).join('\n').trim() || undefined; } private extractFooter(lines: string[]): string | undefined { const footerStart = lines.findIndex((line, i) => i > 0 && /^[A-Z][a-z-]+:/.test(line)); if (footerStart === -1) { return undefined; } return lines.slice(footerStart).join('\n').trim(); } private extractNotes(footer?: string): Note[] { if (!footer) return []; const notes: Note[] = []; const notePattern = /^([A-Z][A-Z\s-]+):\s(.+)$/gm; let match; while ((match = notePattern.exec(footer)) !== null) { notes.push({ title: match[1].trim(), text: match[2].trim() }); } return notes; } private extractReferences(footer?: string): string[] { if (!footer) return []; const references: string[] = []; const patterns = [ /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s#(\d+)/gi, /(?:ref|refs|references)\s#(\d+)/gi, /#(\d+)/g ]; patterns.forEach(pattern => { let match; while ((match = pattern.exec(footer)) !== null) { references.push(match[1]); } }); return [...new Set(references)]; } private extractMentions(message: string): string[] { const mentions = message.match(/@([a-zA-Z0-9_-]+)/g); return mentions ? mentions.map(m => m.substring(1)) : []; } generateChangelog(commits: ConventionalCommit[], version: string): string { const grouped = this.groupCommits(commits); const date = new Date().toISOString().split('T')[0]; let changelog = `## [${version}](${this.compareUrl(version)}) (${date})\n\n`; // Breaking changes first if (grouped.breaking.length > 0) { changelog += '### ⚠ BREAKING CHANGES\n\n'; grouped.breaking.forEach(commit => { changelog += `* ${commit.subject}\n`; const breakingNote = commit.notes.find(n => n.title === 'BREAKING CHANGE'); if (breakingNote) { changelog += `\n ${breakingNote.text}\n\n`; } }); changelog += '\n'; } // Features if (grouped.feat.length > 0) { changelog += '### Features\n\n'; grouped.feat.forEach(commit => { const scope = commit.scope ? `**${commit.scope}:** ` : ''; changelog += `* ${scope}${commit.subject} ([${commit.hash.substring(0, 7)}](${this.commitUrl(commit.hash)}))\n`; }); changelog += '\n'; } // Bug fixes if (grouped.fix.length > 0) { changelog += '### Bug Fixes\n\n'; grouped.fix.forEach(commit => { const scope = commit.scope ? `**${commit.scope}:** ` : ''; const refs = commit.references.length > 0 ? `, closes ${commit.references.map(r => `#${r}`).join(', ')}` : ''; changelog += `* ${scope}${commit.subject} ([${commit.hash.substring(0, 7)}](${this.commitUrl(commit.hash)}))${refs}\n`; }); changelog += '\n'; } // Performance improvements if (grouped.perf.length > 0) { changelog += '### Performance Improvements\n\n'; grouped.perf.forEach(commit => { const scope = commit.scope ? `**${commit.scope}:** ` : ''; changelog += `* ${scope}${commit.subject} ([${commit.hash.substring(0, 7)}](${this.commitUrl(commit.hash)}))\n`; }); changelog += '\n'; } return changelog; } private groupCommits(commits: ConventionalCommit[]): Record { const grouped: Record = { breaking: [], feat: [], fix: [], perf: [], docs: [], style: [], refactor: [], test: [], chore: [] }; commits.forEach(commit => { if (commit.breaking) { grouped.breaking.push(commit); } if (grouped[commit.type]) { grouped[commit.type].push(commit); } }); return grouped; } determineVersionBump(commits: ConventionalCommit[]): 'major' | 'minor' | 'patch' { const hasBreaking = commits.some(c => c.breaking); if (hasBreaking) return 'major'; const hasFeature = commits.some(c => c.type === 'feat'); if (hasFeature) return 'minor'; return 'patch'; } bumpVersion(currentVersion: string, bump: 'major' | 'minor' | 'patch'): string { const [major, minor, patch] = currentVersion.split('.').map(Number); switch (bump) { case 'major': return `${major + 1}.0.0`; case 'minor': return `${major}.${minor + 1}.0`; case 'patch': return `${major}.${minor}.${patch + 1}`; } } private commitUrl(hash: string): string { return `https://github.com/owner/repo/commit/${hash}`; } private compareUrl(version: string): string { return `https://github.com/owner/repo/compare/v${this.previousVersion}...v${version}`; } } ``` ### GitHub Release Generator (Python) ```python import re from datetime import datetime from typing import List, Dict, Optional from dataclasses import dataclass @dataclass class Commit: hash: str type: str scope: Optional[str] subject: str body: Optional[str] breaking: bool references: List[str] author: str date: datetime class ReleaseNotesGenerator: COMMIT_PATTERN = re.compile( r'^(?P\w+)' r'(?:\((?P[^\)]+)\))?' r'(?P!)?' r':\s(?P.+)$' ) def __init__(self, repo_owner: str, repo_name: str): self.repo_owner = repo_owner self.repo_name = repo_name def generate_release_notes( self, commits: List[Commit], version: str, previous_version: str ) -> str: """Generate comprehensive GitHub release notes""" notes = f"# Release {version}\n\n" # Summary section summary = self._generate_summary(commits) notes += f"{summary}\n\n" # Highlights highlights = self._extract_highlights(commits) if highlights: notes += "## Highlights\n\n" for highlight in highlights: notes += f"- {highlight}\n" notes += "\n" # Breaking changes breaking = [c for c in commits if c.breaking] if breaking: notes += "## ⚠️ Breaking Changes\n\n" for commit in breaking: notes += f"- **{commit.subject}**\n" if commit.body: notes += f" \n {commit.body}\n" notes += "\n" # Features features = [c for c in commits if c.type == 'feat'] if features: notes += "## New Features\n\n" for commit in self._group_by_scope(features): scope_prefix = f"**{commit.scope}**: " if commit.scope else "" notes += f"- {scope_prefix}{commit.subject} " notes += f"([{commit.hash[:7]}]({self._commit_url(commit.hash)}))\n" notes += "\n" # Bug fixes fixes = [c for c in commits if c.type == 'fix'] if fixes: notes += "## Bug Fixes\n\n" for commit in self._group_by_scope(fixes): scope_prefix = f"**{commit.scope}**: " if commit.scope else "" refs = f" (fixes #{', #'.join(commit.references)})" if commit.references else "" notes += f"- {scope_prefix}{commit.subject}{refs} " notes += f"([{commit.hash[:7]}]({self._commit_url(commit.hash)}))\n" notes += "\n" # Performance improvements perf = [c for c in commits if c.type == 'perf'] if perf: notes += "## Performance Improvements\n\n" for commit in perf: scope_prefix = f"**{commit.scope}**: " if commit.scope else "" notes += f"- {scope_prefix}{commit.subject} " notes += f"([{commit.hash[:7]}]({self._commit_url(commit.hash)}))\n" notes += "\n" # Contributors contributors = self._get_contributors(commits) notes += "## Contributors\n\n" notes += f"This release was made possible by {len(contributors)} contributor(s):\n\n" for author in contributors: notes += f"- @{author}\n" notes += "\n" # Installation instructions notes += self._generate_installation_instructions(version) # Comparison link compare_url = self._compare_url(previous_version, version) notes += f"\n**Full Changelog**: {compare_url}\n" return notes def _generate_summary(self, commits: List[Commit]) -> str: """Generate release summary""" feat_count = len([c for c in commits if c.type == 'feat']) fix_count = len([c for c in commits if c.type == 'fix']) breaking_count = len([c for c in commits if c.breaking]) summary = f"This release includes " parts = [] if feat_count > 0: parts.append(f"{feat_count} new feature{'s' if feat_count > 1 else ''}") if fix_count > 0: parts.append(f"{fix_count} bug fix{'es' if fix_count > 1 else ''}") if breaking_count > 0: parts.append(f"{breaking_count} breaking change{'s' if breaking_count > 1 else ''}") if not parts: return "This release includes minor updates and improvements." return summary + ", ".join(parts) + "." def _extract_highlights(self, commits: List[Commit]) -> List[str]: """Extract highlights from commit messages""" highlights = [] for commit in commits: # Look for highlights in commit body if commit.body and 'highlight:' in commit.body.lower(): match = re.search(r'highlight:\s*(.+)', commit.body, re.IGNORECASE) if match: highlights.append(match.group(1)) return highlights def _group_by_scope(self, commits: List[Commit]) -> List[Commit]: """Sort commits by scope""" return sorted(commits, key=lambda c: (c.scope or '', c.subject)) def _get_contributors(self, commits: List[Commit]) -> List[str]: """Get unique list of contributors""" authors = set(c.author for c in commits) return sorted(authors) def _commit_url(self, hash: str) -> str: """Generate commit URL""" return f"https://github.com/{self.repo_owner}/{self.repo_name}/commit/{hash}" def _compare_url(self, from_version: str, to_version: str) -> str: """Generate comparison URL""" return f"https://github.com/{self.repo_owner}/{self.repo_name}/compare/v{from_version}...v{to_version}" def _generate_installation_instructions(self, version: str) -> str: """Generate installation instructions""" return f"""## Installation ### npm ```bash npm install {self.repo_name}@{version} ``` ### yarn ```bash yarn add {self.repo_name}@{version} ``` ### pnpm ```bash pnpm add {self.repo_name}@{version} ``` """ ``` ## Best Practices ### Commit Message Format - **Conventional Commits**: Follow conventional commits specification - **Clear Subjects**: Write clear, concise commit subjects (50 chars) - **Detailed Bodies**: Provide context in commit body when needed - **Breaking Changes**: Always document breaking changes in footer - **Issue References**: Link commits to issues with Closes/Fixes ### Changelog Management - **Keep Updated**: Update CHANGELOG.md with every release - **Unreleased Section**: Maintain unreleased section for upcoming changes - **Consistent Format**: Use consistent formatting throughout - **Version Links**: Include comparison links between versions - **Date Stamps**: Always include release dates ### Release Process - **Semantic Versioning**: Strictly follow semantic versioning rules - **Release Notes**: Create detailed release notes for each version - **Tag Creation**: Create git tags for all releases - **Automation**: Automate changelog generation in CI/CD - **Review Process**: Review generated changelogs before publishing ### Documentation - **Migration Guides**: Provide migration guides for breaking changes - **Deprecation Notices**: Document deprecated features clearly - **Examples**: Include usage examples for new features - **Credits**: Acknowledge contributors and community - **Links**: Provide links to documentation and resources --- **ClaudeForge Changelog Generator** - Enterprise-grade changelog automation with conventional commits, semantic versioning, and professional release documentation for modern software projects.