Skills Library Developer skill git releases documentation conventional-commits github changelog

Skill: AI release notes generator from git history and PRs

Copy-paste skill file that generates release notes from git commits and PRs. Handles conventional commits, plain messages, and GitHub CLI.

2 min read

Ready-to-use skill

Download the skill file directly, or scroll down for the inline version with explanation.

On this page

Nobody wants to write release notes. You know what changed because you were there for every PR, but turning that into a readable list for users is tedious. This skill reads your git history between two refs, parses the commit messages, groups them by type, pulls in PR links where available, and outputs formatted markdown you can paste directly into a GitHub release or a CHANGELOG.md.

The most important design choice in this skill: commits are a source, not the truth. Commits are written for developers. Release notes are written for users. This skill rewrites the summary in plain language for the audience that actually reads it.

The skill file

Copy the following into .claude/skills/release-notes.md. This is the complete skill definition.

# Release notes generator

## Purpose

Generate release notes from git history between two refs. Parse conventional commits, group changes by type, include PR links, and output formatted markdown suitable for a changelog or GitHub release.

### Returns

A markdown document with these sections, in this order:

- A one-to-two-sentence plain-language summary at the top
- `## Breaking changes` (omitted if none)
- `## Features`
- `## Bug fixes`
- `## Performance` (omitted if none)
- `## Documentation` (omitted if none)
- `## Other` (chore/build/ci/style/test entries)
- `## Contributors` (de-duplicated authors)

Plus structured metadata for downstream skills: a flag indicating whether the release contains breaking changes, the contributors list as an array, and the resolved start/end refs. A composing skill (such as a release announcer or a status-page updater) can read these fields without parsing the markdown.

## When to use

- The user asks to generate release notes
- The user asks "what changed since the last release?"
- The user asks to write a changelog entry
- The user is preparing a release and needs a summary of changes

## When not to use

- The project is not a git repository, or its canonical changelog lives somewhere other than git (Jira releases, Linear cycles, Notion). Hand off to the user instead.
- 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.
- You don't have read access to the affected repo. Without the git log, the rest of the skill is guesswork.
- The hosting platform is not GitHub and the user wants PR links. The skill assumes GitHub for PR URLs. Adapt the URL format yourself for GitLab, Forgejo, or Azure DevOps before invoking.

## Steps

### 0. Confirm the repo

Before any git command, confirm the working directory is a git repository. Run `git rev-parse --show-toplevel`. If the command fails, ask the user for the path to the repo. If the user mentioned a specific repo by name and your cwd is something else, double-check before continuing.

### 1. Determine the range

Figure out which commits to include:

- If the user specifies two refs (for example, "from v1.2.0 to v1.3.0"), use those: `git log v1.2.0..v1.3.0`.
- If the user specifies one ref ("since v1.2.0"), use that as the start and HEAD as the end: `git log v1.2.0..HEAD`.
- If the user says nothing about refs, find the most recent tag with `git describe --tags --abbrev=0`. Use that as the start and HEAD as the end.
- If there are no tags at all, ask the user: "No tags found. What commit or date should I start from?"

Pull the commit list with a single batched call that captures hash, subject, body, and author in one pass:

```bash
git log <start>..<end> --max-count=500 --format='---COMMIT---%n%H%n%s%n%aN%n%b'
```

The `--max-count=500` cap avoids stuffing thousands of commits into context on long-range queries. If the range exceeds 500 commits, warn the user and ask whether to proceed with the cap or narrow the range.

Tell the user what range you're using: "Generating release notes for v1.2.0..HEAD (14 commits)."

### 2. Parse each commit

Split the batched output by the `---COMMIT---` delimiter and read each block. From each commit, pull out:

- The type (the conventional commit prefix: feat, fix, chore, docs, refactor, perf, test, build, ci, style). If the commit doesn't use conventional commits, infer from the message content. "Fix login crash on empty password" is a `fix`. "Add dark mode support" is a `feat`.
- The scope (the parenthetical scope if present, so `feat(auth)` has scope `auth`).
- The description (the rest of the subject line after the type and scope).
- Whether it's a breaking change. Look for `BREAKING CHANGE:` anywhere in the body or a `!` after the type (`feat!:`).
- The PR number. Look for patterns like `(#123)` in the subject. Many merge workflows append this automatically.

