commit 2458b07edffe15a7e757bbdc389afeeb02d6e3f8 Author: Zhongwei Li Date: Sat Nov 29 18:12:42 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..abb8dc1 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "changelog-generator", + "description": "ClaudeForge Enterprise Changelog Generator delivering comprehensive automated changelog generation, conventional commit parsing excellence, and semantic versioning automation that transforms release documentation from manual effort into intelligent, professional release management", + "version": "1.0.0", + "author": { + "name": "ClaudeForge Community", + "url": "https://github.com/claudeforge/marketplace" + }, + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5a2936 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# changelog-generator + +ClaudeForge Enterprise Changelog Generator delivering comprehensive automated changelog generation, conventional commit parsing excellence, and semantic versioning automation that transforms release documentation from manual effort into intelligent, professional release management diff --git a/commands/changelog-generator.md b/commands/changelog-generator.md new file mode 100644 index 0000000..194380e --- /dev/null +++ b/commands/changelog-generator.md @@ -0,0 +1,659 @@ +--- +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. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..746ac50 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,45 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:claudeforge/marketplace:plugins/commands/changelog-generator", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "276141fb4f250b8fa421da0b997b33ef0fb8d572", + "treeHash": "ef802eaf3f7290995393c7ec26eb301e316862f894191361cb85a23000409e5f", + "generatedAt": "2025-11-28T10:15:26.200489Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "changelog-generator", + "description": "ClaudeForge Enterprise Changelog Generator delivering comprehensive automated changelog generation, conventional commit parsing excellence, and semantic versioning automation that transforms release documentation from manual effort into intelligent, professional release management", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "fd357e54e681370ffdebc824400b347e57d5e22d5f2fe2fc8763232eeb64b695" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "0d3efd3d90b5a15ed14b0c2be37a3b839a44e10ac167d8a225e1297434ccb580" + }, + { + "path": "commands/changelog-generator.md", + "sha256": "ae67e8d332a33e32aad0a97fa0abdb566ccdc1740577dd32ab79965e5ebec2f2" + } + ], + "dirSha256": "ef802eaf3f7290995393c7ec26eb301e316862f894191361cb85a23000409e5f" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file