# Changelog Generator

> Automatically generate changelogs from git commits following conventional commits, semantic versi...

- **Type:** Skill
- **Install:** `agentstack add skill-curiouslearner-devkit-changelog-generator`
- **Verified:** Pending review
- **Seller:** [CuriousLearner](https://agentstack.voostack.com/s/curiouslearner)
- **Installs:** 0
- **Category:** [Search](https://agentstack.voostack.com/c/search)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [CuriousLearner](https://github.com/CuriousLearner)
- **Source:** https://github.com/CuriousLearner/devkit/tree/main/skills/changelog-generator

## Install

```sh
agentstack add skill-curiouslearner-devkit-changelog-generator
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Changelog Generator Skill

Automatically generate changelogs from git commits following conventional commits, semantic versioning, and best practices.

## Instructions

You are a changelog generation expert. When invoked:

1. **Analyze Commit History**:
   - Parse git commit messages
   - Identify conventional commit types
   - Group related changes
   - Determine version bumps (major, minor, patch)

2. **Generate Changelog Entries**:
   - Follow Keep a Changelog format
   - Categorize by change type
   - Include breaking changes prominently
   - Add relevant metadata (dates, versions, authors)

3. **Format Output**:
   - Use markdown formatting
   - Create clear section headers
   - Add links to commits and PRs
   - Include migration guides for breaking changes

4. **Version Management**:
   - Suggest semantic version numbers
   - Identify breaking changes
   - Track deprecations
   - Handle pre-release versions

## Conventional Commit Types

- **feat**: New feature (minor version bump)
- **fix**: Bug fix (patch version bump)
- **docs**: Documentation changes
- **style**: Code style changes (formatting, etc.)
- **refactor**: Code refactoring
- **perf**: Performance improvements
- **test**: Test additions or changes
- **build**: Build system changes
- **ci**: CI/CD changes
- **chore**: Maintenance tasks
- **revert**: Revert previous changes

**Breaking Change**: Any commit with `BREAKING CHANGE:` in body or `!` after type (major version bump)

## Usage Examples

```
@changelog-generator
@changelog-generator --since v1.2.0
@changelog-generator --unreleased
@changelog-generator --version 2.0.0
@changelog-generator --format keep-a-changelog
@changelog-generator --include-authors
```

## Changelog Formats

### Keep a Changelog Format

```markdown
# 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.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- New feature X for improved user experience
- Support for configuration option Y

### Changed
- Updated dependency Z to version 2.0
- Improved performance of data processing

### Deprecated
- Function `oldMethod()` - use `newMethod()` instead

### Removed
- Removed deprecated API endpoint `/api/v1/old`

### Fixed
- Fixed memory leak in cache implementation
- Corrected timezone handling in date formatter

### Security
- Fixed XSS vulnerability in user input handling
- Updated crypto library to address CVE-2024-1234

## [1.5.0] - 2024-01-15

### Added
- User authentication with OAuth2
- Export functionality for reports
- Dark mode theme support

### Changed
- Redesigned dashboard UI
- Optimized database queries

### Fixed
- Fixed bug in pagination logic
- Resolved CORS issues with API

## [1.4.2] - 2024-01-10

### Fixed
- Critical bug in payment processing
- Memory leak in WebSocket connections

### Security
- Patched authentication bypass vulnerability

## [1.4.1] - 2024-01-05

### Fixed
- Hotfix for broken deployment script
- Fixed typo in error messages

## [1.4.0] - 2024-01-01

### Added
- Real-time notifications
- File upload with drag and drop
- Advanced search filters

### Changed
- Migrated from REST to GraphQL
- Updated UI components library

### Deprecated
- Old REST API endpoints (will be removed in 2.0)

[Unreleased]: https://github.com/user/repo/compare/v1.5.0...HEAD
[1.5.0]: https://github.com/user/repo/compare/v1.4.2...v1.5.0
[1.4.2]: https://github.com/user/repo/compare/v1.4.1...v1.4.2
[1.4.1]: https://github.com/user/repo/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/user/repo/releases/tag/v1.4.0
```

## Automated Changelog Generation

### Using Git Commits

```bash
#!/bin/bash
# generate-changelog.sh - Generate changelog from git commits

VERSION=${1:-"Unreleased"}
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")

echo "# Changelog"
echo ""
echo "## [$VERSION] - $(date +%Y-%m-%d)"
echo ""

# Get commits since last tag
if [ -z "$PREV_TAG" ]; then
  COMMITS=$(git log --pretty=format:"%s|||%h|||%an" --reverse)
else
  COMMITS=$(git log ${PREV_TAG}..HEAD --pretty=format:"%s|||%h|||%an" --reverse)
fi

# Arrays for different categories
declare -a features=()
declare -a fixes=()
declare -a breaking=()
declare -a docs=()
declare -a chores=()
declare -a other=()

# Parse commits
while IFS='|||' read -r message hash author; do
  case "$message" in
    feat:*|feat\(*\):*)
      features+=("- ${message#feat*: } ([${hash}](../../commit/${hash}))")
      ;;
    fix:*|fix\(*\):*)
      fixes+=("- ${message#fix*: } ([${hash}](../../commit/${hash}))")
      ;;
    *BREAKING*|*\!:*)
      breaking+=("- ${message} ([${hash}](../../commit/${hash}))")
      ;;
    docs:*)
      docs+=("- ${message#docs: } ([${hash}](../../commit/${hash}))")
      ;;
    chore:*|build:*|ci:*)
      chores+=("- ${message#*: } ([${hash}](../../commit/${hash}))")
      ;;
    *)
      other+=("- ${message} ([${hash}](../../commit/${hash}))")
      ;;
  esac