You already have the body from the batched git log call in step 1. Don't run additional `git log <hash>` calls per commit; that's an unbounded fan-out.

### 3. Look up PR details (optional, GitHub-only)

If PR numbers were found and `gh` is available, fetch additional context. Use `--json url` so you don't have to construct URLs by hand:

```bash
gh pr view <number> --json title,author,labels,url
```

Use the PR title if it's more descriptive than the commit message. Record the author login for the contributors list. Use the `url` field directly when generating the markdown link in step 6 (do not construct `https://github.com/org/repo/pull/N` yourself: the URL constructor breaks on GitLab/Forgejo/Azure DevOps and the `gh pr view` URL is correct for whatever GitHub host the repo lives on, including Enterprise).

If `gh` is not available or the command fails, skip this step. Release notes still work without PR metadata.

### 4. Group by category

Sort the parsed commits into these groups, in this order:

1. Breaking changes: any commit marked breaking, regardless of 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: everything else (chore, refactor, build, ci, style, test)

Within each group, sort commits alphabetically by scope (if scopes exist), then by description. Omit any group that has zero commits.

### 5. Build the contributors list

Collect unique authors. The batched git log already captured `%aN` per commit, so de-duplicate from that list:

```bash
git log <start>..<end> --format="%aN" | sort -u
```

