Skill: changelog generator
A skill that generates a clean changelog from git history. Reads commits between two refs, groups by type, and outputs formatted markdown.
Writing changelogs by hand is tedious. You scroll through git log, copy commit messages, organize them into categories, clean up the wording, and format the whole thing. It takes 20 minutes and you do it the same way every time. That’s exactly the kind of task a skill should handle.
This skill reads your git history between two refs, parses conventional commit messages, groups them by type, and outputs a clean markdown changelog. It handles messy commits gracefully (not every project follows conventions perfectly) and produces output you can paste directly into a CHANGELOG.md or a GitHub release.
The skill file
Create this file at .claude/skills/changelog.md in your project:
# Skill: changelog generator
## Description
Generate a formatted markdown changelog from git commit history.
Reads commits between two git refs, parses them, groups by type,
and outputs a changelog ready for release notes or CHANGELOG.md.
Invoke this skill with: /changelog
Or by asking: "Generate a changelog" or "What changed since the
last release?"
Accepts optional arguments:
- A ref range like `v1.2.0..v1.3.0` or `v1.2.0..HEAD`
- If no range is given, defaults to the most recent tag through HEAD
## Steps
### 1. Determine the ref range
If the user provided two refs (like `v1.2.0..v1.3.0`), use those.
If the user provided one ref (like `v1.2.0`), use that ref through
HEAD: `v1.2.0..HEAD`.
If the user provided no refs, find the most recent tag:
```bash
git describe --tags --abbrev=0
```
Use that tag through HEAD. If no tags exist at all, use the first
commit through HEAD:
```bash
git rev-list --max-parents=0 HEAD
```
Store the resolved "from" and "to" refs for later use.
### 2. Get the version info
Determine the version label for the changelog header:
- If the "to" ref is a tag, use that tag name as the version
(e.g., `v1.3.0`).
- If the "to" ref is HEAD, label it "Unreleased".
Get the date of the "to" ref:
```bash
git log -1 --format=%ai <to-ref>
```
Format that date as YYYY-MM-DD.
### 3. Read the commit log
Run git log between the two refs with a parseable format:
```bash
git log <from>..<to> --format="%H%x00%s%x00%b%x00%an" --no-merges
```
This gives you, for each commit:
- Full hash
- Subject line
- Body
- Author name
The `--no-merges` flag excludes merge commits, which are usually
noise in a changelog.
### 4. Parse conventional commits
For each commit, try to parse the subject line as a conventional
commit:
Format: `type(scope): description`
The type is one of:
- `feat` — new feature
- `fix` — bug fix
- `docs` — documentation changes
- `style` — formatting, semicolons, etc. (no code change)
- `refactor` — code restructuring without feature or fix
- `perf` — performance improvement
- `test` — adding or updating tests
- `build` — build system or dependency changes
- `ci` — CI configuration changes
- `chore` — maintenance tasks
The scope is optional (it appears in parentheses).
The description is everything after the colon and space.
If a commit's subject line doesn't match the conventional format,
classify it as "other" and use the full subject as the description.
Also check if the commit body contains `BREAKING CHANGE:` or if
the type has a `!` suffix (like `feat!:`). Flag these as breaking
changes.
### 5. Group the commits
Create these groups, in this order:
1. **Breaking changes** — any commit flagged as a breaking change,
regardless of its type
2. **Features** — commits with type `feat`
3. **Bug fixes** — commits with type `fix`
4. **Performance** — commits with type `perf`
5. **Documentation** — commits with type `docs`
6. **Other changes** — everything else (refactor, style, test,
build, ci, chore, and any unparsed commits)
Within each group, sort commits alphabetically by scope (scopeless
commits come last), then by description.
### 6. Format the changelog
Output the changelog in this exact format:
```markdown
## [version] - YYYY-MM-DD
### Breaking changes
- **scope:** description ([hash-short](commit-url))
- description without scope ([hash-short](commit-url))
### Features
- **scope:** description ([hash-short](commit-url))
### Bug fixes
- **scope:** description ([hash-short](commit-url))
### Performance
- **scope:** description ([hash-short](commit-url))
### Documentation
- **scope:** description ([hash-short](commit-url))
### Other changes
- **scope:** description ([hash-short](commit-url))
```
For `hash-short`, use the first 7 characters of the commit hash.
For `commit-url`, try to detect the remote URL:
```bash
git remote get-url origin
```
If it's a GitHub, GitLab, Forgejo, or Gitea URL, construct a link
to the commit: `https://host/org/repo/commit/<full-hash>`. If you
can't determine the URL format, just show the short hash without
a link.
If a section has no commits, omit that section entirely (don't
show empty headings).
If a commit has a scope, bold it and prefix the description with
it. If no scope, just show the description.
### 7. Show a summary
After the formatted changelog, add a brief summary line:
```
---
[X] commits: [Y] features, [Z] fixes, [W] other
```
### 8. Offer next steps
After showing the changelog, ask the developer:
- "Want me to prepend this to CHANGELOG.md?"
- "Want me to create a GitHub/Forgejo release with this?"
- "Want me to adjust the grouping or formatting?"
Do not take any of these actions automatically. Wait for the
developer to choose.
## Rules
- Never modify any files unless the developer explicitly asks.
This skill is read-only by default.
- Always exclude merge commits from the changelog.
- If a commit appears in multiple categories (e.g., it's both a
feat and a breaking change), show it in Breaking Changes only.
Do not duplicate entries.
- Preserve the original commit message wording. Do not rewrite
or "improve" the descriptions.
- If the ref range produces zero commits, say so clearly instead
of producing an empty changelog.
How to install it
- Create the directory:
mkdir -p .claude/skills/ - Save the file above as
.claude/skills/changelog.md - Invoke it by typing
/changelogor/changelog v1.2.0..v1.3.0in Claude Code.
Commit the skills directory to your repo so the whole team can use it. For a personal skill that follows you across projects, put it in ~/.claude/skills/ instead.
Example usage
You’re about to cut a release. Your last tag is v2.1.0 and you’ve merged 8 commits since then. You type /changelog and get:
## [Unreleased] - 2026-03-27
### Breaking changes
- **api:** remove deprecated /v1/users endpoint ([a1b2c3d](https://github.com/yourorg/yourproject/commit/a1b2c3d4e5f6789))
### Features
- **auth:** add SAML SSO support for enterprise customers ([b2c3d4e](https://github.com/yourorg/yourproject/commit/b2c3d4e5f6789ab))
- **dashboard:** add export to CSV button on reports page ([c3d4e5f](https://github.com/yourorg/yourproject/commit/c3d4e5f6789abcd))
### Bug fixes
- **auth:** fix session expiry not redirecting to login ([d4e5f6a](https://github.com/yourorg/yourproject/commit/d4e5f6a789abcde))
- **dashboard:** fix chart rendering on Safari 17 ([e5f6a7b](https://github.com/yourorg/yourproject/commit/e5f6a7b89abcdef))
### Other changes
- **deps:** bump express from 4.18.2 to 4.19.0 ([f6a7b8c](https://github.com/yourorg/yourproject/commit/f6a7b8c9abcdef0))
- **ci:** add Node 22 to test matrix ([a7b8c9d](https://github.com/yourorg/yourproject/commit/a7b8c9dabcdef01))
- update README with new setup instructions ([b8c9d0e](https://github.com/yourorg/yourproject/commit/b8c9d0eabcdef12))
---
8 commits: 2 features, 2 fixes, 4 other
Then the agent asks whether you want to prepend it to CHANGELOG.md or create a release. You say “prepend it” and it handles the file edit.
How it works
The skill is a sequence of git commands followed by text processing. Nothing complicated. But the details matter.
Tag detection as the default range. Most teams tag releases. By defaulting to “last tag through HEAD,” the skill does the right thing without any arguments for the most common case: “what changed since the last release?”
Conventional commit parsing with a fallback. Not every commit in your history will follow the conventional format. Maybe a contributor forgot the prefix. Maybe you squash-merged a PR and the message didn’t conform. The skill handles these by bucketing them as “other” instead of failing or skipping them. This is a design decision from How to design AI agent skills: always handle messy input gracefully.
No merge commits. Merge commits add noise. If you merge a feature branch, the individual commits already tell the story. The merge commit (“Merge branch ‘feature-x’ into main”) adds nothing. The --no-merges flag strips these out.
Remote URL detection for links. The skill tries to construct clickable links to each commit. This works for GitHub, GitLab, Forgejo, and Gitea because they all use the same /commit/<hash> URL pattern. If the remote is something else (or doesn’t exist), it degrades to just showing the short hash. Graceful degradation, not failure.
Read-only by default. The skill generates the changelog and shows it to you, then asks what you want to do. It doesn’t write to CHANGELOG.md automatically. This is a good default for any skill that could modify files. Let the human decide. The skill definition in Writing effective skill instructions covers why explicit output descriptions prevent unexpected side effects.
Customizing it
Change the commit types. If your team uses different conventional commit types (or has custom ones like infra or data), update the list in step 4 and add corresponding groups in step 5.
Change the output format. Some teams prefer Keep a Changelog format. Others want a flat bullet list without categories. Rewrite step 6 to match whatever format your project already uses. The agent will follow whatever template you provide.
Include merge commits. If your team uses squash merges and each merge commit is meaningful, remove the --no-merges flag from step 3. For squash-merge workflows, the merge commit IS the changelog entry.
Add PR links. If you want each entry to link to the pull request instead of (or in addition to) the commit, add a step that uses gh pr list --search <hash> to find associated PRs. This works well for squash-merge workflows where the PR number is in the commit message.
Filter out noise. If you don’t want chore, ci, or build commits in your changelog at all, update step 5 to exclude them rather than grouping them under “Other changes.” Some changelogs are only for user-facing changes. That’s a valid choice.
Auto-detect version bumps. You could extend step 2 to suggest a version number based on the changes: if there are breaking changes, suggest a major bump; if there are features, suggest a minor bump; if only fixes, suggest a patch. This turns the skill into a lightweight release assistant.
This skill pairs well with the commit message writer skill, which ensures your commits follow conventional format so the changelog parser can group them correctly.
Related articles
Skill: commit message writer
A skill that reads your staged changes and writes a conventional commit message following your project's conventions.
Skill: documentation generator
A skill that reads your code and generates documentation. README sections, function docs, API references, all matching your project's existing style.
Skill: PR review
A ready-to-use skill that reviews pull requests for bugs, style issues, and missing tests. Copy the markdown file into your project and invoke it.