| name | ship-it |
|---|---|
| description | End-to-end PR workflow: create PR, iteratively run reviews + fix issues + push, and let a Sonnet sub-agent decide each round whether the PR is merge-ready. Loop exits only when an external evaluator confirms all-green. Use this skill when the user says 'ship it', 'PR and fix everything', 'create PR and handle reviews', 'make it mergeable', 'submit and fix until green', 'full PR loop', or wants to go from finished implementation to a merge-ready PR in one command. Also trigger when the user asks to combine pr-all + total-review + fix-pr-comments into a single workflow. |
Make a PR merge-ready: create the PR, then loop review→fix→push with an external Sonnet sub-agent acting as the convergence judge. Loop exits only when the judge confirms every part of the merge-ready condition. Does NOT merge — the user controls the merge button.
The working agent (Opus) is doing the fixing, so asking it to also decide "are we done?" invites premature exits. A separate Sonnet sub-agent reads only the evidence the working agent surfaced this iteration, and returns MET or NOT MET with a one-line reason. That mirrors how Anthropic's /goal command uses a fresh small model — but stays inside tools we can actually invoke from a skill.
- Does NOT merge the PR — user controls the merge button
- Does NOT create additional PRs or branches — works on exactly one PR on the feature branch
- Does NOT run
gh pr merge— ever, under any condition - Does NOT create GitHub issues — never runs
gh issue createduring the workflow, not even "just one" for a batch of leftovers. It does recommend which leftovers deserve an issue and offers to file them; the user decides, in a later turn
This skill is authorized to commit and push to the feature branch during the loop. Do not ask for confirmation on those actions.
- If not already on a feature branch, create one.
- Check whether a PR already exists for the current branch:
gh pr view --json number,url,state 2>/dev/null - If a PR exists and is open, capture its number and URL.
- If no PR exists, invoke
/pr-allto commit, push, and open a PR. Capture the number and URL. - Also capture
OWNER/REPO:gh repo view --json nameWithOwner --jq .nameWithOwner
- Capture the baseline diff. Everything in it is the fix the user asked for. Everything Phase 2 adds on top is loop-introduced code, and the two are governed by different rules (see "Loop-introduced code" below).
Hold
git rev-parse HEAD git diff main...HEAD --stat
BASELINE_SHAand the stat line (files changed, +/-) as loop state — same asiteration. Report both in Phase 3 alongside the final numbers.
Initialize iteration = 0, MAX_ITERATIONS = 3.
The cap is hard. Three review-fix rounds, then stop and list the remainder in the final report. A review loop that keeps finding things is not evidence that it should keep running — past round 3 the findings are usually about code the loop itself wrote (see below), and each further round grows the diff and the risk without shrinking the original bug.
An iteration only counts against the cap if it fixed at least one finding. Rounds spent solely waiting on CI, re-running a crashed reviewer, or resolving comment threads are free — but cap those at 3 as well, so a flaky pipeline cannot spin the loop forever.
Anything not in the Phase 1 baseline diff is loop-introduced. Findings against it are handled differently from findings against the original fix:
- A finding against loop-introduced code defaults to reverting that code, not patching it. Patch it only when the revert would reopen a finding that was itself must-fix.
- Hard rule: if the fix for finding X introduces finding Y, remove the fix for X. Record X as a known limitation in the PR body. Do not fix Y. Two guards stacked to cover each other is the shape of a loop that will not converge.
- Watch for this specifically in defensive additions — watchdogs, timeouts, retries, global error listeners, fallbacks, guard clauses. These are the usual response to a "silent failure" finding, and each one adds a new runtime path that can fire when nothing is actually wrong. A backstop that can brick a healthy run is worse than the silent failure it was added to catch.
- Before adding any new runtime failure path, state in one line what it does when it misfires. If the answer is "breaks a working run", don't add it.
Findings that are real but belong to a different class of problem than the PR's stated purpose are not fixed in this loop. Record them and report them in Phase 2b — reported, not filed as issues. The deliverable is the reported bug, fixed and merge-ready — not every defect reachable from the changed files.
-
Collect findings (in parallel):
/total-reviewlocally. Phase 1 already committed and pushed, so the working tree is clean and a baregit diffwould be empty — tell it explicitly to review the PR range (<base>...HEAD, the same baseline captured in Phase 1 step 6). If its report comes back without a resolved range, or claims zero findings against an empty diff, treat that as a failed review and re-run it — never as a clean bill of health.- Fetch unresolved PR review comments:
Filter out the PR author's own comments, resolved (✅), and outdated inline.
gh api "repos/<OWNER>/<REPO>/pulls/<N>/comments" --paginate gh api "repos/<OWNER>/<REPO>/issues/<N>/comments" --paginate gh api "repos/<OWNER>/<REPO>/pulls/<N>/reviews" --paginate
-
Triage every finding, then fix. For each one, classify first:
- In-scope, against baseline code → fix it. For PR review comments that means every severity the reviewer used, nitpicks included. For
/total-reviewoutput it means theMust FixandShould Fixbuckets — itsIgnoredbucket is already-triaged and is NOT work, and re-fixing it defeats the triage. - Against loop-introduced code → revert the code that caused it (see "Loop-introduced code" above). Only patch if reverting reopens a must-fix finding.
- Out-of-scope (different class of problem than the PR's purpose) → defer, record for Phase 2b.
Never self-filter discovery — every finding gets recorded. Filtering happens in the open, never inside the reviewers:
/total-reviewstep 4b triages first, then this step decides scope. Two stages, both visible, neither hidden in a reviewer's head. - In-scope, against baseline code → fix it. For PR review comments that means every severity the reviewer used, nitpicks included. For
-
Run project tests. Revert any fix that regresses.
-
Commit and push to the feature branch.
-
Wait for CI:
gh pr checks <N> --watch --fail-fast
-
Reply to and resolve each PR comment thread that was addressed this iteration.
-
Surface evidence by running and showing the output of:
gh pr view <N> --json mergeable,mergeStateStatus,reviewDecision,statusCheckRollup gh pr checks <N> gh api "repos/<OWNER>/<REPO>/pulls/<N>/comments" --paginate --jq '[.[] | select(.in_reply_to_id == null and .position != null)] | length'
Plus the latest
/total-reviewrun'sVerdict:line verbatim (the judge is fail-closed on it — if the run didn't emit one, say so rather than paraphrasing) together with itsIgnoredandOut of Scopelists, and:git diff main...HEAD --stat # current git diff main...<BASELINE_SHA> --stat # Phase 1 baseline
The two stat lines make scope growth visible. Also list, explicitly:
- findings deferred this iteration (out-of-scope) — title + one-line reason
- loop-introduced code reverted this iteration, and which finding it had been fixing
-
Dispatch the Sonnet judge via the
Agenttool:Agent( description: "Judge ship-it convergence", subagent_type: "general-purpose", model: "sonnet", prompt: """ You are an external evaluator for the ship-it workflow. The working agent has just finished one review-and-fix iteration on PR #<N> in <OWNER>/<REPO>. Your only job is to read the evidence below and return a verdict. ## Completion condition (all four must hold) 1. `mergeStateStatus` is `CLEAN` 2. `statusCheckRollup` is **non-empty** and every entry in it is `SUCCESS`, `NEUTRAL`, or `SKIPPED` (no `PENDING`, `FAILURE`, or `ERROR`). An empty rollup means no checks ran — that is not "all green", it is no evidence. 3. The unresolved-review-comment count was actually read and is `0`. A blank, errored, or missing count is not zero. 4. The latest `/total-review` run's `Verdict:` line explicitly reports **0 must-fix**. Four categories do not block: - sub-reviewers that crashed or were unavailable (only ignore them if they failed cleanly) - the `Should Fix` bucket — worth fixing, never merge-blocking - the `Ignored` bucket — `/total-review` already cut those on the merits - the `Out of Scope` bucket (findings against code this PR's diff didn't introduce, touch, or newly make reachable), plus anything the working agent separately deferred as off-topic for this PR with a stated reason **All four conditions are fail-closed.** Each is met only by reading positive evidence for it. Missing, empty, malformed, or unfamiliar-looking output means NOT met — return `NOT MET` and say which evidence was unreadable. Never read an absent section, an empty list, or an unmatched label as a passing result: output you cannot parse is evidence of nothing, not evidence of success. Note the two senses of "out of scope" in play: `/total-review`'s bucket is about **code locality** (the diff didn't touch it), while this workflow's own deferral is about **topicality** (a different class of problem than the PR's purpose). Both are non-blocking, but they are different lists — do not let a Must Fix finding against baseline code be relabelled into either one. ## Evidence from this iteration <paste verbatim output from the commands listed above + the /total-review summary + the deferred list + the reverted list> ## Also flag (does not change the verdict) Compare the current diffstat against the Phase 1 baseline diffstat. If the diff has grown by more than ~2x, say so in your reason — the loop may be fixing itself rather than the bug. ## Response format Return EXACTLY one line: - `MET: <one-line reason confirming all four hold>` if every part is demonstrably satisfied - `NOT MET: <one-line reason naming which part(s) still fail and what evidence shows it>` Judge the deferrals too: if a "deferred, out-of-scope" item is plainly the reported bug itself, return NOT MET. Do not run tools. Do not propose fixes. Judge only what the evidence shows. """ ) -
Read the verdict:
MET: ...→ exit Phase 2. Go to Phase 2b if anything was deferred, otherwise straight to Phase 3NOT MET: <reason>→ use the reason as guidance, incrementiteration(only if this iteration fixed something), and ifiteration < MAX_ITERATIONSgo back to step 1- At
iteration >= MAX_ITERATIONS→ stop editing and go to Phase 2b
Reached when the cap is hit, or whenever findings were deferred as out-of-scope. Do not keep fixing.
- Write up each outstanding or deferred finding in the Phase 3 report — title, one-line reason it was left out, and enough detail (reproduction, source reviewer) that it could be filed as-is. Batch near-duplicates into one entry.
- Propose which of them are worth an issue — mark each entry
→ issue 推奨or→ 見送り推奨with a half-line of why. This is a recommendation, not an action. - Do not run
gh issue createin this skill run. Creating issues is never part of the workflow — end the report with a single offer ("これらを issue にするなら言って") and stop. If the user says yes in a later turn, file exactly the ones they name, then. - Proceed to Phase 3 and report the PR as capped, not as MET.
- Merge conflicts unresolvable in-iteration → surface and stop
- Required human approvals blocking
mergeStateStatus→ surface and stop (only humans can approve) - Zero new findings AND zero unresolved comments AND CI green but judge says NOT MET twice in a row → likely a condition mismatch; surface the judge's reason and stop
Language: Translate the final report (headings, labels, and prose) to match the user's configured response language from CLAUDE.md / system prompt. The template below uses English labels for reference only — render them in the user's language. Keep identifiers (PR URL, iteration numbers, command names) untranslated.
## Ship It Complete — PR ready for review
**PR**: <url>
**Iterations**: N of 3
**Judge's final verdict**: MET — <reason>
**Diff**: baseline <files>/+<a>-<b> → final <files>/+<a>-<b>
### Per-iteration summary
- Iteration 1: Fixed 5 issues (3 from total-review, 2 from PR comments)
- Iteration 2: Fixed 1 CodeRabbit nit; CI green; judge: MET
### Deferred — not fixed (issue 化は提案のみ)
- <title> — <why out of scope> — <detail / source reviewer> — **→ issue 推奨** / **→ 見送り推奨**: <half-line why>
> これらを issue にするなら言って。ぼくが立てる。
### Reverted during the loop
- <what was removed> — was fixing <finding>, but introduced <new finding>
Omit the last two sections when empty. If the diff grew more than ~2x over baseline, say so in one line and name what drove it — that is the number the user actually wants to see.
If the loop hit MAX_ITERATIONS without MET, report it as capped: the judge's last reason and what's still outstanding, as listed in Phase 2b. Do NOT merge. Do NOT re-launch the loop automatically.
- Flaky CI unrelated to the diff → note it, continue the loop; judge will eventually time out via
MAX_ITERATIONSif CI never settles - Rate limiting from
gh api→ back off and retry within the same iteration /total-reviewreturns ambiguous output → surface raw output to the judge; let it decide- A sub-reviewer in
/total-reviewcrashes or is unavailable (e.g. Codex returns no output, a CodeRabbit subagent isn't registered in this environment) → note which reviewer failed in the evidence, proceed with the remaining reviewers' output, and explicitly tell the judge that the missing reviewer should NOT count as a finding. Do not block the loop on a tool outage. SKIPPEDCI checks are treated the same asSUCCESSfor convergence (common for conditional workflows likeclaude-code-action)
claude -p "/ship-it"Runs the entire workflow in a single CLI invocation. Ctrl+C interrupts.
If the user is in an interactive Claude Code session, /goal is a built-in command that fires its own Haiku evaluator after every turn (cheaper, automatic). The Skill tool cannot invoke /goal programmatically — /goal is not in the built-ins whitelist exposed to skills. To use it, after Phase 1 completes, paste this at the prompt:
/goal PR #<N> in <OWNER>/<REPO> is merge-ready — mergeStateStatus=CLEAN, a non-empty statusCheckRollup with every entry SUCCESS/NEUTRAL/SKIPPED, an actually-read unresolved-comment count of 0, and the latest /total-review "Verdict:" line explicitly reporting 0 must-fix. Every one of those is fail-closed: missing, empty, or unparseable evidence does NOT count as passing. Should Fix, Ignored and Out of Scope never block. Per turn: collect findings (run /total-review, fetch gh api comments), fix every must-fix and should-fix finding against the original diff; for findings against code added during this loop, revert that code instead of patching it; if a fix for X introduces Y, remove the fix for X and note X as a known limitation; defer out-of-scope findings and list them in the report, each marked with whether it deserves a follow-up issue. Test, commit, push, wait for CI, reply+resolve threads, then surface gh pr view/checks output plus the current-vs-baseline diffstat. Never merge. Never run gh issue create — recommend and offer, let me decide. Stop after 3 turns and report the remainder as a written list with issue recommendations, not as filed issues.
That's the same condition the Sonnet judge uses, just delegated to /goal's machinery.
Works with both git and jj — /pr-all, /total-review, and /fix-pr-comments handle VCS detection.