Release Skill
Automated release workflow: pre-flight, branch, changelog+version update, PR, merge, tag, GH release.
Arguments
--auto— Skip all confirmation gates (pre-flight + PR merge)--preview— Create GH release as prerelease instead of latest
Phase 1: Pre-flight
1.1 Read Current State
CURRENT_VERSION=$(cat VERSION)
# Parse semver: MAJOR.MINOR.PATCH
NEXT_PATCH=$((PATCH + 1))
NEXT_VERSION="$MAJOR.$MINOR.$NEXT_PATCH"
TODAY=$(date +%Y-%m-%d)
Read CHANGELOG.md and extract the ## [Unreleased] section content (everything between ## [Unreleased] and the next ## [ header).
1.2 Validate
- CHANGELOG
[Unreleased]section MUST have content (not empty) - If empty: STOP — "No unreleased changes in CHANGELOG.md. Nothing to release."
- Current branch MUST be
main - Working tree MUST be clean (
git status --porcelainempty)
1.3 Explain Procedure
Display to user:
Release v{NEXT_VERSION}
Current: v{CURRENT_VERSION}
Next: v{NEXT_VERSION}
Unreleased changes:
{unreleased content summary — first 10 lines}
Procedure:
1. Create branch release/v{NEXT_VERSION}
2. Update CHANGELOG.md and VERSION
3. Create PR, squash-merge to main
4. Tag v{NEXT_VERSION} on main
5. Create GH release ({"prerelease" if --preview else "latest"})
Proceed?
Confirmation gate: Use AskUserQuestion (yes/no). Skip if --auto.
Phase 2: Branch
git checkout -b release/v{NEXT_VERSION}
Phase 3: Update Files
3.1 CHANGELOG.md
Replace the ## [Unreleased] section:
Before:
## [Unreleased]
### Added
- item A
...
After:
## [Unreleased]
## [{NEXT_VERSION}] - {TODAY}
### Added
- item A
...
The [Unreleased] section becomes empty (just the header), and a new versioned section is inserted immediately below it with all the previous unreleased content.
3.2 VERSION
Write {NEXT_VERSION} to VERSION file (single line, no trailing newline beyond what exists).
3.3 Plugin Versions
Update the version field in every plugins/*/.claude-plugin/plugin.json to {NEXT_VERSION}:
for f in plugins/*/.claude-plugin/plugin.json; do
# Update "version": "..." to new version
done
Use the Edit tool (not sed) for each file. Only change the version field — leave everything else untouched.
3.4 Commit
git add CHANGELOG.md VERSION plugins/*/.claude-plugin/plugin.json
git commit -m "chore(release): prepare v{NEXT_VERSION}"
Phase 4: PR + Merge
4.1 Push Branch
git push -u origin release/v{NEXT_VERSION}
4.2 Create PR
Create a concise PR:
gh pr create --title "chore(release): v{NEXT_VERSION}" --body "$(cat <<'EOF'
## Release v{NEXT_VERSION}
{Concise 2-5 bullet summary of unreleased changes — high-level only, not the full changelog}
EOF
)"
Capture the PR number from output.
4.3 Confirmation Gate
Display PR URL. Ask user to confirm merge via AskUserQuestion. Skip if --auto.
4.4 Merge
gh pr merge {PR_NUMBER} --squash --admin
Phase 5: Checkout Main
git checkout main
git pull origin main
Phase 6: Tag
6.1 Extract Changelog Entry
Extract the ## [{NEXT_VERSION}] section content from CHANGELOG.md (everything between ## [{NEXT_VERSION}] header line and the next ## [ header). Include the ### sub-headers (Added, Changed, Fixed, Removed) but NOT the ## [{NEXT_VERSION}] line itself.
6.2 Create Annotated Tag
git tag -a "v{NEXT_VERSION}" -m "$(cat <<'EOF'
{changelog entry content with ### headers}
EOF
)"
The tag message mirrors the changelog entry content — same format as existing tags (e.g., v0.1.18).
6.3 Push Tag
git push origin "v{NEXT_VERSION}"
Phase 7: GH Release
gh release create "v{NEXT_VERSION}" \
--title "v{NEXT_VERSION}" \
--notes "$(cat <<'EOF'
{changelog entry content — same as tag message}
EOF
)" \
{--prerelease if --preview, otherwise --latest}
Default: --latest. If --preview argument provided: --prerelease instead.
Phase 8: Summary
Release complete: v{NEXT_VERSION}
- Branch: release/v{NEXT_VERSION} (merged)
- PR: {PR_URL}
- Tag: v{NEXT_VERSION}
- Release: {RELEASE_URL}
Cleanup (optional):
git branch -d release/v{NEXT_VERSION}
git push origin --delete release/v{NEXT_VERSION}
Abort Conditions
| Condition | Action |
|---|---|
| Empty [Unreleased] | STOP — nothing to release |
| Dirty working tree | STOP — commit or stash first |
| Not on main | STOP — checkout main first |
| PR creation fails | STOP — show error |
| Merge fails | STOP — show error, branch still exists |
| Tag push fails | Retry with --force if tag exists on remote from failed attempt |
| User declines at any gate | STOP — clean up branch if created |