done  0) {
    lines.push('### ⚠️ BREAKING CHANGES');
    lines.push('');
    for (const commit of breaking) {
      lines.push(`- **${commit.description}** ([${commit.hash.slice(0, 7)}](../../commit/${commit.hash}))`);
      if (commit.body) {
        const breakingNote = commit.body.match(/BREAKING CHANGE:\s*(.+)/);
        if (breakingNote) {
          lines.push(`  ${breakingNote[1]}`);
        }
      }
    }
    lines.push('');
  }

  // Other sections
  const sectionOrder = [
    'Added',
    'Changed',
    'Deprecated',
    'Removed',
    'Fixed',
    'Security',
    'Performance',
    'Documentation',
  ];

  for (const section of sectionOrder) {
    if (groups[section] && groups[section].length > 0) {
      lines.push(`### ${section}`);
      lines.push('');

      const commits = groups[section];
      const grouped = {};

      // Group by scope if present
      for (const commit of commits) {
        const key = commit.scope || '_default';
        if (!grouped[key]) grouped[key] = [];
        grouped[key].push(commit);
      }

      for (const [scope, scopeCommits] of Object.entries(grouped)) {
        if (scope !== '_default') {
          lines.push(`#### ${scope}`);
          lines.push('');
        }

        for (const commit of scopeCommits) {
          let line = `- ${commit.description}`;

          if (options.includeHash) {
            line += ` ([${commit.hash.slice(0, 7)}](../../commit/${commit.hash}))`;
          }

          if (options.includeAuthor) {
            line += ` - @${commit.author}`;
          }

          lines.push(line);
        }
      }

      lines.push('');
    }
  }

  return lines.join('\n');
}

function getLatestTag() {
  try {
    return execSync('git describe --tags --abbrev=0', { encoding: 'utf-8' }).trim();
  } catch (error) {
    return null;
  }
}

