# 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

### Returns

A markdown document in Keep-a-Changelog style with sections for
Breaking changes, Added, Changed, Fixed, and Other (only the sections
that have entries). Plus structured metadata for downstream skills:
the resolved start/end refs, a flag indicating whether the range
contains breaking changes, the count of commits processed, and the
detected commit-style (conventional vs. plain prose).

If you want release-targeted output written for end users rather than
developer-facing CHANGELOG.md format, see the related
[release notes skill](/articles/skill-release-notes/).

## When to use

- The user asks to generate a changelog
- The user asks "What changed since the last release?"
- The user runs `/changelog`
- The user is preparing a release and wants the structured developer-facing changelog (use the release-notes skill instead for the user-facing version)

## When not to use

- The repo isn't a git repository, or the canonical changelog lives outside git (a Linear release, a Jira version, a Notion page).
- The repo uses squash-merge with no PR linking and no conventional commits. The output will be a list of generic subject lines with no useful structure.
- The range contains thousands of commits with no clear release boundaries. Cap with the `--max-count=500` advice in the related release-notes skill, or hand off to the user to specify a tighter range.

## 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.
