
Write Release Notes
- 5 installs
- 7.5k repo stars
- Updated August 5, 2026
- antinomyhq/forge
write-release-notes is a Claude Code skill that fetches a GitHub release and its linked PRs and synthesizes them into structured, user-facing release notes.
About
This skill generates user-facing release notes for a given version tag. A maintainer runs it when publishing a release and wants a polished changelog. It pulls the GitHub release and every linked PR's title and description, groups the changes by category, and writes a structured Markdown release note. It then validates the output stays under 2000 characters and lists only external contributors.
- Fetches a GitHub release and every linked PR's title and body via a bundled script
- Categorizes PRs (features/fixes/perf/refactor/maintenance) and synthesizes user-facing notes
- Validates the draft is under 2000 characters and excludes core-team and bot contributors
Write Release Notes by the numbers
- 5 all-time installs (skills.sh)
- Ranked #193 of 248 Release Management skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
write-release-notes capabilities & compatibility
- Capabilities
- release notes · pr description · documentation
- Works with
- github
- Use cases
- documentation · copywriting
What write-release-notes says it does
Fetches the release from GitHub, retrieves every linked PR's title and description, then synthesizes all changes into a polished, user-facing release note
run the bundled validation script to confirm the output is under 2000 characters
npx skills add https://github.com/antinomyhq/forge --skill write-release-notesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 7.5k |
| Last updated | August 5, 2026 |
| Repository | antinomyhq/forge ↗ |
What it does
Synthesize a GitHub release and its linked PRs into a structured, user-facing Markdown release note under 2000 characters.
Who is it for?
Turning a GitHub release and its PRs into polished user-facing release notes
When should I use this skill?
Asked to write, generate, or create release notes for a version
What you get
A structured, under-2000-character Markdown release note grouping changes by category with external contributors credited.
- A Markdown release note grouped by change category under 2000 characters
By the numbers
- output validated to stay under 2000 characters
- groups PRs into 5 change categories
Files
Write Release Notes
Generate clear, informative, and enthusiastic release notes by pulling live data from GitHub and synthesizing every PR into a cohesive narrative.
Workflow
1. Fetch Release Data
Run the bundled script to pull the release metadata and all linked PR details in one shot:
bash .forge/skills/write-release-notes/scripts/fetch-release-data.sh <version> [owner/repo]<version>: The release tag (e.g.v1.32.0)[owner/repo]: Optional. Defaults to the current repo detected viagh repo view.
The script outputs two sections:
### RELEASE METADATA ###— tag name, publish date, release name, raw body### PR DETAILS ###— one JSON object per PR with:number,title,body,labels,author,mergedAt,url
2. Categorize Changes
Group PRs by their conventional commit prefix or label:
| Category | Prefixes / Labels |
|---|---|
| Features | feat, type: feature |
| Bug Fixes | fix, type: fix |
| Performance | perf |
| Refactors | refactor |
| Maintenance | chore, docs, ci, build, deps |
Dependency bumps (e.g. Dependabot PRs) go into Maintenance. Skip PRs with error: "not found".
3. Write the Release Notes
Produce a Markdown document with the following structure. Keep the tone informative and enthusiastic — explain what changed and why it matters, without resorting to marketing fluff.
# [Product Name] [Version] — [Descriptive Tagline]
> One-sentence summary of what this release focuses on.
## What's New
[2-4 sentence narrative covering the biggest features and fixes.
Describe what changed and what users can now do. Use active voice. Be factual but upbeat.]
## Highlights
### [Feature/Fix Category]
**[PR Title rephrased as a clear description of the change]**
[1-2 sentences expanding on the PR description. Explain what changed and what users can now do differently.
If the PR body has useful context, distill it. If empty, infer from the title.]
[Repeat for each significant PR — skip pure chores/dep bumps unless noteworthy]
## Bug Fixes & Reliability
[Bullet list of fixes, each with a brief impact statement]
## Under the Hood
[Brief paragraph or bullet list covering refactors, maintenance, and dep updates —
keep it light, acknowledge the work without boring the reader]
## Contributors
A huge thank you to everyone who made this release happen: [list @handles — exclude bots like @dependabot]
---
**Full changelog**: [GitHub Release link]4. Tone & Style Guidelines
- Lead with what changed: "You can now..." or "Forge now..." beats "We added..."
- Be specific: Name the feature and describe what it does, not just the category
- Be informative, not marketty: Avoid vague adjectives like "seamless", "smarter", "blazing", "powerful", "rock-solid". Instead, state the concrete fact (e.g. "editor no longer spawns a git process on every keystroke" beats "blazing-fast editor")
- Enthusiasm through substance: Let the actual improvement speak for itself. Use active, direct language.
- Short paragraphs: Max 3 sentences per block
- Skip internal jargon: Translate crate names and internal concepts into plain language
- Celebrate contributors: Name them by handle
- Tagline formula:
[Version] — [Factual Theme Description](e.g. "v1.32.0 — Terminal Context, File Drop Support, Windows Performance") - No implementation details: Do not mention internal module names, struct names, function names, crate names, or how something was implemented. Focus purely on what the user experiences or gains.
- No PR/issue references: Do not include PR numbers, issue numbers, or links to GitHub PRs/issues in the release notes. Focus on the changes themselves, not their tracking identifiers.
5. Contributors Filter
Only include external contributors in the Contributors section — exclude the core team:
@tusharmath@amitksingh1490@laststylebender14- Bots (e.g.
@dependabot)
If no external contributors exist, omit the Contributors section entirely.
6. Validate Length
After writing the release notes, run the bundled validation script to confirm the output is under 2000 characters:
echo "<release notes>" | bash .forge/skills/write-release-notes/scripts/validate-release-notes.shIf it prints FAIL, trim the draft and re-run until it prints PASS:
- Remove the Under the Hood section first
- Consolidate Bug Fixes into a shorter bullet list
- Shorten individual PR descriptions to one tight sentence
- Remove the least impactful Highlights entries
7. Output
Print the final release notes directly in the chat. Do not write to a file unless the user explicitly asks.
Notes
- The script handles ANSI color codes injected by
ghCLI automatically. - PRs not found (closed without merge, private, etc.) are silently skipped.
- If the release has no linked PRs in its body, fall back to listing commits between tags:
gh api repos/<owner>/<repo>/compare/<prev_tag>...<version> --jq '.commits[].commit.message'#!/usr/bin/env bash
# Fetches all PR numbers from a GitHub release and outputs their details.
# Usage: ./fetch-release-data.sh <version> [repo]
# Example: ./fetch-release-data.sh v1.32.0 antinomyhq/forge
set -euo pipefail
VERSION="${1:?Usage: $0 <version> [repo]}"
REPO="${2:-$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null)}"
if [[ -z "$REPO" ]]; then
echo "ERROR: Could not determine repository. Pass it as second argument." >&2
exit 1
fi
# Use a temp file to avoid large variable issues with bash subshells
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
# Fetch release metadata (strip ANSI color codes that gh CLI may inject)
gh api "repos/$REPO/releases/tags/$VERSION" | sed 's/\x1b\[[0-9;]*m//g' > "$TMPFILE"
if [[ ! -s "$TMPFILE" ]]; then
echo "ERROR: Release $VERSION not found in $REPO" >&2
exit 1
fi
echo "### RELEASE METADATA ###"
jq '{tagName: .tag_name, publishedAt: .published_at, releaseName: .name, body: .body}' < "$TMPFILE"
# Extract PR numbers from the release body (handle \r\n line endings)
PR_NUMBERS=$(jq -r '.body // ""' < "$TMPFILE" | tr -d '\r' | grep --color=never -oE '#[0-9]+' | tr -d '#' | sort -un)
if [[ -z "$PR_NUMBERS" ]]; then
echo "WARNING: No PR numbers found in release body." >&2
exit 0
fi
echo "### PR DETAILS ###"
for PR_NUM in $PR_NUMBERS; do
PR_DATA=$(gh pr view "$PR_NUM" \
--repo "$REPO" \
--json number,title,body,labels,author,mergedAt,url \
2>/dev/null | sed 's/\x1b\[[0-9;]*m//g') || true
if [[ -n "$PR_DATA" ]]; then
echo "$PR_DATA"
else
echo "{\"number\": $PR_NUM, \"error\": \"not found\"}"
fi
done
#!/usr/bin/env bash
# Validates release notes piped via stdin or passed as a file argument.
# Usage:
# echo "..." | bash validate-release-notes.sh
# bash validate-release-notes.sh release-notes.md
#
# Exit codes:
# 0 — valid (under 2000 characters)
# 1 — invalid (2000 characters or over)
set -euo pipefail
MAX_CHARS=2000
if [[ $# -ge 1 ]]; then
content=$(cat "$1")
else
content=$(cat)
fi
char_count=${#content}
if [[ $char_count -lt $MAX_CHARS ]]; then
echo "PASS: $char_count characters (limit: $MAX_CHARS)"
exit 0
else
echo "FAIL: $char_count characters — exceeds limit of $MAX_CHARS" >&2
echo "Trim $(($char_count - $MAX_CHARS)) more character(s) to pass." >&2
exit 1
fi
Related skills
FAQ
Where does the data come from?
A bundled script fetches the release metadata and every linked PR's number, title, body, labels, author, and merge date via the GitHub CLI.
Is there a length limit?
Yes. A validation script confirms the output is under 2000 characters and the draft is trimmed until it passes.