| name | release |
| description | Create and push a release tag for the SmartLists plugin with a changelog in the annotated tag message. Project-specific version scheme — RC number lives in the Revision segment with a bare -rc suffix (e.g. v12.0.0.3-rc); stable releases bump the Build segment (e.g. v10.11.31.0). Use when the user wants to tag a release or create an RC. |
| argument-hint | stable|rc |
| allowed-tools | Bash |
Release Skill
Create a new git version tag with a changelog generated from commits and PR titles since the last relevant tag, then push it to the remote. This plugin uses .NET's four-part Major.Minor.Build.Revision version scheme, not SemVer, and currently ships two parallel release lines while Jellyfin 12 is in RC — this skill handles both.
Steps
1. Validate input
$ARGUMENTS must be stable or rc. patch and minor are accepted as synonyms for stable — the four-part scheme doesn't distinguish bump sizes, since the plugin version tracks the Jellyfin version line, not the size of the change.
If $ARGUMENTS is major, stop and explain: the Major/Minor segments of the version track the target Jellyfin version line, and only change when that target changes. That's a manual decision made when a new Jellyfin release lands, not something this skill infers from a bump type.
If $ARGUMENTS is empty, present this prompt and wait for the user's answer before proceeding:
What type of release do you want to create?
stable — Final release for the current Jellyfin 10.11 line (tagged on 10.11-release, e.g. v10.11.31.0)
rc — Release candidate for the upcoming Jellyfin 12 line (tagged on main, e.g. v12.0.0.3-rc)
Use the user's answer as $ARGUMENTS and continue. If $ARGUMENTS is present but doesn't match stable, patch, minor, or rc, stop and tell the user: "Usage: /release stable|rc".
2. Sync and check working tree
First check the current branch:
git branch --show-current
The correct branch depends on release type:
rc → must be on main.
stable → must be on 10.11-release (the current stable line while Jellyfin 12 is in RC).
If on the wrong branch, warn and ask for confirmation before continuing:
You are on branch '<current>' but <rc/stable> releases are tagged from '<expected>'.
Continue anyway? (yes / no)
Run git pull to bring the local branch up to date, then git fetch --tags --prune-tags --force so the local tag list matches the remote (a plain git pull doesn't reliably sync new or deleted tags, and a stale tag list would corrupt the version calculation). If the pull fails, stop and show the error — do not proceed with a stale or diverged branch.
For stable releases specifically, also verify main has been merged in:
git log HEAD..origin/main --oneline
If this returns any commits, warn the user that main has unmerged work and point them at the promotion flow documented in CLAUDE.md: merge main into 10.11-release, merge back so the trees converge, then smoke test with JELLYFIN_ABI=10.11.0 ./build-local.sh. Ask whether to continue anyway despite the gap.
Then check for uncommitted changes:
git status --porcelain
If there are uncommitted changes, show the user a summary and ask:
You have uncommitted changes:
<list of changed files>
Auto-generate a commit message and commit? (yes / no)
3. Gather context
Determine the changelog base tag depending on release type:
git tag --list 'v12.*' --sort=-v:refname | head -1
git tag --list 'v10.11.*' --sort=-v:refname | head -1
Then collect commits and PR titles since that base:
CHANGELOG_BASE="<tag from above, may be empty>"
if [ -z "$CHANGELOG_BASE" ]; then
git log --oneline --no-merges
else
git log ${CHANGELOG_BASE}..HEAD --oneline --no-merges
fi
if command -v gh &>/dev/null; then
CHANGELOG_BASE_DATE=$(git log -1 --format=%cI "${CHANGELOG_BASE}" 2>/dev/null)
if [ -n "$CHANGELOG_BASE_DATE" ]; then
gh pr list --state merged --search "merged:>${CHANGELOG_BASE_DATE}" --json number,title,mergedAt --limit 50
fi
fi
If there are no commits since the changelog base, stop and tell the user: "Nothing to release."
4. Calculate next version
The version scheme is .NET's four-part Major.Minor.Build.Revision. Ordering is purely numeric, left to right — there are no pre-release labels baked into the .NET version itself.
- Revision > 0 with a bare
-rc git-tag suffix marks a release candidate; the Revision number is the RC number. The -rc suffix exists only as a workflow routing marker (it sends the build to the unstable manifest branch and marks the GitHub release as a prerelease) — it is never part of the actual .NET version.
- Never produce
-rc.N-style suffixes (e.g. v12.0.0.1-rc.2). Two such tags would strip to the identical manifest version 12.0.0.1 and break plugin auto-update ordering. Each RC increments the Revision segment itself.
rc: base = highest v12.* tag (from step 3).
- If the base is itself an RC (ends in
-rc), increment Revision: v12.0.0.2-rc → v12.0.0.3-rc.
- If the base is a stable v12 tag (a future state, once Jellyfin 12 has shipped and this line has stable releases), start a new RC cycle: bump Build, set Revision to 1:
v12.0.1.0 → v12.0.2.1-rc.
- If there is no
v12.* tag yet, ask the user for the starting Major.Minor before proceeding — this skill has no basis to guess it.
stable: base = highest stable v10.11.*.0 tag.
- Bump Build, Revision stays 0:
v10.11.30.0 → v10.11.31.0.
- Never put a hotfix counter in Revision — Revision is reserved exclusively for RC numbers on the v12 line.
Future note: when Jellyfin 12 final ships, the first v12.0.X.0 stable release is cut from main, it sorts above both existing lines, all users converge onto it, and the split-line scheme described here ends. Update this skill at that point.
5. Generate changelog
Write a concise, human-readable changelog using the commits and PR titles collected in step 3.
Guidelines:
- Group changes into categories where it makes sense: Features, Bug Fixes, Improvements — but only include categories that have entries.
- Prefer PR titles over raw commit messages when both refer to the same change (PRs are usually better worded).
- Skip noise: merge commits, version bumps, "fix typo", "WIP", etc.
- Only include application-facing changes: omit commits that only touch repo infrastructure —
.github/ workflows, .gitignore, linter configs, docs-only changes (docs/, README, CLAUDE.md), or other housekeeping that doesn't affect the running plugin.
- For stable releases: omit bug fixes that were fixing issues in the new features added during that same RC cycle. Those are internal RC iteration details, not user-facing changes. Only include the features, improvements, and unrelated bug fixes that matter to users upgrading from the previous stable release.
- Keep each line short and punchy — this is a tag message, not a novel.
- Use plain text, not markdown (git tag messages render as plain text).
- The changelog is shown to end users (GitHub release + Jellyfin plugin catalog), so keep the wording non-technical and focused on what changed for them.
Format:
Features:
- ...
Bug Fixes:
- ...
Improvements:
- ...
Do not include a title line here — that's assembled separately in step 6.
6. Tag message format — critical
The release workflow reads the changelog with git tag -l --format='%(contents:body)', which returns everything after the first blank line of the tag message. That body is published verbatim as both the GitHub release body and the Jellyfin plugin-manifest changelog entry.
This means the tag message must be structured as:
Release <version>
<changelog body from step 5>
For RCs, use "Release candidate " instead of "Release " as the first line.
The first line and the blank line beneath it are stripped before publishing — so the changelog body from step 5 must stand alone and make sense without that first line. Do not fold the title into the body, and do not omit the blank line separator.
7. Show summary and ask for confirmation
Display to the user:
New tag: <new-version>
Previous: <changelog-base-tag> (or "none")
Commits: <count> commit(s) since previous tag
Changelog:
-----------
<full tag message text, including "Release <version>" header>
-----------
Ready to create and push this tag? (yes / no / edit)
- yes → proceed to step 8
- no → abort, inform the user no tag was created
- edit → ask the user to provide the revised changelog, then re-show the summary and ask again
8. Create and push the tag
Write the changelog text to a temporary file and use -F — passing a multi-line message with embedded quotes/backticks through -m "..." is a shell-quoting accident waiting to happen:
CHANGELOG_FILE=$(mktemp)
cat > "$CHANGELOG_FILE" <<'CHANGELOG_EOF'
Release <version>
<changelog body>
CHANGELOG_EOF
git tag -a <new-version> -F "$CHANGELOG_FILE"
rm -f "$CHANGELOG_FILE"
git push origin <new-version>
Confirm success: "✓ Tagged and pushed "
If any command fails, show the error output and stop — do not attempt to clean up automatically. If the tag was created locally but the push failed, tell the user the tag exists locally and can be removed with git tag -d <new-version> or pushed manually once the issue is resolved.