<< All versions

Skill v1.0.0

currentAutomated scan100/100
durchnull/git/release
──Details
PublishedSeptember 27, 2026 at 06:06 AM
Content Hashsha256:dfa5d87ba9559a93...
Git SHA6b78a095b1de
──Files
Files (1 file, 13.2 KB)
SKILL.md13.2 KBactive
SKILL.md · 232 lines · 13.2 KB

version: "1.0.0" model: sonnet effort: low description: Cut a release PR into the production tier (stamp the version into the changelog, then tag production once it merges) argument-hint: "[major|minor|patch|X.Y.Z] [--tag] [--draft]" disable-model-invocation: true allowed-tools: Bash(node "${CLAUDE_PLUGIN_ROOT}/scripts/resolve-git-config.mjs":), Bash(git fetch:), Bash(git ls-remote:), Bash(git rev-parse:), Bash(git log:), Bash(git show:), Bash(git describe:), Bash(git tag:), Bash(git checkout:), Bash(git pull:), Bash(git push:), Bash(gh pr list:), Bash(gh pr view:), Bash(gh pr create:), Bash(gh pr edit:), Bash(gh release create:)


Release: ship to production

Drive this project's release flow: a release is a pull request into the production tier from the tier directly below it in the chain.

This project's resolved git config — the chain, the changelog format, the tag format, and what merging production deploys:

!node "${CLAUDE_PLUGIN_ROOT}/scripts/resolve-git-config.mjs"

The release head is the tier immediately before production in the chain. On a three-tier chain that is the pre-production tier, and by the time you release, the exact code has already been live there. On a two-tier chain it is the integration tier. On a single-tier chain there is no PR to cut — only the tagging phase applies.

To get work onto the head tier in the first place, promote it with `/git:promote` (when the project has a pre-production tier); this command is only the final hop.

Semver lives in git tags. Versions are recorded as annotated git tags on the production tier, formatted per the project's configured tagFormat — the source of truth. A release also stamps the version into the changelog: the rolling accumulator section (everything promoted since the last release) is renamed to a version section and a fresh empty accumulator is opened. Because that edit can't touch a protected tier directly, it lands through the normal chain, so this command runs in three phases and auto-detects which one applies:

  1. Stamp — rename the accumulator to the new version, land it to the integration tier, promote

it along the chain.

  1. Cut — open the release PR into production.
  2. Tag — after it merges, tag production.

Run /git:release once per phase; it detects where you are and does the next step. If the project has no changelog configured, phase 1 does not apply — go straight to cutting the PR, and take the version from the argument or the tag history.

Parse `$ARGUMENTS` first: pull out flags, treat remaining free text as release-note context.

  • major | minor | patch — force the semver bump level. Default: infer from the accumulator

contents (a features group present ⇒ minor, otherwise ⇒ patch; never auto-major).

  • X.Y.Z — set the exact version explicitly (overrides the bump level).
  • --tag — force the tag phase (Phase 3) even if detection is ambiguous.
  • --draft — open the release PR as a draft.

Major-zero. While the project's latest tag is 0.x, releases run 0.1.0 → 0.2.0 → …; the default inference applies unchanged (a feature ⇒ minor, else patch). Cutting 1.0.0 is a deliberate milestone — always an explicit 1.0.0 (or major) argument, never automatic, so "never auto-major" also protects against accidentally leaving a beta.

Git-flow notes for release

  • A release is a PR, not a push: base is the production tier, head is the tier below it — it

ships that branch verbatim. The one push this command makes is the annotated tag (Phase 3).

  • The source tier is protected and is not deleted; after the merge the two tiers are identical

— no back-merge (unlike a hotfix).

  • The version stamp is an ordinary file edit — it rides the chain like any change; this command

never edits a file on a protected tier.

Phase detection

Preflight — the chain must be intact. Before any phase logic, confirm the tiers exist and line up. If either check fails, stop and report — don't stamp, promote, tag, or push against a broken chain:

  • git ls-remote --heads origin <each tier> returns every tier in the configured chain. A