If PR author logins were fetched in step 3, prefer those (they're usually GitHub usernames and can be linked). Otherwise, use the git author names.

### 6. Generate the release notes

Output the release notes in this format:

```markdown
# [version or range]

[One to two sentence summary of the release. Mention the most
significant change first. If there are breaking changes, call
that out here.]

## Breaking changes

- **scope:** description of the breaking change and what users
  need to do to migrate ([#PR](url))

## Features

- **scope:** description ([#PR](url))

## Bug fixes

- **scope:** description ([#PR](url))

## Contributors

@username, @username, @username
```

Formatting rules:

- If a commit has a scope, bold it at the start: `**auth:** add token refresh`. (This bold-and-colon format inside the output template is intentional, since it's the format the user actually wants in their CHANGELOG.)
- If a commit has no scope, just start with the description.
- If a PR number exists, link it at the end using the URL from `gh pr view --json url`.
- If no PR number exists, omit the link. Do not invent one.
- The summary at the top is for end users. "You can now export reports as PDF" is better than "feat: add PDF export to report controller."
- Capitalize the first word of each description.
- Do not end descriptions with periods.

### 7. Present and ask (human actions)

Show the full release notes and offer next steps. Frame them so the user understands these are actions they need to approve, not actions you've already taken:

```
Here are the release notes for v1.2.0..v1.3.0.

[release notes]

Want me to (each requires your approval):
1. Copy this to your clipboard
2. Write it to CHANGELOG.md (prepended, not overwritten)
3. Create a GitHub release with this content
4. Edit anything before any of the above
```

If the user approves writing to a file, use your file-writing tool. Don't use a shell heredoc: a `cat << 'EOF'` block silently truncates if the release notes contain a line that's exactly `EOF`, which is rare but ugly when it happens.

If the user approves a GitHub release, write the notes to a temporary file with the file-writing tool, then run:

```bash
gh release create <tag> --title "<version>" --notes-file /tmp/release-notes.md
```

If the user approves writing to `CHANGELOG.md`, prepend the new entry to the top of the file (below any existing header). Do not overwrite existing entries.

## Important rules

- NEVER invent PR numbers or links. If you can't find a PR reference, omit it.
- NEVER include merge commits (commits starting with "Merge ") unless they contain meaningful information.
- NEVER include commits that are purely version bumps or tag updates unless the user asks for them.
- NEVER take a writing or release action without explicit approval. Step 7's options describe human actions the user has to confirm.
- Before presenting the notes, scan for sensitive content: email addresses, internal hostnames, ticket numbers from internal trackers, anything that looks like a credential or API key. Flag any matches to the user before writing to a public location or creating a GitHub release.
- If the git history doesn't use conventional commits, do your best to categorize by reading the messages. Put anything ambiguous in "Other".
- Keep descriptions concise. One line per change. If a commit message is 200 characters, shorten it to the key point.
- The summary paragraph is for end users, not developers.

How to install it

Create the skill file in your project:

mkdir -p .claude/skills
# paste the skill content into this file
touch .claude/skills/release-notes.md

For a personal skill that works across all your projects, place it in ~/.claude/skills/release-notes.md instead.

Example usage

You’re about to cut a release and want to know what changed:

> Generate release notes since the last tag

Confirming repo: ~/code/acme-app is a git repo. Good.
Finding latest tag... v2.4.0 (12 days ago).
Generating release notes for v2.4.0..HEAD (9 commits).

Parsing commits (batched git log)...
Looking up PR details for #187, #191, #193, #195...

# v2.5.0

This release adds PDF export for reports and fixes a login bug
that affected users with special characters in their passwords.

## Features

- **reports:** Add PDF export with customizable page layout ([#193](https://github.com/acme/app/pull/193))
- **reports:** Add date range filter to the export dialog ([#195](https://github.com/acme/app/pull/195))
- **onboarding:** Show a welcome tour on first login ([#191](https://github.com/acme/app/pull/191))

## Bug fixes

- **auth:** Handle special characters in passwords during login ([#187](https://github.com/acme/app/pull/187))
- **auth:** Fix session expiry redirect loop on Safari

## Other

- Update Node.js from 20.11 to 20.12
- Remove deprecated /api/v1/legacy endpoints

## Contributors

@jsmith, @agarcia, @mwong

---

Want me to (each requires your approval):
1. Copy this to your clipboard
2. Write it to CHANGELOG.md (prepended, not overwritten)
3. Create a GitHub release with this content
4. Edit anything before any of the above

How it works

The non-obvious design choice in this skill is that the categorized list and the summary paragraph are written for two different audiences. Most automated changelog tools dump commit messages verbatim and call it done. That works for the categorized list (developers reading the changelog know what feat(auth): add token refresh means), but it fails for the summary at the top, which is the part end users actually read.

Step 6 splits these explicitly. The categorized list keeps commit-style brevity and scopes. The summary rewrites the changes in plain language. “You can now export reports as PDF” is a sentence a user understands. “feat(reports): add PDF export with customizable page layout” is a sentence a developer understands. They aren’t interchangeable, and the skill stops trying to make them be.

A few smaller decisions worth flagging. Step 1 batches the commit log into one call rather than fanning out per-commit body reads. On a 500-commit range that’s the difference between one subprocess and 500. Step 3 trusts gh pr view --json url instead of constructing URLs from https://github.com/org/repo/pull/N, because the constructor breaks on GitHub Enterprise hosts and the field is right there. Step 7 makes “create a GitHub release” a confirmable action with a “human actions” label, not something the agent slides into the user’s repo.

For more on why explicit human-approval gates matter on writing actions like these, see human-in-the-loop patterns for AI agents. For the related skill that handles the lower-level CHANGELOG.md format specifically, see the changelog generator skill. Well-structured commit messages make this skill significantly more useful, since it groups by commit type; the commit message skill covers how to set that up.

Customizing it

If your project uses different groupings (“Security” as its own category, or “Performance” merged into “Bug fixes”), edit the list in step 4. The agent will sort commits into whatever groups you define.

To bump the version number alongside generating notes, add a step between 6 and 7 that reads the current version from package.json (or Cargo.toml, or pyproject.toml’s [project] version, or whatever convention your ecosystem uses). Based on the changes, suggest major if there are breaking changes, minor if there are features, patch if there are only fixes. Ask the user to confirm before updating the file. Don’t auto-bump.

To include short commit hashes for traceability, change the formatting rule in step 6 to include them: - **scope:** description (af3e21). Some teams find this useful for quickly jumping to specific changes. Most users don’t.

To filter out noise (lots of chore or test commits you don’t want surfacing), add to step 4: “Omit commits with type chore, ci, or test from the output entirely unless the user asks for a full changelog.” The “Auto-create PRs” workflow in CI is the natural home for the full unfiltered version.

For releases that span multiple repos (a monorepo or coordinated multi-service release), add a step that accepts a list of repo paths and runs the git log in each one, then merges the results. The rest of the skill works the same way. If you’re already wiring this into a release pipeline, the agents for DevOps article covers how this skill fits next to deployment automation.

The point of all this

Release notes are a translation problem, not an aggregation one. The commits already exist; the work is rewriting them for the audience that’s reading. Spend 30 seconds reviewing what the agent produced instead of 30 minutes massaging commit messages into prose. The skill won’t catch every nuance you’d want to add, but it gets you 90% of the way there, which is exactly the right amount of help.