Release - Cut a Project Release
Executes the project's release procedure with safety guards appropriate to a high-blast-radius operation. Discovers the procedure from the project itself rather than dictating one, always runs /review-release as preflight, and never pushes past a BLOCKER. Plans before executing; halts on first failure rather than guessing at recovery.
Scope of action: local repo and configured remotes. The skill pushes commits, pushes tags, and (when the procedure includes them) publishes to package registries. It does not attempt automatic rollback of partially-executed releases.
Reversibility note: local steps (version bump, commit, local tag) are reversible with git reset / git tag -d. The remote push is effectively irreversible once anyone has fetched. Package-registry publication is irreversible (most registries permit yank but not delete). The skill names each step's reversibility class in the plan.
Philosophy
High blast radius warrants high friction. Tagging and pushing a release is the kind of action that should never happen by accident. The skill is generous with confirmation prompts and pessimistic about partial-failure recovery. Auto-execution is never the default.
Composition, not duplication. Preflight checking is /review-release's job. This skill always invokes it and fail-stops on red. It does not re-implement the checklist or offer to skip it.
Discover the procedure, don't invent it. Release procedure is project-specific — make release, npm publish, cargo publish, gh release create, a custom CI workflow, or some combination. The skill searches the conventional surfaces before falling back to asking the user. If asked, it offers durable capture so the next release is fully automated.
Halt on first failure. Release sequences are not atomic. A failure halfway through (e.g., tag pushed but npm publish failed) leaves the project in a partial state. The skill stops at the first failure and surfaces the partial state for human decision rather than pushing through or guessing at rollback.
Workflow Overview
┌────────────────────────────────────────────────────────┐
│ RELEASE │
├────────────────────────────────────────────────────────┤
│ 1. Detect repo context │
│ 2. Discover release procedure │
│ 3. If procedure not found: offer durable capture │
│ 4. Determine target version (propose from CHANGELOG) │
│ 5. Idempotence check (scan for partial prior run) │
│ 6. Run /review-release as preflight (fail-stop) │
│ 7. Construct release plan (commands + reversibility) │
│ 8. Present plan + confirm │
│ 9. Execute step-by-step (one pause at local→remote │
│ boundary; halt on first failure) │
│ 10. Final summary │
└────────────────────────────────────────────────────────┘
Workflow Details
1. Detect Repo Context
Run all of the following; abort cleanly on any failure:
- Is this a git repo?
git rev-parse --is-inside-work-tree. Abort if not. - Main branch detection. Try
git symbolic-ref refs/remotes/origin/HEAD, then check formain, thenmaster. Ask the user if none detected. - Current branch.
git branch --show-current. - Working tree clean.
git status --porcelain. Abort if dirty with the suggestion: "Commit or stash before releasing." Releases from a dirty tree are unsafe. - Up-to-date with origin.
git fetch, then checkgit rev-list --count HEAD..@{u}. If behind, abort with the suggestion to pull. If ahead, note this (it's expected if the release commit is being prepared locally, but flag it). - On the main branch. If current branch is not the main branch, ask the user to confirm intent — releasing from a non-main branch is unusual but valid (e.g., backport release lines). Do not abort; defer to the user.
2. Discover Release Procedure
Search the following surfaces in order. Stop at the first hit, but record every hit (procedure may span multiple surfaces — e.g., Makefile target + RELEASING.md narrative):
- Makefile — parse for a
release,publish, ortagtarget. Read the recipe. - RELEASING.md or RELEASE.md at repo root.
- CONTRIBUTING.md — look for a section titled "Release", "Releasing", or "Cutting a Release".
- CLAUDE.md (project-level) — look for the same.
- package.json — scripts named
release,publish,prepublish*,version*,postversion. - pyproject.toml / Cargo.toml / equivalent — packaging metadata that implies a publish step.
.github/workflows/release.ymlor similar CI release config.- Git tag history —
git log --tags --simplify-by-decoration --pretty="format:%d %s"to infer pattern from prior releases (tag format likev1.2.3vs1.2.3, accompanying commit message convention likechore: release v1.2.3).
Record what was found and where. Prefer executable sources over prose — a Makefile target is the authoritative procedure if it exists.
3. Offer Durable Capture if Missing
If step 2 found no procedure (no executable target, no prose doc, no CI workflow, no git-tag pattern beyond bare history):
- Tell the user no procedure was discoverable.
- Ask the user to describe the release steps in order.
- Once described, offer four options for where to record them:
- Makefile target (Recommended) — executable, version-controlled, callable by humans and future skill invocations. Best for sequences of shell commands.
- RELEASING.md — conventional prose home. Good when steps need narrative explanation or have human-judgment branches.
- CLAUDE.md — only if the project doesn't have a Makefile and doesn't warrant a top-level doc. Adds to prompt context, so prefer the above two.
- Skip durable capture for this release — proceed without recording.
If the user opts to record, write the file/target now and commit it as a separate commit before proceeding with the release (chore: document release procedure). The recording commit is part of pre-release hygiene, not part of the release itself.
If the user declines to describe the procedure, abort with the suggestion that they invoke the steps manually for this release and re-run /release next time once a procedure is documented.
4. Determine Target Version
- Read
CHANGELOG.md(or equivalent —CHANGES,HISTORY.md, etc.) and locate the unreleased section. - Inspect commits since the last tag (
git log <last-tag>..HEAD --oneline) for breaking-change markers (BREAKING CHANGE:,feat!:,fix!:, etc.) and conventional commit types (feat:,fix:,chore:). - Propose a semver bump:
- Major if breaking-change markers present.
- Minor if
feat:commits but no breaking changes. - Patch otherwise.
- Present: "Last tag: vX.Y.Z. Proposing vA.B.C based on [N feat: commits / breaking change in commit abc1234 / no notable commits]. Confirm or override?"
- Validate the user's chosen version against the project's tag format convention (from step 2's tag-history inspection). If the convention is
vX.Y.Zbut the user typed1.3.0, normalize and confirm.
If no CHANGELOG exists, skip the proposal step and ask the user directly for the target version.
5. Idempotence Check
Scan for evidence of a partial prior run at the target version:
- Local tag.
git tag --list <target-tag>— non-empty means a local tag already exists. - Remote tag.
git ls-remote --tags origin <target-tag>— non-empty means the tag was already pushed. - GitHub release (if
ghis available).gh release view <target-tag>— success means a GitHub release exists. - Package-registry publication (best-effort; depends on procedure). For
npm,npm view <pkg>@<version>. Forcargo, the publish step itself will fail with a clear error.