| name | release-notes |
| description | Generates brief, truth-based release notes for a release PR (typically main → prod) by listing each squash-merged PR, a one-paragraph summary drawn from each PR body, and the linked issue-tracker tickets (Linear, Jira, GitHub Issues, Asana, ClickUp, Shortcut, Plane, etc. — any tracker, configured via a URL template). Use when opening or updating a release PR. |
| argument-hint | ["pr-number"] |
| disable-model-invocation | true |
| allowed-tools | ["AskUserQuestion","Bash","Read"] |
Release Notes
Generate release notes for a release PR. Brief, truth-based, no fabrication.
Configuration
On startup, use the Read tool to load ~/.claude/skills/release-notes/preferences.md.
Tracker-agnostic — works with any issue tracker (Linear, Jira, GitHub Issues, Asana, ClickUp, Shortcut, Plane, Notion, custom). You configure two things: how to detect ticket IDs in PR bodies, and how to link them.
Expected keys:
- ticket-prefix: <PREFIX> # Optional. Tracker key — e.g., ENG, PROJ, ABC, JIRA. Used to detect IDs as <PREFIX>-NNNN. If unset, falls back to the generic regex [A-Z]{2,5}-\d+ (any uppercase prefix + dash + digits).
- ticket-url-template: <url-with-{TICKET}> # Optional. URL template with literal `{TICKET}` placeholder. Examples:
# Linear: https://linear.app/myorg/issue/{TICKET}
# Jira: https://myteam.atlassian.net/browse/{TICKET}
# GitHub Issues: https://github.com/myorg/myrepo/issues/{TICKET}
# Asana: https://app.asana.com/0/{TICKET}
# Shortcut: https://app.shortcut.com/myorg/story/{TICKET}
# Plane: https://app.plane.so/myorg/projects/.../issues/{TICKET}
# If unset, ticket IDs render as bare text without links.
Behavior when keys are missing:
- No
ticket-prefix → use the generic regex [A-Z]{2,5}-\d+. Works for most trackers.
- No
ticket-url-template → render bare ticket IDs (e.g., Closes ENG-1234.). Mention once at the end: "Set ticket-url-template in preferences.md to enable clickable ticket links."
Hard rules
- Truth only. Every claim must be sourced from a merged PR's actual title/body. Never invent features, motivations, or impact statements that aren't in the source PRs.
- Brief. Each PR section is 1–3 sentences max. Cut file enumerations, architecture diagrams, dead-code lists, "what changed" prose blocks. Mirror the source PR's
## Summary, not its full body.
- Never
git add -A and never push the release notes without showing them to the user first and getting explicit approval.
- No drive-bys. Do not add commentary, suggestions, or "follow-ups" the source PRs didn't flag.
Steps
1. Resolve the release PR
- If
$ARGUMENTS is a number → that's the PR.
- Else →
gh pr list --base prod --state open --limit 5 --json number,title,headRefName and ask the user which one if multiple, or use the only open one.
- Capture:
number, title, baseRefName, headRefName, url.
2. List the squash-merged PRs in the release
gh pr view <release-pr> --json commits --jq '.commits[].messageHeadline'
Each squash-merged commit headline ends in (#NNN) — extract the PR numbers. If commits is sparse (squash-merge collapsed history), fall back to:
git log origin/<base>..origin/<head> --oneline
and grep (#\d+) from the headlines.
3. Fetch each child PR
For each PR number, in parallel:
gh pr view <num> --json title,body,number,url,labels
Extract from each:
title (use as section heading after stripping conventional-commit prefix if helpful)
## Summary paragraph (or first 1-3 sentences of the body if no Summary section)
- Issue-tracker ticket IDs from anywhere in the body — match
<TICKET-PREFIX>-NNNN (where TICKET-PREFIX comes from preferences, falling back to [A-Z]{2,5}-\d+). When ticket-url-template is set, substitute {TICKET} to build the link; otherwise render the bare ticket ID.
- PR URL (for the
#NNN link, GitHub auto-links)
If a PR's Summary is more than ~3 sentences, halve it, then halve again. Keep only the user-visible/product-visible bits. Cut implementation prose ("service-side normalizers", "Zod widens", file paths) unless that is the product change.
4. Build the body
Template:
## What's in this release
**1. <Short topic — strip cc-prefix> — #<PR>**
<1–3 sentence summary, sourced from the PR body. User-visible framing.>
Closes [<TICKET>](<ticket-url-template with {TICKET} substituted>)<` · [<TICKET-2>](...)` if multiple>. If no template configured, render as `Closes <TICKET>.` (no link).
**2. <Short topic> — #<PR>**
<1–3 sentences.>
Closes [<TICKET>](...).
## How to test
- <One bullet per PR — pulled from that PR's test plan, simplified to the manual-verify path.>
- <...>
If — and only if — a source PR explicitly flags a follow-up that blocks prod (deploy ordering, flag flip, infra step), add:
## Follow-ups
- <The blocking item, verbatim from the source PR.>
Otherwise, skip the Follow-ups section. Do not invent follow-ups.
5. Title
Format: Release YYYY-MM-DD — <2–6 word theme>
The theme should name the 1–2 biggest user-visible changes (e.g., "PMI UI + migration cleanup"). Use today's date.
6. Show, then push
Print the full proposed title + body to the user. Then ask:
Push this to PR #? (yes / edit / no)
7. Verify
After push, gh pr view <num> --json title,body --jq '.title' to confirm the edit landed. Report the URL.
Style reminders (drawn from user prefs)
- Cut: file paths, line counts, "Adds/Removes/Migrates" tables, architecture diagrams, Figma node IDs, internal scaffolding details.
- Keep: what changed for the user, what it closes, how to verify it on staging.
- If a source PR is purely internal (refactor, test infra, dep bump) and has no user-visible surface, still list it but flag it as such in one sentence: "Internal — ." No padding.
- If two source PRs are part of the same chunked feature, group them under one numbered section with both
#NNN links rather than splitting.