function suggestVersion(breaking, groups) {
  const latestTag = getLatestTag();
  if (!latestTag) return '1.0.0';

  const [major, minor, patch] = latestTag.replace('v', '').split('.').map(Number);

  if (breaking.length > 0) {
    return `${major + 1}.0.0`;
  }

  const hasFeatures = groups['Added'] && groups['Added'].length > 0;
  if (hasFeatures) {
    return `${major}.${minor + 1}.0`;
  }

  return `${major}.${minor}.${patch + 1}`;
}

// Main execution
function main() {
  const args = process.argv.slice(2);
  const options = {
    includeHash: !args.includes('--no-hash'),
    includeAuthor: args.includes('--author'),
    version: args.find(a => a.startsWith('--version='))?.split('=')[1],
    since: args.find(a => a.startsWith('--since='))?.split('=')[1],
  };

  const latestTag = options.since || getLatestTag();
  const commits = getCommitsSinceTag(latestTag);
  const parsed = commits.map(parseCommit);
  const { groups, breaking } = groupCommits(parsed);

  const version = options.version || suggestVersion(breaking, groups);
  const date = new Date().toISOString().split('T')[0];

  const changelog = generateMarkdown(version, date, groups, breaking, options);

  // Read existing changelog or create new
  let existingChangelog = '';
  try {
    existingChangelog = fs.readFileSync('CHANGELOG.md', 'utf-8');
  } catch (error) {
    existingChangelog = '# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n';
  }

  // Insert new version after header
  const lines = existingChangelog.split('\n');
  const headerEnd = lines.findIndex(l => l.startsWith('## '));
  const newLines = [
    ...lines.slice(0, headerEnd === -1 ? lines.length : headerEnd),
    changelog,
    ...lines.slice(headerEnd === -1 ? lines.length : headerEnd),
  ];

  fs.writeFileSync('CHANGELOG.md', newLines.join('\n'));

  console.log(`✓ Generated changelog for version ${version}`);
  console.log(`  - ${commits.length} commits processed`);
  console.log(`  - ${breaking.length} breaking changes`);
  console.log(`  - Suggested version: ${version}`);
}

if (require.main === module) {
  main();
}

module.exports = { parseCommit, groupCommits, generateMarkdown };
```

**Usage:**
```bash
# Make executable
chmod +x generate-changelog.js

# Run
./generate-changelog.js

# With options
./generate-changelog.js --version=2.0.0 --author --since=v1.5.0
```

## Python Version

```python
#!/usr/bin/env python3
# generate_changelog.py

import re
import subprocess
from datetime import datetime
from collections import defaultdict
from typing import List, Dict, Tuple

COMMIT_PATTERN = re.compile(r'^(\w+)(\([\w-]+\))?(!)?:\s(.+)$')

TYPES = {
    'feat': {'section': 'Added', 'bump': 'minor'},
    'fix': {'section': 'Fixed', 'bump': 'patch'},
    'docs': {'section': 'Documentation', 'bump': None},
    'style': {'section': 'Style', 'bump': None},
    'refactor': {'section': 'Changed', 'bump': None},
    'perf': {'section': 'Performance', 'bump': 'patch'},
    'test': {'section': 'Tests', 'bump': None},
    'build': {'section': 'Build System', 'bump': None},
    'ci': {'section': 'CI', 'bump': None},
    'chore': {'section': 'Chores', 'bump': None},
}

def get_commits_since_tag(tag: str = None) -> List[str]:
    cmd = ['git', 'log', '--pretty=format:%H|||%s|||%b|||%an|||%ae|||%ai']
    if tag:
        cmd.insert(2, f'{tag}..HEAD')

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        return [line for line in result.stdout.split('\n') if line]
    except subprocess.CalledProcessError:
        return []

def parse_commit(commit_line: str) -> Dict:
    parts = commit_line.split('|||')
    if len(parts)  Tuple[Dict, List]:
    groups = defaultdict(list)
    breaking = []

    for commit in commits:
        if commit['breaking']:
            breaking.append(commit)

        type_info = TYPES.get(commit['type'], {'section': 'Other Changes'})
        section = type_info['section']
        groups[section].append(commit)

    return dict(groups), breaking

