SDD Ship
Announce: "Using sdd-ship — merge main, typecheck+tests, Bugbot, security if risky, push, PR, cleanup worktree."
Fully automated. No menus. No confirmation prompts unless blocked (see stops).
Invoked by sdd after SDD final and outside-voice are both clean. Do not use finishing-a-development-branch or gstack-ship for this path.
Preconditions
- SDD
code-reviewer.mdfinal passed and sdd outside-voice passed (or user explicitly overrides with "ship anyway") - On a named feature branch (not
main/master) - Working directory is the feature worktree (or branch checkout)
Standalone /sdd-ship without sdd: still run this pipeline. Do not invent an outside-voice pass here; report if it was skipped.
Step 1: Detect environment
GIT_DIR=$(cd "$(git rev-parse --git-dir)" && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" && pwd -P)
WORKTREE_PATH=$(git rev-parse --show-toplevel)
BRANCH=$(git branch --show-current)
MAIN_ROOT=$(git -C "$GIT_COMMON/.." rev-parse --show-toplevel)
Record: WORKTREE_PATH, BRANCH, MAIN_ROOT, IN_WORKTREE (yes if GIT_DIR != GIT_COMMON).
Abort if BRANCH is empty, detached HEAD, or equals base branch.
Step 2: Detect base branch
BASE=$(gh pr view --json baseRefName -q .baseRefName 2>/dev/null) \
|| git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||' \
|| echo main
Use main if detection fails. Print detected BASE.
Step 3: Clean working tree
git status --porcelain
- Commit any intentional SDD changes not yet committed.
- Restore or stash unrelated dirt. Do not ship unrelated files.
- Untracked files: leave out of commits unless part of the feature.
Step 4: Sync and merge base
From WORKTREE_PATH:
git fetch origin "$BASE"
git merge "origin/$BASE" --no-edit
Prefer merge over rebase (preserves SDD task commits).
On conflicts:
- Resolve in the worktree (feature intent wins unless base clearly correct).
git addresolved paths,git commit(merge commit).- Re-run the verification ladder (Step 5) before reviews (Step 6).
Stop only if conflicts need product judgment you cannot infer from the plan.
Step 5: Verification ladder (local CI)
GitHub Actions may be out of minutes. This step is the ship gate, not gh pr checks.
From WORKTREE_PATH, run in order:
- Typecheck if
"typecheck"exists inpackage.jsonscripts (npm run typecheck). Stop on failure. - Tests from the first matching row:
| Signal | Command |
|---|---|
package.json + vitest |
npx vitest run --exclude '**/.worktrees/**' |
package.json + "test": "node --test |
npm test |
Cargo.toml |
cargo test |
go.mod |
go test ./... |
pyproject.toml / requirements.txt |
pytest or project convention |
Stop if typecheck or tests fail on the merged result. Fix in worktree, re-run this step, then continue.
Do not skip typecheck because tests passed.
Step 6: Review gates (Bugbot always; security if risky)
After merge + verification ladder pass. Reviews run on the post-merge tree. Do not skip because Grok SDD final or outside-voice already passed.
6a. Bugbot (always)
Launch exactly one bugbot subagent (run_in_background: false, description: "Bugbot", subagent_type: "bugbot"). Repository path is WORKTREE_PATH. Do not pre-compute the diff.
Full Repository Path: <WORKTREE_PATH>
Diff: branch changes
Base Branch: <BASE>
Summarize findings: Severity | Location (file:line) | Finding.
If Bugbot fails to run: retry once per review-bugbot skill. If still blocked, stop and report error (do not push).
6b. Security review (path-scoped)
Collect changed files:
git diff --name-only "origin/${BASE}...HEAD"
Run review-security (exactly one security-review subagent, same prompt shape as Bugbot, Diff: branch changes) only if any path matches:
- Auth / session / OAuth / OTP / login
- Stripe / billing / checkout / webhooks
- SMS / phone / Twilio
- Crypto / tokens /
_crypt - Unlock / proof-link / tool gate
- SQL / migrations
- New or changed public HTTP handlers (
api/routes, inbound webhooks)
Skip security-review for copy/CSS/docs, prompt-only edits with no data-path change, and test-only diffs that do not change production code.
If skipped, record security: skipped (paths not in scope).
6c. Combine
Union Bugbot + security findings (all severities). If none: continue to Step 8 (push). If any: go to Step 7 (fix loop) first.
Step 7: Fix review findings
Dispatch one composer-2.5 fix subagent (subagent_type: generalPurpose) with:
- Work from
WORKTREE_PATHon branchBRANCH - The combined findings list (source labeled Bugbot vs security)
- Fix every finding you can verify from the diff; do not expand scope
- Re-run the full Step 5 ladder (typecheck then tests) and report results
- Commit fixes with intent-prefixed messages (
fix: ...)
After the fixer returns:
- Re-run Step 5. Stop if typecheck or tests fail.
- Re-run Step 6 reviews that had findings (always re-run Bugbot if it had findings; re-run security only if it ran this ship).
Fix loop: repeat Step 7 → ladder → reviews until both gates are clean, or 3 rounds complete. On round 3 with remaining findings, stop and list open items (do not push).
Step 8: Push branch
git push -u origin HEAD
Step 9: Create or update PR
Gather context:
git log --oneline "${BASE}..HEAD"
git diff --stat "${BASE}...HEAD"
If PR exists for this branch: update body with current summary + test plan.
If no PR:
gh pr create --base "$BASE" --head "$BRANCH" \
--title "<intent-prefix>: <short description>" \
--body "$(cat <<'EOF'
## Summary
- ...
## Test plan
- [ ] ...
EOF
)"
Use commit-message intent prefixes: feat:, fix:, refactor:, test:, docs:, chore:, perf:.
Derive summary and test plan from SDD plan, implementer reports, Step 5 ladder results, and review-gate status.
Print PR URL verbatim from gh output.
GitHub Actions (soft): Run gh pr checks once. If checks are in progress, you may wait up to 10 minutes with gh pr checks --watch. If there are no checks, or the failure is Actions billing/quota ("minutes exceeded", no runners billed), do not block ship. Record ci: skipped (no Actions / quota). Local Step 5 remains the gate. If Actions ran and failed for a non-quota reason: Stop, show the failing job, keep the worktree, still run Step 11.
Step 10: Remove worktree
Only after push succeeds and GitHub Actions did not fail for a real (non-quota) reason.
If Step 9 recorded a real CI failure, keep the worktree.
If IN_WORKTREE=yes and path is under .worktrees/ or worktrees/:
cd "$MAIN_ROOT"
git worktree remove "$WORKTREE_PATH"
git worktree prune
Critical: Run git worktree remove from MAIN_ROOT, never from inside the worktree being removed.
If not in a superpowers worktree (normal repo checkout): skip removal; report branch + PR URL.
Verify:
git worktree list
test -d "$WORKTREE_PATH" && echo "STILL EXISTS" || echo "worktree gone"
Step 11: Report
Always run this step, including on Stop (so the user gets the PR URL).
Return to user:
- PR URL
- Branch name
- Merge base used
- Verification ladder: typecheck command + pass/fail (or "n/a"); test command + pass/fail
- Bugbot rounds + final status (clean / stopped with open findings)
- Security: ran (clean / findings) or skipped (reason)
- GitHub Actions: green / skipped (quota or no checks) / failed (non-quota)
- Worktree path removed, or kept with reason (Actions failure / "n/a — normal checkout")
Stops (only these)
| Condition | Action |
|---|---|
| On base branch | Abort |
| Merge conflicts needing user product call | Stop, list files |
| Tests or typecheck fail after merge or after review fixes | Stop, show failures |
| Bugbot or security-review fails twice, or 3 fix rounds with open findings | Stop, list findings |
git push fails |
Stop, show error |
| GitHub Actions quota / no checks | Continue; record skip |
| GitHub Actions ran and failed (not quota) | Stop, show failing job; keep worktree; Step 11 |
git worktree remove fails |
Report PR URL anyway, ask user to remove manually |
Do not
- Present finishing-a-development-branch menu
- Keep worktree "for PR iteration" when local ladder is green and Actions is skipped/quota
- Invoke gstack-ship, VERSION bumps, CHANGELOG, or gstack's local review army unless the user separately asks
- Force-push without explicit user request
- Treat GitHub Actions as required while quota is exhausted
- Skip
npm run typecheckwhenpackage.jsondefines the script
Idempotency
Re-running sdd-ship on the same branch:
- Skip merge if already up to date with
origin/$BASE - Push is no-op if nothing new
- Update existing PR body instead of creating duplicate
- Skip worktree remove if already gone