missing one (a tier was deleted, or git fetch prints fatal: couldn't find remote ref …) means the chain is broken. Restoring a deleted protected branch is a manual repair — it may need to bypass the project's push protections — and is not part of this flow; surface it and let the user fix it first.

  • For each adjacent pair, git log --oneline --no-merges <lower>..<upper> is empty. Any

non-merge commit here was authored directly on the upper tier, bypassing the chain; that change isn't on the tier below, so a stamp or promotion can clobber or lose it. Call out the drift and reconcile it (cherry-pick the commit back down) before releasing.

Then run first (in parallel where possible), using the project's tagFormat to recognize tags:

  • git fetch origin --tags — sync remote refs and tags.
  • The latest version tag (base for the next-version calc), newest first by version sort. **No tags

at all ⇒ the version history hasn't been tagged yet: this run is a Phase 3 tag that must backfill every untagged version section in the changelog, not just the top one — create an annotated tag for each** (oldest → newest), all pointing at the current production HEAD (they reached production together, so they share a commit). Skipping the older sections is the exact bug to avoid: stamping a version above an untagged earlier one and then tagging only the newer leaves the earlier one permanently untagged and breaks the changelog's promise that every version section has a matching tag. Tags are always created here, never by hand.

  • The head tier's changelog — inspect its accumulator section:
  • Populated (has category groups with bullets) ⇒ this release hasn't been stamped yet.
  • Empty (just the header/comment) with a version section directly below ⇒ already stamped;

that version is this release's version.

  • git log --oneline <production>..<head> — the commits a release would ship. **Empty ⇒ nothing to

release** unless Phase 3 applies. (If empty but the tier below the head is ahead of it, the work hasn't been promoted yet — tell the user to /git:promote first.)

  • Commits on production past the latest tag (⇒ a merged-but-untagged release).
  • gh pr list --base <production> --head <head> --state all --json url,state,number — existing

release PR ([] when none; if multiple, take the highest number).

Decide:

  • Phase 1 — stamp: the head tier's accumulator is populated. The version hasn't been

recorded yet; stamp it and carry it along the chain.

  • Phase 2 — cut the release PR: the accumulator is empty with a fresh version section on

top, the head tier is ahead of production, and there is no open release PR. (The stamp has arrived; ship it.)

  • Phase 3 — tag the release: --tag is set, or production has commits past the latest tag

and the head tier is not ahead of production (the release PR merged). If the most recent release PR is MERGED and production is un-tagged, this is the case.

  • Open release PR already exists: report its URL and state; if its body is stale (the changelog

moved on), regenerate and gh pr edit --body-file. Don't open a second one.

  • Nothing to do: head not ahead of production, accumulator empty of new work, and production

already tagged ⇒ report "already released" and stop.


Phase 1 — stamp the version

  1. Compute the next version. Latest tag → bump per the arg or inference. Inference: a features

group in the accumulator ⇒ minor, else patch; never auto-major. An explicit X.Y.Z arg wins. State the chosen version and why in your report.

  1. Stamp the changelog. Cut a short release branch off the integration tier (using the project's

chore prefix). In the changelog, rename the accumulator heading to the version heading with today's date, and insert a fresh empty accumulator (with its comment) directly above it. Match the file's existing heading style rather than imposing one. Optionally lead the new version section with a one-line theme summary. Don't rewrite the bullets — they're already product-level from /git:promote.

  1. Land it, then promote. Open the release branch's PR into the integration tier with

/git:ship, then merge that PR — /git:ship only opens it, so merge it yourself (the repo's "Merge" button, or gh pr merge) and confirm the stamp has landed on the integration tier before you go on. Then run `/git:promote` to carry it along the chain (skip that when the project has no pre-production tier — the integration tier is the release head). ⚠️ If the project's deploy note for the pre-production tier says the merge deploys something, call that out.

  1. Report: the chosen version and why; that the stamp is on its way; next step: once the head

tier carries it, re-run `/git:release` to cut the release PR (Phase 2).


Phase 2 — cut the release PR

  1. Pre-flight. The release ships the head tier. Confirm git log <production>..<head> is

non-empty and that the head tier's accumulator is empty with the target version section on top (i.e. the stamp landed). If the accumulator is still populated, you're in Phase 1 — stamp first. A dirty local tree or your current branch is irrelevant.

  1. Read the version from the top version section of the head tier's changelog (it was set in

Phase 1). An explicit X.Y.Z arg still wins if given. With no changelog configured, take the version from the argument, else bump the latest tag.

  1. Build the release notes from that version section (it's already grouped by category). Fall

back to git log --merges --oneline <production>..<head> for a PR-level summary if needed. Lead with a one-line theme.

  1. Open the release PR (base production, head the tier below):

gh pr create --base <production> --head <head> --title "Release <version>" --body-file <file> --assignee @me (add --draft if asked). Always pass --title and --body-file so no editor opens. Add a label only if it exists in the repo. Body uses the release PR template below.

  1. Report:
  • The PR URL and the version.
  • ⚠️ What merging this PR does — the project's deployNotes for the production tier,

verbatim, if one is configured. If none is, say plainly that the project documented no deploy effect rather than inventing one.

  • If the release includes any of the project's flagged high-risk paths (schema migrations and

the like), call them out as the highest-risk step, and say what the rollback path is if the project documented one.

  • Next step: once it merges, re-run `/git:release` to create and push the tag.

Phase 3 — tag the merged release

  1. Verify the release landed: the latest release PR is MERGED (or --tag was passed) and

production is ahead of the latest tag.

  1. Check out the production tier and git pull --ff-only (then return to the original branch when

done). Never commit here — only tag.

  1. Determine the version: explicit X.Y.Z arg, else the top version section from production's

changelog (the stamped version), else recompute from the latest tag + the merged changelog.

  1. Create an annotated tag on the merge commit, named per the project's tagFormat, and push it:
  • git tag -a <tag> -m "Release <version>" -m "<short changelog>"
  • git push origin <tag>
  • No tags existed before this run (baseline / first release): don't tag only the top version —

backfill every untagged version section in the changelog, oldest → newest, each an annotated tag on the current production commit (they arrived together). Verify with a version-sorted tag list that the newest is still on top, then push them all (git push origin --tags).

  1. Report: the tag name and the commit it points at; offer to draft a GitHub Release from it

(gh release create <tag> --verify-tag --notes-file <file>) — create it only if the user confirms (it's an outward-facing publish). Restore the user's original branch.


Release PR template

markdown
## Release <version>
<!-- One-line theme of this release. -->
<!-- Fill from the version's changelog section (already grouped). Every "- …" needs real text;
an empty section becomes "- None." — never ship a blank "- " bullet. -->
### Features
-…
### Fixes
-…
### Chores
-…
### Deploy notes
-<the project's deploy note for the production tier, verbatim — omit the line if none is configured>
-High-risk paths in this release: <none | the flagged paths>.
-Rollback: <the project's documented rollback, if any; else "revert via a follow-up PR">.
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Keep sections tight and factual — describe what actually ships. Confirm before anything hard to reverse: promoting the stamp along the chain, opening the release PR (it gates a production deploy on merge), and publishing a GitHub Release. If a release includes a high-risk path, never bury it — surface it in both the PR body and your report.

All versions