def generate_markdown(version: str, groups: Dict, breaking: List) -> str:
    lines = [f"## [{version}] - {datetime.now().strftime('%Y-%m-%d')}", ""]

    if breaking:
        lines.append("### ⚠️ BREAKING CHANGES")
        lines.append("")
        for commit in breaking:
            lines.append(f"- **{commit['description']}** ([{commit['hash'][:7]}](../../commit/{commit['hash']}))")
        lines.append("")

    section_order = ['Added', 'Changed', 'Fixed', 'Security', 'Performance', 'Documentation']

    for section in section_order:
        if section in groups and groups[section]:
            lines.append(f"### {section}")
            lines.append("")
            for commit in groups[section]:
                lines.append(f"- {commit['description']} ([{commit['hash'][:7]}](../../commit/{commit['hash']}))")
            lines.append("")

    return '\n'.join(lines)

def main():
    commits = get_commits_since_tag()
    parsed = [parse_commit(c) for c in commits if parse_commit(c)]
    groups, breaking = group_commits(parsed)

    version = input("Enter version number (or press Enter for auto): ").strip() or "1.0.0"
    changelog = generate_markdown(version, groups, breaking)

    print(changelog)

    # Optionally write to file
    with open('CHANGELOG.md', 'r+') as f:
        content = f.read()
        f.seek(0, 0)
        f.write(changelog + '\n\n' + content)

if __name__ == '__main__':
    main()
```

## GitHub Release Notes

### Automated Release with Notes

```yaml
# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Generate changelog
        id: changelog
        run: |
          # Get previous tag
          PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")

          # Generate changelog
          if [ -z "$PREV_TAG" ]; then
            CHANGELOG=$(git log --pretty=format:"- %s (%h)" --reverse)
          else
            CHANGELOG=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s (%h)" --reverse)
          fi

          # Set output
          echo "changelog> $GITHUB_OUTPUT
          echo "$CHANGELOG" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Create Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          body: |
            ## Changes in this release
            ${{ steps.changelog.outputs.changelog }}
          draft: false
          prerelease: false
```

## Best Practices

### Commit Messages
- **Use conventional commits**: Enables automated changelog generation
- **Be descriptive**: Clear, concise descriptions of changes
- **Include context**: Why the change was made, not just what
- **Reference issues**: Link to related issue numbers

### Changelog Organization
- **Group by type**: Features, fixes, breaking changes, etc.
- **Sort chronologically**: Newest changes first
- **Include dates**: Each version should have a release date
- **Link to commits**: Provide links for detailed information

### Version Management
- **Follow semver**: Major.Minor.Patch versioning
- **Document breaking changes**: Prominently display breaking changes
- **Provide migration guides**: Help users upgrade
- **Track deprecations**: Warn before removing features

### Maintenance
- **Update regularly**: Generate changelog with each release
- **Review before release**: Manually verify automated output
- **Keep format consistent**: Use same structure throughout
- **Archive old versions**: Keep full history available

## Notes

- Always use conventional commits for automatic changelog generation
- Include breaking changes at the top of changelog
- Provide migration guides for major version bumps
- Link to detailed documentation when needed
- Keep changelog entries user-focused (not technical)
- Review and edit generated changelogs before release
- Use semantic versioning consistently
- Automate changelog generation in CI/CD pipeline
- Consider using tools like standard-version or release-please
- Maintain changelog in git repository alongside code

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [CuriousLearner](https://github.com/CuriousLearner)
- **Source:** [CuriousLearner/devkit](https://github.com/CuriousLearner/devkit)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** yes
- **Shell / process execution:** yes
- **Environment & secrets:** no
- **Dynamic code execution:** yes

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-curiouslearner-devkit-changelog-generator
- Seller: https://agentstack.voostack.com/s/curiouslearner
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
