Release preparation orchestrator for the current branch. It runs your
existing pre-deploy gate, sweeps the docs, bumps the version and commits —
then stops and reports. It never pushes, opens a PR, merges, tags, or
publishes: those are yours to authorize by name.
It does not re-implement checks, and it does not review code. Review is a
separate command that must have run first.
Guardrails
- Spawn a worker and explicitly select your tool's mid tier. State the
tier on the spawn — do not omit it and rely on a default. An omitted tier
inherits the parent's tier, which is not the same thing as the balanced
one. Pick the judgment-capable tier that is cheaper and faster than your top
reasoning tier. Not the cheapest/fastest tier: on judgment work it
measurably degrades (misclassification rates several times higher). Choose by
tier, not by a vendor model name copied from this file — names drift, and
this command ships to several tools. Fall back to running inline if your tool
has no subagent mechanism.
- Escalate, never assume. Anything you cannot decide, cannot verify, or
that this spec does not cover → stop and report it to the orchestrator
(the main session). Never improvise, never widen scope, never fix a finding
you noticed along the way.
- Nothing leaves the machine. No
git push, no gh, no npm publish,
under any circumstance — not even if every gate is green. You report the
sequence; a human authorizes it.
Phase 0 — Preflight (current branch, always)
- Release the branch you are on. No branch argument, no branch creation.
- On
main → stop and ask what should be released. main is only ever
the merge target; never release it, never commit to it.
- The tree must be clean — do not commit for the user. Run
git status --porcelain. Any line at all — modified, staged, or untracked — is a
stop: list the paths and say "commit this to the branch, re-run
/branch-review, then re-run /release." Committing here would create a
commit after the review and so fail Phase 0.5 by its own rule on the very
next step; a phase that guarantees the next phase fails is not a phase.
git fetch origin; the release diff is origin/main...HEAD. Empty →
stop, nothing to release.
- Record both version numbers. Read the local version (
package.json
or this project's equivalent) and, if the project has a publish path, the
published one (npm view <pkg> version, or the registry equivalent).
Report them side by side. Local ahead of published means a version was cut
on a branch and never published — Phase 3 cannot see that gap unless you
record it here, and a worker that cannot see it will re-cut a number that
already exists.
- Print a one-line plan: branch · commit count · files changed · HEAD SHA ·
local version → published version.
Phase 0.5 — Review precondition (do not skip)
A review must have run on this branch at the current HEAD SHA.
Compare the SHAs yourself; do not settle for an answer. Run git rev-parse HEAD and compare it against the sha: line in
.claude/remember/last-review.md, which /branch-review writes. Asking the
orchestrator "did a review run?" puts the question to the one party with an
incentive to say yes, so its word is not evidence — and neither is a SHA
quoted from a chat message, which is the same claim in another costume and is
gone after a compaction or a handover. Read the file; match the two strings.
No such file, or no sha: line in it = no review, never a pass. A review
that predates this file's introduction has no record, so it does not count.
- No review, or no recorded SHA obtainable → stop: "No review at
<sha>. Run /branch-review medium (or /code-review medium) first."
- Stale — recorded SHA ≠
git rev-parse HEAD, i.e. commits landed after
the review (including fix commits) → stop and ask for a re-review. This
is what makes "all findings fixed" checkable instead of promised.
No exceptions — including the fix ledger. It is normally gitignored, so
appending to it moves nothing and this never comes up. A repo that tracks
.claude/ instead will see a ledger commit land after the review and make
it stale. That is the rule working, not a case to carve out: re-review, or
leave the ledger uncommitted until after the release.
coverage: naming any stage NOT RUN → stop. A ready from a run
that skipped the security stage is not the same fact as one that did not,
and this line is the only place the difference is visible to you.
verdict: blocked in the record → stop, even when the SHA matches.
Read that line as mechanically as the sha: one. A matching SHA proves a
review ran here; it says nothing about what the review concluded, and
leaving the conclusion to the orchestrator's recollection restores exactly
the unverified claim this file replaced. Only verdict: ready with a
matching SHA is a pass.
- Reviewed at this SHA with findings outstanding → stop. Findings are
resolved before a release is cut.
This phase runs before /release writes anything, so the docs-and-bump
commit it makes later cannot invalidate the review it just checked. That
If Phase 2's docs sweep happens to correct a line that a fix-ledger bullet
also names, that is ordinary sweep work — the doc changed with the feature,
so it was already yours to update. Do not delete the bullet. /refactor
is the only deleter, and its revalidation will drop that bullet on its next
run when it finds the finding no longer holds. Deleting it here would make
/release a second writer on state that has exactly one owner, and the whole
value of the ledger's one-append-one-delete split is that it stays readable
as a log.
Report the comparison you actually ran: recorded <sha> vs HEAD <sha>,
match yes/no.
This is the only thing guaranteeing the branch was reviewed and security
scanned, so treat a missing answer as a stop, never as a pass.
Phase 1 — Verify
Load the real checklist: locate and read the installed ship.md so
you apply its exact checks, not an approximation. If it cannot be found, run
what you can from its name and flag that the full checklist was
unavailable — never pretend it passed.
/ship — mechanical pre-deploy gate (tests, lint, build, debug
leftovers, secrets grep, migrations, docs/config sync, tree state).
Capture fresh evidence: the exact command, its exit code, and the result.
A check you did not actually run is a FAIL, never an assumed pass. Emit a
coverage row: ran? ✓/✗ · evidence · verdict. A ✗ is Blocked 🛑.
Security is not re-run here — it is stage 2 of the review, already
confirmed in Phase 0.5.
🚦 Gate
- Any Critical (failing tests, broken build) → stop, report, escalate.
- Warnings, or anything you cannot confidently decide → stop,
summarize, escalate. Do not weigh it yourself.
- All clean → continue.
Phase 2 — Docs sweep
Update what this feature actually changed, wherever those docs live in this
project — match each file's existing format, touch nothing unrelated. Use
docs/index.md when the project has one to find what exists.
- CHANGELOG.md — new entry.
- README.md — only if user-facing usage changed.
- PRD — the feature's entry / status.
- Guide / context docs — the project's standing context.
- Findings / learnings — where the project keeps them.
- Any other frequently-updated doc this change makes stale.
If a doc needs no change, say so rather than editing it for its own sake.
Phase 3 — Cut (local only)
- Version bump — pick the semver level from the change (patch / minor /
major; ask if ambiguous) and update
package.json. The local-vs-
published gap you recorded in Phase 0 is an ambiguity: if local is ahead
of published, a version was cut and never published, so ask whether to
publish that number or bump past it. Never silently re-cut a version that
already exists locally. The bump must land
on the branch, before any merge — a version committed to main directly,
or added after the merge, breaks the tag/package match.
- Commit —
release: vX.Y.Z — <summary>, including the docs and the
bump.
Then stop. Nothing else.
This release commit is the one commit allowed to land after the review, and
only because it contains docs and a version number — no code, so it cannot
invalidate a finding. It does move HEAD past the reviewed SHA, which is why
/release must not be run twice on the same branch without a re-review: the
second run will correctly stop as stale.
Report — the sequence, for a human to authorize
Print the evidence, then hand back the exact remaining steps so the
orchestrator can run them on the user's named go:
Cut ✅ vX.Y.Z on <branch> — /ship green, docs updated, release commit
made locally. Reviewed at <sha>.
Ready when you are:
git push -u origin <branch>
gh pr create into main
gh pr checks <pr> --watch — merge only on green. Every gate before
this one ran on the same machine; CI is the only differently-configured
instrument in the chain, and this is the first time it sees the branch.
A test that passes locally because of a path, a fixture, or a tool that
exists only on your box fails here and nowhere earlier. Read the exit
code off the bare command. Red → stop, fix, re-review, and start again.
gh pr merge --admin --squash --delete-branch (main is PR-protected;
owner-authorized admin merge on a solo repo). Keep --squash — gh
requires an explicit merge-method flag (--squash / --merge /
--rebase); drop it and the command will not squash-merge.
git tag vX.Y.Z on main and push the tag
- Publish if this project has a publish path (e.g.
gh workflow run publish.yml) — manual by design
- Verify it is actually live (
npm view <pkg> version, and the published
tarball's contents), not the working tree
Every exit code in this sequence is read off the bare command, including
the ones you type yourself. /ship's rule is not just for the worker: a
pipeline reports its last element's status, so gh run watch --exit-status | tail -2; echo $? prints 0 for a failed run. That has already turned a red
CI into a green reading in a real release.
Final line: Cut ✅ (vX.Y.Z — ready to push) or Blocked 🛑 with the
specific reason.
1---2name: release3description: Verify, sweep docs, cut a version — then hand the release sequence back4---5Release **preparation** orchestrator for the **current branch**. It runs your6existing pre-deploy gate, sweeps the docs, bumps the version and commits —7then **stops and reports**. It never pushes, opens a PR, merges, tags, or8publishes: those are yours to authorize by name.910It does not re-implement checks, and it does not review code. Review is a11separate command that must have run first.1213## Guardrails14- **Spawn a worker and explicitly select your tool's mid tier.** State the15 tier on the spawn — do not omit it and rely on a default. An omitted tier16 inherits the *parent's* tier, which is not the same thing as the balanced17 one. Pick the judgment-capable tier that is cheaper and faster than your top18 reasoning tier. **Not the cheapest/fastest tier**: on judgment work it19 measurably degrades (misclassification rates several times higher). Choose by20 tier, not by a vendor model name copied from this file — names drift, and21 this command ships to several tools. Fall back to running inline if your tool22 has no subagent mechanism.23- **Escalate, never assume.** Anything you cannot decide, cannot verify, or24 that this spec does not cover → **stop and report it to the orchestrator**25 (the main session). Never improvise, never widen scope, never fix a finding26 you noticed along the way.27- **Nothing leaves the machine.** No `git push`, no `gh`, no `npm publish`,28 under any circumstance — not even if every gate is green. You report the29 sequence; a human authorizes it.3031## Phase 0 — Preflight (current branch, always)32- **Release the branch you are on.** No branch argument, no branch creation.33- **On `main` → stop and ask** what should be released. `main` is only ever34 the merge target; never release it, never commit to it.35- **The tree must be clean — do not commit for the user.** Run `git status36 --porcelain`. Any line at all — modified, staged, or untracked — is a37 **stop**: list the paths and say "commit this to the branch, re-run38 `/branch-review`, then re-run `/release`." Committing here would create a39 commit *after* the review and so fail Phase 0.5 by its own rule on the very40 next step; a phase that guarantees the next phase fails is not a phase.41- `git fetch origin`; the release diff is `origin/main...HEAD`. Empty →42 **stop**, nothing to release.43- **Record both version numbers.** Read the **local** version (`package.json`44 or this project's equivalent) and, if the project has a publish path, the45 **published** one (`npm view <pkg> version`, or the registry equivalent).46 Report them side by side. Local *ahead* of published means a version was cut47 on a branch and never published — Phase 3 cannot see that gap unless you48 record it here, and a worker that cannot see it will re-cut a number that49 already exists.50- Print a one-line plan: branch · commit count · files changed · HEAD SHA ·51 local version → published version.5253## Phase 0.5 — Review precondition (do not skip)54A review must have run on this branch **at the current HEAD SHA**.5556**Compare the SHAs yourself; do not settle for an answer.** Run `git rev-parse57HEAD` and compare it against the `sha:` line in58`.claude/remember/last-review.md`, which `/branch-review` writes. Asking the59orchestrator "did a review run?" puts the question to the one party with an60incentive to say yes, so its word is not evidence — and neither is a SHA61quoted from a chat message, which is the same claim in another costume and is62gone after a compaction or a handover. Read the file; match the two strings.63**No such file, or no `sha:` line in it = no review**, never a pass. A review64that predates this file's introduction has no record, so it does not count.6566- **No review**, or no recorded SHA obtainable → **stop**: "No review at67 `<sha>`. Run `/branch-review medium` (or `/code-review medium`) first."68- **Stale** — recorded SHA ≠ `git rev-parse HEAD`, i.e. commits landed after69 the review (including fix commits) → **stop** and ask for a re-review. This70 is what makes "all findings fixed" checkable instead of promised.71 **No exceptions — including the fix ledger.** It is normally gitignored, so72 appending to it moves nothing and this never comes up. A repo that tracks73 `.claude/` instead will see a ledger commit land after the review and make74 it stale. That is the rule working, not a case to carve out: re-review, or75 leave the ledger uncommitted until after the release.76- **`coverage:` naming any stage `NOT RUN`** → **stop**. A `ready` from a run77 that skipped the security stage is not the same fact as one that did not,78 and this line is the only place the difference is visible to you.79- **`verdict: blocked` in the record** → **stop**, even when the SHA matches.80 Read that line as mechanically as the `sha:` one. A matching SHA proves a81 review ran here; it says nothing about what the review concluded, and82 leaving the conclusion to the orchestrator's recollection restores exactly83 the unverified claim this file replaced. Only `verdict: ready` with a84 matching SHA is a pass.85- **Reviewed at this SHA with findings outstanding** → **stop**. Findings are86 resolved before a release is cut.8788This phase runs **before** `/release` writes anything, so the docs-and-bump89commit it makes later cannot invalidate the review it just checked. That90If Phase 2's docs sweep happens to correct a line that a fix-ledger bullet91also names, that is ordinary sweep work — the doc changed with the feature,92so it was already yours to update. **Do not delete the bullet.** `/refactor`93is the only deleter, and its revalidation will drop that bullet on its next94run when it finds the finding no longer holds. Deleting it here would make95`/release` a second writer on state that has exactly one owner, and the whole96value of the ledger's one-append-one-delete split is that it stays readable97as a log.9899Report the comparison you actually ran: recorded `<sha>` vs HEAD `<sha>`,100match yes/no.101102This is the only thing guaranteeing the branch was reviewed *and* security103scanned, so treat a missing answer as a **stop**, never as a pass.104105## Phase 1 — Verify106**Load the real checklist**: locate and **read** the installed `ship.md` so107you apply its exact checks, not an approximation. If it cannot be found, run108what you can from its name and **flag that the full checklist was109unavailable** — never pretend it passed.110111- **`/ship`** — mechanical pre-deploy gate (tests, lint, build, debug112 leftovers, secrets grep, migrations, docs/config sync, tree state).113114Capture **fresh evidence**: the exact command, its exit code, and the result.115A check you did not actually run is a **FAIL**, never an assumed pass. Emit a116coverage row: `ran? ✓/✗` · evidence · verdict. A ✗ is **Blocked 🛑**.117118Security is **not** re-run here — it is stage 2 of the review, already119confirmed in Phase 0.5.120121## 🚦 Gate122- **Any Critical** (failing tests, broken build) → **stop**, report, escalate.123- **Warnings, or anything you cannot confidently decide** → **stop**,124 summarize, escalate. Do not weigh it yourself.125- **All clean** → continue.126127## Phase 2 — Docs sweep128Update what this feature actually changed, wherever those docs live in this129project — match each file's existing format, touch nothing unrelated. Use130`docs/index.md` when the project has one to find what exists.131132- **CHANGELOG.md** — new entry.133- **README.md** — only if user-facing usage changed.134- **PRD** — the feature's entry / status.135- **Guide / context docs** — the project's standing context.136- **Findings / learnings** — where the project keeps them.137- **Any other frequently-updated doc** this change makes stale.138139If a doc needs no change, **say so** rather than editing it for its own sake.140141## Phase 3 — Cut (local only)1421. **Version bump** — pick the semver level from the change (patch / minor /143 major; **ask if ambiguous**) and update `package.json`. The local-vs-144 published gap you recorded in Phase 0 **is** an ambiguity: if local is ahead145 of published, a version was cut and never published, so ask whether to146 publish that number or bump past it. Never silently re-cut a version that147 already exists locally. The bump must land148 on the branch, before any merge — a version committed to `main` directly,149 or added after the merge, breaks the tag/package match.1502. **Commit** — `release: vX.Y.Z — <summary>`, including the docs and the151 bump.152153Then **stop.** Nothing else.154155This release commit is the **one** commit allowed to land after the review, and156only because it contains docs and a version number — no code, so it cannot157invalidate a finding. It does move HEAD past the reviewed SHA, which is why158`/release` must not be run twice on the same branch without a re-review: the159second run will correctly stop as stale.160161## Report — the sequence, for a human to authorize162Print the evidence, then hand back the exact remaining steps so the163orchestrator can run them on the user's named go:164165> **Cut ✅ vX.Y.Z on `<branch>`** — `/ship` green, docs updated, release commit166> made locally. Reviewed at `<sha>`.167> Ready when you are:168> 1. `git push -u origin <branch>`169> 2. `gh pr create` into `main`170> 3. `gh pr checks <pr> --watch` — **merge only on green.** Every gate before171> this one ran on the same machine; CI is the only differently-configured172> instrument in the chain, and this is the first time it sees the branch.173> A test that passes locally because of a path, a fixture, or a tool that174> exists only on your box fails here and nowhere earlier. Read the exit175> code off the bare command. Red → stop, fix, re-review, and start again.176> 4. `gh pr merge --admin --squash --delete-branch` (main is PR-protected;177> owner-authorized admin merge on a solo repo). **Keep `--squash`** — `gh`178> requires an explicit merge-method flag (`--squash` / `--merge` /179> `--rebase`); drop it and the command will not squash-merge.180> 5. `git tag vX.Y.Z` on `main` and push the tag181> 6. Publish **if this project has a publish path** (e.g.182> `gh workflow run publish.yml`) — manual by design183> 7. Verify it is actually live (`npm view <pkg> version`, and the published184> tarball's contents), not the working tree185186**Every exit code in this sequence is read off the bare command, including187the ones you type yourself.** `/ship`'s rule is not just for the worker: a188pipeline reports its last element's status, so `gh run watch --exit-status |189tail -2; echo $?` prints `0` for a failed run. That has already turned a red190CI into a green reading in a real release.191192Final line: **Cut ✅ (vX.Y.Z — ready to push)** or **Blocked 🛑** with the193specific reason.