# Sdd Ship

> Thin SDD finish pipeline: merge main into feature branch, typecheck then tests, Bugbot plus path-scoped security review, fix loop, push, create or update GitHub PR, remove git worktree. Use after sdd outside-voice passes, with sdd, or when the user says sdd-ship or ship the SDD branch.

- Skill: `0nate4/sdd-ship` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 0nate4/sdd-ship`
- Raw SKILL.md: https://api.skillmd.com/api/skills/0nate4/sdd-ship/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: 0NATE4 (https://skillmd.com/u/0nate4)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/0nate4/sdd-ship

---


# 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.md` final 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

```bash
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

```bash
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

```bash
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`:

```bash
git fetch origin "$BASE"
git merge "origin/$BASE" --no-edit
```

Prefer **merge** over rebase (preserves SDD task commits).

**On conflicts:**

1. Resolve in the worktree (feature intent wins unless base clearly correct).
2. `git add` resolved paths, `git commit` (merge commit).
3. 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:

1. **Typecheck** if `"typecheck"` exists in `package.json` scripts (`npm run typecheck`). **Stop** on failure.
2. **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.

```text
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:

```bash
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_PATH` on branch `BRANCH`
- 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:

1. Re-run Step 5. **Stop** if typecheck or tests fail.
2. 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

```bash
git push -u origin HEAD
```

## Step 9: Create or update PR

Gather context:

```bash
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:**

```bash
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/`:

```bash
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:

```bash
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 typecheck` when `package.json` defines 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

