# Changelog Gen

> Generate changelogs from git history: conventional commits, git-cliff, keep-a-changelog format, release notes generation. Trigger: when generating a changelog, conventional commits, git-cliff, release notes, keep-a-changelog, CHANGELOG.md generation, semantic release

- Skill: `dennisonbertram/changelog-gen` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dennisonbertram/changelog-gen`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dennisonbertram/changelog-gen/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: dennisonbertram (https://skillmd.com/u/dennisonbertram)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dennisonbertram/changelog-gen

---

# Changelog Generation

You are now operating in changelog generation mode.

## Conventional Commits Format

Changelogs are generated from commit messages that follow the Conventional Commits specification:

```
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

### Types and Their Changelog Sections

| Type | Changelog Section | Example |
|------|-------------------|---------|
| `feat` | Added | `feat(auth): add OAuth2 login support` |
| `fix` | Fixed | `fix(api): correct pagination offset calculation` |
| `perf` | Changed | `perf(db): add index on user_id column` |
| `refactor` | Changed | `refactor(core): extract runner into separate package` |
| `docs` | - (omitted) | `docs: update README installation steps` |
| `test` | - (omitted) | `test(auth): add JWT expiry tests` |
| `build` | - (omitted) | `build: upgrade Go to 1.22` |
| `ci` | - (omitted) | `ci: add race detector to test workflow` |
| `BREAKING CHANGE` | Breaking Changes | Footer: `BREAKING CHANGE: removed /v1/users endpoint` |

### Examples of Good Conventional Commits

```bash
# Feature
git commit -m "feat(billing): add Stripe subscription management"

# Bug fix with scope
git commit -m "fix(auth): prevent session fixation on password change"

# Breaking change in footer
git commit -m "feat(api)!: remove deprecated /v1/search endpoint

BREAKING CHANGE: /v1/search has been removed. Use /v2/search with the new filter syntax.
Migration guide: https://docs.example.com/migration/v2-search"

# Fix with issue reference
git commit -m "fix(scheduler): handle concurrent job cancellation

Closes #142"
```

## git-cliff — Automated Changelog Generation

### Installation

```bash
# macOS
brew install git-cliff

# Cargo (Rust)
cargo install git-cliff

# Download binary
curl -s https://api.github.com/repos/orhun/git-cliff/releases/latest \
  | jq -r '.assets[] | select(.name | contains("x86_64-unknown-linux-musl.tar.gz")) | .browser_download_url' \
  | xargs curl -L -o git-cliff.tar.gz
tar -xzf git-cliff.tar.gz && mv git-cliff /usr/local/bin/

# Verify
git-cliff --version
```

### Configuration (cliff.toml)

```toml
# cliff.toml — place in project root

[changelog]
header = """
# Changelog

All notable changes to this project are documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
"""
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [Unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}:** {% endif %}{{ commit.message | upper_first }}\
{% if commit.breaking %} [**BREAKING**]{% endif %}\
{% endfor %}
{% endfor %}\n
"""
footer = """
<!-- generated by git-cliff -->
"""
trim = true

[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_preprocessors = []
commit_parsers = [
  { message = "^feat", group = "Added" },
  { message = "^fix", group = "Fixed" },
  { message = "^refactor|^perf", group = "Changed" },
  { message = "^docs", skip = true },
  { message = "^test", skip = true },
  { message = "^build|^ci|^chore", skip = true },
  { body = ".*security", group = "Security" },
]
protect_breaking_commits = false
filter_commits = false
tag_pattern = "v[0-9].*"
skip_tags = "v0.1.0-beta.1"
ignore_tags = ""
sort_commits = "newest"
```

### git-cliff Usage

```bash
# Generate full changelog from all tags (outputs to stdout)
git-cliff

# Generate changelog and write to CHANGELOG.md
git-cliff -o CHANGELOG.md

# Generate changelog since last tag (unreleased section)
git-cliff --unreleased

# Generate changelog for a specific version
git-cliff --tag v1.2.3 -o CHANGELOG.md

# Generate changelog between two tags
git-cliff v1.1.0..v1.2.0

# Preview what would be in the next release
git-cliff --unreleased --tag v1.2.0

# Strip the header (for appending to existing file)
git-cliff --strip header

# Output as JSON
git-cliff --output-format json

# Initialize a default cliff.toml
git-cliff --init
```

## Keep a Changelog Format (Manual)

