Drafting release notes
SkillDocs & knowledgeLets your agent draft GitHub release notes for a RomM release from the changes since the last stable tag.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Drafting release notes skill
About this capability
Draft the GitHub release notes for a RomM release, stable or alpha/beta prerelease, from the diff since the previous stable tag, in the house format the published RomM releases use. Use when asked to "draft the 5.3.0 notes", "write the release notes for the beta", "what goes in the 5.3.0-alpha.1 not
What this skill tells your AI
The instructions your AI receives, as published by rommapp/romm in .claude/skills/draft-release-notes/SKILL.md and read by ahel’s review.
Turn a tag range into the notes RomM actually publishes, for a stable release or for an alpha/beta prerelease. The GitHub autogenerated list is raw material, not the deliverable: the published notes reorganize it into sections, lift the few changes that matter into prose Highlights, and add the callouts an upgrader needs before they pull the image.
Output a markdown file and print it. Never run gh release create or
gh release edit. Publishing is the maintainer's call.
1. Pick the shape
Two documents exist, and the tag decides which one you are writing.
| Tag | Document |
|---|---|
X.Y.Z, X.Y.Z-alpha.1, X.Y.Z-beta.1 | the full treatment, sections 3 onward |
X.Y.Z-alpha.2 and later, X.Y.Z-beta.2 and later | flat ## What's Changed |
a stable patch with no feat: work | flat ## What's Changed |
This is what the history does without exception: every .1 prerelease and every
feature-carrying stable gets Highlights and partitioned bullets, while every
later prerelease in the same line is a plain list of what landed since. The flat
shape is just the autogenerated body with ## New Contributors and the compare
link kept, so for those tags steps 4 through 6 do not apply.
2. Resolve the range
$ARGUMENTS is the tag being drafted. If it is empty, infer it: take the last
stable tag, bump minor for a range containing feat: work or patch otherwise,
and say which you picked so it can be corrected.
The base is the stable tag below the release line, for a stable release and a
prerelease alike, so a whole line accumulates against one base. 5.1.0-alpha.1,
5.1.0-beta.1 and 5.1.0 all compare against 5.0.0, which makes each document
a superset of the one before it rather than a delta on top of it.
TAG=5.3.0-alpha.1 # what you are drafting, stable or prerelease
LINE=${TAG%%-*} # 5.3.0, the stable version of this line
git fetch origin master --tags
# the stable tag immediately below $LINE. Numeric tags only, since the legacy
# v-prefixed ones sort above them, and sort -V rather than --sort=v:refname
BASE=$(git tag --list '[0-9]*' | grep -vE -- '-(alpha|beta|rc)' | grep -vFx "$LINE" \
| { cat; echo "$LINE"; } | sort -V | grep -B1 -Fx "$LINE" | head -1)
# an already-tagged prerelease is drafted against its tag, not against master,
# or the draft picks up everything merged since it shipped
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then HEAD_REF="$TAG"; else HEAD_REF=origin/master; fi
RANGE="$BASE..$HEAD_REF"
git rev-list --count $RANGE
git diff --stat $RANGE | tail -20
Sanity-check the pair before going further. $BASE should equal the tag in the
previous release's compare link. If $TAG is a prerelease and $LINE is already
tagged stable, the line has shipped and $TAG is wrong: stop and confirm the
version.
3. Pull the raw material
The autogenerated list is the spine. Its order is merge order, keep it when
you partition lines into sections, and keep each line's by @user in <url> tail
byte for byte so attribution and first-time-contributor detection stay correct.
gh api repos/rommapp/romm/releases/generate-notes \
-f tag_name="$TAG" -f previous_tag_name="$BASE" --jq '.body' > /tmp/raw-notes.md
Then run the sweeps that find what the PR titles do not say. They are an index,
not an answer: a @protected_route(...) or _get_env(...) call wrapped across
lines matches only on the line that actually changed, so a moved path or a new
default can surface as a bare +@protected_route(. Read the full hunks of every
file a sweep touches before writing the tables.
# new or changed env vars
git diff $RANGE -- backend/config/__init__.py | grep -E '^[+-].*_get_env'
# new, moved or removed routes
git diff $RANGE -- backend/endpoints/ | grep -E '^[+-].*@(protected_route|router\.)'
# response-schema changes the frontend types are generated from
git diff --stat $RANGE -- backend/endpoints/responses/ backend/handler/scan_handler.py
# migrations, which imply an irreversible upgrade step
git diff --name-status $RANGE -- backend/alembic/versions/
# config.yml surface
git diff $RANGE -- backend/config/config_manager.py | grep -E '^[+-].*(class |: )' | head -40
# the sweeps only point at files, so read what actually changed inside them
git diff $RANGE -- backend/endpoints/ backend/config/
For each candidate Highlight, read the PR itself rather than paraphrasing its
title: gh pr view <n> --json title,body,author. The prose in the notes is
usually a compression of the PR description.
4. Assemble the document
Follow template.md in this skill directory for the exact skeleton. Sections in
this order, all optional except the last two:
| Section | Contents |
|---|---|
| Callouts | > [!WARNING] upgrade actions, > [!NOTE] env vars and API changes |
## Highlights | ### Title + prose per headline feature |
## Minor changes | every feat: PR not already a Highlight |
## Fixes | every fix: PR |
## Other changes | perf:, chore:, docs:, refactor:, revert:, build(deps): |
| API changes | table, inline for an alpha, in <details> once it gets long |
## New Contributors | verbatim from the autogenerated body |
**Full Changelog** | verbatim from the autogenerated body |
Which of the two optional tables you carry depends on the line, not on whether the tag is stable. See section 6.
A patch release skips all of it, stable or prerelease. Under roughly eight
PRs with no feat:, publish a single flat ## What's Changed list plus the
changelog link, the way 4.9.1-beta.1 and 4.9.1 both did. Do not manufacture
Highlights for a bugfix release.
5. Section rules
Highlights. Three to eight for a minor line, zero for a patch line. A change earns one only if it changes what a user can do or how they run RomM: a new subsystem, a new metadata source, a new UI surface, a new export format. Format:
### Sentence case name of the thing, no trailing punctuation.- One or two short paragraphs, second person, present tense. Say what it does, then the one thing the reader has to know to use it (the config key, the setting, the caveat). Name real keys and paths in backticks.
- End the first paragraph with a space and the bare PR reference,
#3211. Bare#NNNNin prose, full URLs only in the bullet lists. - Add a fenced
yamlblock when a config key drives the feature. - Leave a
<!-- screenshot: ... -->placeholder for anything visual. Do not inventuser-images.githubusercontent.comURLs; the maintainer pastes real ones.
Bullet sections. One line per PR, copied from the autogenerated body. Fix up
only the title, in place: give it a conventional-commit prefix and scope if it
lacks one (Skip DMCA-locked grids when scraping SteamGridDB becomes
fix: Skip DMCA-locked grids when scraping SteamGridDB), and lowercase a shouty
one. Never drop a PR, never merge two into one line, never reorder across
sections. Promoting a PR to a Highlight removes its bullet: every published
release does this, so 5.1.0-beta.1 lists neither #3211 nor #3854 under Minor
changes, and #3155 left the 4.9.0-alpha.1 bullets once the beta gave it a
Highlight. Promote or list, never both.
build(deps): and dependabot[bot] lines always land in Other changes, never
in Fixes, even when the bump closes a CVE. Call the CVE out in a callout instead
if it matters.
Callouts. A > [!WARNING] is for something the upgrader must do, and it goes
at the very top, above Highlights: a renamed config.yml key, a DB privilege
the migration needs, a proxy cache to purge. Write the migration action, not the
rationale. A > [!NOTE] carries the env var table and the API change table. A
> [!IMPORTANT] marks a feature shipping ahead of its documentation.
Env vars go in a table with variable | default | description; API changes in a
table with Change | Description for edits, and Method | Path | Description
for a new endpoint group. Prefix a breaking entry with ⚠️.
6. Prerelease versus stable
The three documents on a line are the same document, regenerated over a range that keeps growing against a fixed base. Each one starts as its predecessor, not as a blank page, so open the previous one's body and edit that:
# the release before $TAG on this line: its alpha.1, or its last full beta
gh release view 5.1.0-beta.1 --json body --jq .body
-
alpha.1 opens the line. Put the env var and API tables in one
> [!NOTE]block at the top, where the third-party app developers reading the alpha will see them first. -
beta.1 is the alpha's notes plus everything merged since. Carry the alpha's Highlights across verbatim unless the feature changed, add the new ones, and fold in any warning that alpha feedback produced. Once the API table runs past roughly fifteen rows, move it to the bottom inside
<details><summary><h2>API changes</h2></summary>, since by the beta most readers are upgraders rather than integrators. -
the stable release is the last full-treatment prerelease's notes plus everything merged during the beta. Two things change:
Re-curate the Highlights. They are not carried over untouched. Going from
5.0.0-beta.1to5.0.0, four were added (CRT mode, server-side ROM patching, the permission system, the music player), three were dropped, and three were renamed, "A brand-new frontend" becoming "UI overhaul". Pick what the finished release is about, not what the beta was about.Drop the tables a prerelease already published. The API table is for the developers tracking the line, and they read it in the alpha.
5.1.0,5.0.0,4.9.0and4.8.0all dropped theirs;5.2.0kept one only because its line shipped no prerelease that carried it. Keep the env var table when it grew during the beta, as5.1.0did going from three variables to seven, since a reader upgrading straight from the last stable has seen none of them.
7. Before handing it over
- Every PR in
/tmp/raw-notes.mdis accounted for exactly once, either as a Highlight or as a bullet, never both and never dropped. - Every
#NNNNin the Highlights resolves to a PR in the range. - Every new env var found in step 3 is documented, and every route change too.
- A migration in the range means you have checked whether it needs a warning.
- The
**Full Changelog**compare link ends in the tag you are drafting. Editing a prerelease's body into a stable one makes this easy to miss:4.9.0shipped pointing at4.8.1...4.9.0-beta.1. - No em-dashes, no invented screenshot URLs, no marketing claims the diff does not support.
Print the draft and say what you could not determine (which screenshots are needed, which version you inferred), rather than guessing.
Signals
- GitHub stars
- 13k
- Forks
- 735
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
draft-release-notes-rommapp- Source
- github.com/rommapp/romm