```markdown
# Changelog

All notable changes to this project are documented here.
Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
This project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added
- New dashboard analytics page with real-time metrics

### Fixed
- Session expiry now correctly respects custom TTL settings

---

## [1.2.0] - 2024-03-15

### Added
- OAuth2 login with GitHub and Google
- Export to CSV from all list views
- Webhook support for issue status changes

### Changed
- Improved query performance for large datasets (10x faster)
- Updated error messages to be more actionable

### Fixed
- Fixed pagination on the search results page
- Fixed incorrect timestamps in export files (was UTC, now user timezone)

### Security
- Upgraded dependencies to address CVE-2024-1234 in lodash

---

## [1.1.0] - 2024-02-01

### Added
- Rate limiting on all public API endpoints

### Deprecated
- `/v1/search` endpoint is deprecated; use `/v2/search` instead

### Removed
- Removed legacy XML response format support

---

## [1.0.0] - 2024-01-01

Initial stable release.

[Unreleased]: https://github.com/your-org/your-repo/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/your-org/your-repo/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/your-org/your-repo/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/your-org/your-repo/releases/tag/v1.0.0
```

## Script-Based Changelog Generation (No External Tool)

```bash
#!/bin/bash
# generate-changelog.sh — generate changelog from conventional commits using only git

set -euo pipefail

REPO_URL="${REPO_URL:-https://github.com/your-org/your-repo}"
OUTPUT="${1:-CHANGELOG.md}"
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
DATE=$(date +%Y-%m-%d)

generate_section() {
    local type="$1"
    local heading="$2"
    local commits

    if [ -n "$LAST_TAG" ]; then
        commits=$(git log "${LAST_TAG}..HEAD" --pretty=format:"%s" 2>/dev/null | grep "^${type}" || true)
    else
        commits=$(git log --pretty=format:"%s" | grep "^${type}" || true)
    fi

    if [ -n "$commits" ]; then
        echo "### ${heading}"
        echo ""
        echo "$commits" | sed "s/^${type}[^:]*: /- /" | sed 's/^- (/- **/' | sed 's/): /**: /'
        echo ""
    fi
}

{
    echo "## [Unreleased] - ${DATE}"
    echo ""
    generate_section "feat" "Added"
    generate_section "fix" "Fixed"
    generate_section "perf\|refactor" "Changed"
    generate_section "security" "Security"

    if [ -n "$LAST_TAG" ]; then
        echo "[Unreleased]: ${REPO_URL}/compare/${LAST_TAG}...HEAD"
    fi
} > "/tmp/new-changes.md"

if [ -f "$OUTPUT" ]; then
    # Insert new section after the header (first ## line)
    awk '/^## \[/{if (!done) {system("cat /tmp/new-changes.md"); done=1} print; next} 1' "$OUTPUT" > "${OUTPUT}.tmp"
    mv "${OUTPUT}.tmp" "$OUTPUT"
else
    cat /tmp/new-changes.md > "$OUTPUT"
fi

echo "Changelog updated: $OUTPUT"
```

## Release Notes from Git Log

```bash
# Generate formatted release notes between two tags
from_tag="v1.1.0"
to_tag="v1.2.0"

git log "${from_tag}..${to_tag}" \
  --pretty=format:"%s" \
  --no-merges | \
  grep -E "^(feat|fix|perf|refactor|security)" | \
  sort | \
  awk '
    /^feat/    { print "- " $0 > "/tmp/added.txt" }
    /^fix/     { print "- " $0 > "/tmp/fixed.txt" }
    /^perf|refactor/ { print "- " $0 > "/tmp/changed.txt" }
    /^security/ { print "- " $0 > "/tmp/security.txt" }
  '

echo "## Release Notes for ${to_tag}"
echo ""
for section in added fixed changed security; do
    if [ -s "/tmp/${section}.txt" ]; then
        echo "### ${section^}"
        cat "/tmp/${section}.txt"
        echo ""
    fi
done
```

## CI/CD Integration

```bash
# In GitHub Actions: generate and commit changelog on tag push
# .github/workflows/release.yml
# on:
#   push:
#     tags: ['v*']

# Step: generate changelog
git-cliff --tag "${GITHUB_REF_NAME}" -o CHANGELOG.md

# Step: create GitHub release with notes
gh release create "${GITHUB_REF_NAME}" \
  --title "${GITHUB_REF_NAME}" \
  --notes-from-tag \
  --notes "$(git-cliff --unreleased --strip all)"
```

## Best Practices

- Follow Conventional Commits in every commit message — automation depends on consistent types.
- Use scopes (`feat(auth):`, `fix(api):`) for multi-module projects to make changelogs navigable.
- Never manually edit the auto-generated sections of CHANGELOG.md — only edit the `[Unreleased]` section.
- Run `git-cliff --unreleased` before creating a release tag to preview the generated notes.
- Add `CHANGELOG.md` to version control and update it as part of the release process.
- Include the compare URL at the bottom of each version section for easy diffing.
- Mark breaking changes with `!` in the commit type (e.g., `feat!:`) and include a `BREAKING CHANGE:` footer.
- Generate release notes automatically in CI to reduce manual overhead and ensure consistency.

