# Ship

> Drive a published spec to verified, pushed completion through a resumable backlog, worktree-isolated parallel builds under a lazy-senior-dev ladder, gate-first review, a milestone reviewer, and a final adversarial teardown. Use when the user invokes /ship, asks to build out a spec autonomously, or resumes a ship run.

- Skill: `vraj-ai/ship` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add vraj-ai/ship`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vraj-ai/ship/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: vraj-ai (https://skillmd.com/u/vraj-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vraj-ai/ship

---


# Ship

`/ship` takes a spec reference and builds it, unattended, until a final adversary
says it can go — then pushes it.

It is the lean pipeline. Where `goals` gates and stops at every milestone,
`/ship` runs one builder and one reviewer per item, checks the gate before
spending either, and stops only for trouble. `goals` remains available for work
that earns the ceremony.

Start `/ship` with the spec reference. The handoff and push happen inside
`/ship`.


## Worker Roles

The Ship Orchestrator may spawn named Worker Roles one level deep through the
host's native mechanism:

- `researcher` — settle ordinary research and fact lookups.
- `builder` — complete each code item or isolated verification command.
- `reviewer` — report item and milestone findings without editing them.
- `adversary` — report the read-only T3 verdict.
- `small-task` — handle a bounded mechanical lookup or lane.

Workers never spawn. A `builder` commits only its item branch. Workers return
evidence and never write the backlog, lock, handoff, or push; the Orchestrator
alone owns those actions. Role names are host-neutral; do not use a host API,
path, or provider name as a Role identity. Use `council` only for a genuinely
contested fork, never for ordinary research, verification, or review.
**`/ship` is the code review.** Every item passes its locked gate and one
non-maker reviewer applying Ship's review rubric; the milestone gate
and T3 widen the same lenses. Running a separate review skill afterwards
re-reads work that has already been reviewed.


## Farm brief

`/ship` owns the worktree-isolated build farm. It keeps this brief and the
runner beside `/ship`. One item equals one branch and one worktree:

```text
git worktree add -b ship/<slug>/<id> CONTEXT/worktrees/ship/<slug>/<id>
  -> contributor builds to the ladder, commits
  -> ship runs the locked Verification-command
  -> green? one non-maker reviewer. red? straight back, no model spent
  -> merge to MAIN_BRANCH or report failure
```

The farm runner pins one contributor model. A model never reviews its own
code.

### The ladder

Pass this to every contributor, verbatim. It is the whole build brief.

> **Work down these rungs in order. Stop at the first one that solves it.**
>
> 1. **Necessity** — does this need to exist at all? (YAGNI)
> 2. **Codebase reuse** — is it already implemented in this project?
> 3. **Standard library** — does the stdlib solve it?
> 4. **Native platform** — can built-in features (CSS, HTML5 inputs, DB constraints) handle it?
> 5. **Existing dependencies** — use what is installed before adding anything
> 6. **One-liner** — can this be a single line?
> 7. **Minimal implementation** — only now, write the smallest thing that works
>
> **Never simplify away:** input validation at trust boundaries, error handling
> that prevents data loss, security measures, accessibility, or anything the
> acceptance criteria explicitly asked for. Lazy is not negligent.
>
> **Tests:** non-trivial logic requires at least one minimal runnable check
> (`assert`, `demo()`, or a single small test). Trivial code gets none. The
> locked Verification-command must pass. Write nothing beyond that.
>
> **No speculative abstractions** — no interface, factory, or config for a
> single call site. Fix root causes in shared code, not symptoms in callers.
> Prefer the compact diff and the fewest files.
>
> Mark deliberate trade-offs with a `ship:` comment naming the upgrade path.
> Report as: `[what you built] → skipped: [X], add when [Y].`

The ladder is adapted from [ponytail](https://github.com/DietrichGebert/ponytail)
(MIT, DietrichGebert). Interactive sessions can invoke the plugin directly;
this copy exists because worktree subagents may not load plugins.

### The review rubric

Pass this to every reviewer, verbatim, after the correctness brief. Correctness,
security, and data loss are judged first and keep their existing weight. What
follows is the quality pass, and it has three lenses.

> **over-build** — reinvented standard library, a dependency for what the
> platform already ships, an abstraction with one implementation, a factory with
> one product, config nobody sets, a wrapper that only delegates, a flag with one
> caller.
>
> **slop** — a comment restating the line below it, a defensive `try/catch` on a
> path that cannot fail, a cast that silences the compiler instead of fixing the
> type, nesting where a guard clause would return early, code whose shape does
> not match the file it was added to.
>
> **structure** — a new special case bolted into a flow that does not own it,
> feature logic living in a shared path, a bespoke helper duplicating one this
> repo already has, a file this diff pushed past the size the rest of the tree
> keeps.
>
> **One line per finding:** `<file>:<line>: <lens>: <what to cut>. <replacement>.`
>
> **A finding is admissible when it names the replacement** — a standard-library
> function, an existing symbol in this repo, a native platform feature, or
> `delete, nothing replaces it`. Name the replacement and the finding stands.
> Without one it is an opinion about taste, and taste does not block a merge.
>
> An admissible `over-build` or `structure` finding is **P1** and blocks the
> merge. `slop` is **P2**. Cap the quality pass at **5 findings**, ranked biggest
> cut first, and print `net: -<N> lines possible.` on its own line, or
> `Lean already.` for a clean item. **It goes immediately before `VERDICT:`**,
> never after `FOLLOWUPS:`: the result contract parses the last three lines, so
> a trailing net line displaces the verdict and invalidates the whole review.
>
> One runnable check per piece of non-trivial logic (`assert`, `demo()`, or one
> small test) is the required minimum and is never an `over-build` finding.
> Missing trust-boundary validation, data-loss handling, security,
> accessibility, or an unmet acceptance criterion stays P0/P1 under-build.

The rubric is adapted from [ponytail](https://github.com/DietrichGebert/ponytail)
(MIT, DietrichGebert), Cursor's `deslop` and `thermo-nuclear-code-quality-review`,
and [@elithrar](https://github.com/elithrar)'s `simplify`. It is inlined here for
the same reason the ladder is: worktree subagents may not load plugins.

### Batch input

`MAX_BATCH` defaults to 4 and is capped at 8. `/ship` marks all selected ids
`in-progress` atomically before invoking the farm. The CLI manifest is one line
per item:

```text
<id>|<branch>|<model>|<task prompt>
```

The task carries the plan/item path, exact acceptance criteria, the locked
Verification-command, `MAIN_BRANCH`, the absolute worktree path, and the ladder
above. Set `TEST_CMDS_JSON` to a complete JSON map from id to its locked
command; `TEST_CMD` is only accepted for a one-item batch. Reject any manifest
model other than the run's contributor pin.

The runner resolves its agent binary as `AGENT_BIN`, then `OPENCODE_BIN`, then
the bundled harness's default install path, then a `PATH` lookup, so any agent
CLI taking the same `run --dir --agent --model --format` flags works. The
flags alone are not the whole contract: a substitute CLI must also honour the
read-only permissions injected through `OPENCODE_CONFIG_CONTENT` for reviewer
runs, because the runner's snapshot and ref-diff check proves only that nothing
local changed — it cannot prove a reviewer caused no remote effect.
`CONTRIBUTOR_MODEL` sets the maker pin and `COUNCIL_<NAME>_MODEL` each reviewer
pin, defaulting to the pins the bundled harness profile ships. A model the
runner does not recognise needs its family declared (`CONTRIBUTOR_FAMILY`,
`COUNCIL_<NAME>_FAMILY`) so maker-is-never-checker stays provable; only the
reviewers a run actually selects constrain it. A declaration cannot reclassify
a model the runner already recognises, and under `HARDENED` the extra
adversary is checked the same way.

### Gate-first review and merge

- **Gate first.** Run the locked Verification-command before spending a model.
  A red gate is a failure on its own — report it and move on. Nothing to review.
- **One reviewer per green item**, a non-maker `reviewer` Worker Role,
  never the contributor's model. No verdict means no merge.
- Review items separately, never as a batch.
- Any evidenced P0/P1 blocks even if the reviewer mistakenly prints `PASS`.
- **Every reviewer applies the review rubric above** — the `over-build`, `slop`,
  and `structure` lenses, the named-replacement admissibility rule, and the
  `net: -<N>` close.
- `HARDENED=1` adds one read-only `council-adversary` pass on top.
- Partial success is default: merge green/PASS peers and report failed ids.
- `STRICT_BATCH=1` integrates on a temporary branch and fast-forwards
  `MAIN_BRANCH` only when the whole batch succeeds, so a late failure cannot
  leave earlier peers merged.
- `/ship` alone integrates. Workers commit only their item branch; reviewers do
  not edit; no child updates the backlog or issue labels.

Each reviewer emits:

```text
VERDICT: PASS | FAIL
FINDINGS: [{"severity":"P0|P1|P2|P3","file":"path:line","trigger":"...","wrong_behavior":"..."}]
FOLLOWUPS: []
```

For overlap, begin a no-commit merge. Keep both unique non-overlapping hunks.
For the same hunk, prefer the incoming branch only inside its declared
`files_touched`; otherwise prefer HEAD. Remove markers and run the locked
Verification-command. If still red, `git merge --abort`, mark that item failed,
and continue the wave. After a resolver edits the integration, materialize an
immutable candidate commit and rerun the review in a separate detached worktree
before committing the same tree.

### Results contract

Every batch atomically writes:

```json
{"main_branch":"...","merged":[{"name":"...","branch":"...","sha":"...","verdict":"PASS"}],"blocked":[],"failed":[],"conflicts":[]}
```

Every item writes a compact one-line `<id>.digest.json`:

```json
{"id":"...","verdict":"PASS|FAIL","files_touched":[],"tests":{"pass":0,"fail":0,"cmd":"..."},"findings":[],"followups":[]}
```

`/ship` ingests only these files, never logs. `results.json`, digests, branch
ancestry, and Git are producer truth for Phase R.

## Input

`/ship <reference>` takes an issue number, URL, or file path; fetch the spec from
that reference. If the repo has a tracker-location document, use it only as an
optional hint. With no argument, look for the most recent spec carrying the
`ready-for-agent` label — on GitHub that is `gh issue list --label ready-for-agent
--limit 1`. If no tracker is reachable, ask the user for the spec reference
rather than guessing.

Derive a stable `<slug>` from the spec's identifier (issue number + title slug,
or the filename). The slug is the resume key — pin it in `goal.md` and never
recompute it from a different source.

## State

`/ship` is the sole writer of everything under `CONTEXT/ship/<slug>/`.
Reviewers report findings; they never edit state.

```text
CONTEXT/ship/<slug>/
  goal.md            plan, locked gates, success criteria
  backlog.jsonl      runtime state
  handoff.md         resume cursor
  lock.d/owner.json
  reviews/M<n>.json  milestone verdicts
  reviews/T3.json    final adversary verdict
CONTEXT/worktrees/ship/<slug>/
  results.json
  <id>.digest.json
  *.log
```

Every backlog line has at most these 12 fields: `id`, `type`, `title`, `status`,
`milestone`, `priority`, `source`, `source_id`, `depends_on`, `acceptance`,
`attempts`, `created_by`.

Use `delivery/ship/scripts/state.mjs` for locks, validation, ready calculation, and
atomic replacement. It is the same contract `goals` uses, so a backlog written
by either is readable by both. Never append or edit the backlog in place.

Mirror backlog state to GitHub issue labels when a GitHub remote is available,
but **always resume from the local backlog** — labels are a view, never the
source of truth. Use the same `goals:*` vocabulary `goals` uses, one label per
status, and **exactly one `goals:*` label per issue** at any time. The
namespace is shared deliberately: the two pipelines share one backlog format
and one `state.mjs`, so an issue's state should not be named differently
depending on which pipeline drove it.

| backlog `status` | label |
|---|---|
| `planned` | `goals:planned` |
| `ready` | `goals:ready` |
| `in-progress` | `goals:in-progress` |
| `blocked` | `goals:blocked` |
| `failed` | `goals:failed` |
| `done` | `goals:done` |
| `cancelled` | `goals:cancelled` |

Rules:

- **Remove the old label in the same operation that adds the new one.** An
  issue carrying two `goals:*` labels is a corrupt view; if you find one,
  the backlog wins and the extra labels go.
- **Create labels on demand**, idempotently — `gh label create goals:<status>
  --force`. Never fail a run because a label is missing.
- **A label write never blocks the run.** No GitHub remote, no `gh`, no auth,
  or a rate limit means skip the mirror and keep building; record it in
  `handoff.md` rather than stopping.
- **Never read labels or milestones back as state.** Phase R reconciles from
  `backlog.jsonl`, `results.json`, digests, and branch ancestry only.
- Leave the spec's own triage label (`ready-for-agent`) alone; it belongs to
  whoever wrote the spec, not to this run.

### Milestones

Publish the plan's thematic groups as native GitHub Milestones, the same view
`goals` publishes. Milestone size has no fixed issue cap.

- **The title is the group's name; the backlog only keys it.** A backlog line's
  `milestone` is an id — `state.mjs ready` matches it against
  `milestone_cursor`, and `reviews/M<n>.json` is named for it. Pin each id's
  thematic name in `goal.md` at plan time and title the Milestone with that
  name, so the published view reads as the plan rather than as `M1`, `M2`.
- **Create or match by title, across open and closed milestones alike.** `gh`
  has no milestone command; use `gh api repos/{owner}/{repo}/milestones`. A
  resumed run whose gate already closed M1 has to match that closed milestone —
  searching only open ones makes it create a second M1, which GitHub rejects as
  a duplicate title and which would otherwise fail the run.
- **Attach an item's issue whenever the item first has one.** `/ship` opens no
  issue merely to fill a milestone; an item living only in the backlog stays
  there. That is the lean default.
- **Publishing degrades exactly as the label mirror above does**, and is skipped
  on the same conditions.

Track `goal.md`, `handoff.md`, and `reviews/*.json`. Add the rest —
`backlog.jsonl`, `lock.d/`, `CONTEXT/worktrees/ship/<slug>/`, logs, `results.json`,
`*.digest.json` — to the project's ignore rules.

## Preflight

Verify the spec resolves, the cwd is a Git worktree on a named branch,
`CONTEXT/worktrees/ship` is writable, and every configured Worker Role is dispatchable.
Record the pre-ship merge base. `MAX_BATCH` defaults to 4 and may not exceed 8.


## Phase R: resume first

Run Phase R before every other phase in every fresh session.

1. Atomically acquire `lock.d`. If it exists, stop unless its owner is proven
   stale. Never run two orchestrators for one slug.
2. If `backlog.jsonl` exists, this is a resume. Never reparse the spec.
3. Read `handoff.md`, then the backlog.
4. Reconcile every stale `in-progress` item from `results.json`, its digest, and
   `git merge-base --is-ancestor <branch> <MAIN_BRANCH>`. Never infer from chat,
   commit subjects, or elapsed time — and never from labels, which the previous
   session may have died halfway through writing.
5. Re-mirror the `goals:*` labels from the reconciled backlog, dropping any
   duplicate or contradictory label the interrupted session left behind. In
   the same pass, match each milestone by title and attach any item whose issue
   an interrupted session created the milestone without attaching.
6. Recompute ready items and enter the execution loop.

Generate one stable session id when acquiring the lock and use that same id to
release it. A same-host lock whose PID is provably dead may be recovered;
malformed or remote-host locks require human inspection.

## Phase 0: plan once

Only when no backlog exists.

1. Parse the spec into 3-7 thematic milestones and dependency-ordered `code`,
   `research`, and `verify` items. Each code item is a **tracer bullet** — a
   narrow but complete path through every layer, verifiable on its own, sized
   for one fresh context window.
2. **Wide refactors are the exception to tracer bullets.** A wide refactor is
   one mechanical change — rename a shared column, retype a shared symbol —
   whose blast radius fans across the codebase, so a single edit breaks
   thousands of call sites and no slice can land green. Sequence it as
   **expand-contract** instead. *Expand:* add the new form beside the old so
   nothing breaks. *Migrate:* move call sites over in batches sized by blast
   radius (per package, per directory), each batch its own item blocked by the
   expand, green batch to batch because the old form still exists. *Contract:*
   delete the old form once no caller remains, blocked by every migrate batch.
   Forcing this into tracer bullets makes every slice red and burns the attempt
   cap on a mechanical rename.
3. **Derive one runnable Verification-command per code item** and lock it in
   `acceptance`. The spec's Testing Decisions section is prose; read it together
   with the repo's test config (`package.json` scripts, test runner config, CI
   workflow) and write a real command that exits 0 exactly when the item is
   done — e.g. `npm test -- auth.spec && tsc --noEmit`. An item with no runnable
   gate is not ready.
4. Pin `MAIN_BRANCH`, `WORKTREE_ROOT`, the pre-ship merge base, the Worker Role roster, and the success criteria in both `goal.md` and `handoff.md`.
5. **Present the plan and its gates once, together, for approval.** Show every
   item with the command that will judge it. This is the only approval in the
   run — after it, the gates are locked and never re-asked.
6. Atomically write the backlog. Then publish the plan's milestones and mirror
   each item's initial state to its `goals:*` label, creating no duplicates of
   either. Always resume from the local backlog, never from the published view.

## Execution loop

Repeat until every item is `done` or `cancelled`.

1. Compute ready items whose dependencies are all `done`, preserving dependency
   order and the active milestone. Pass the handoff's `milestone_cursor` to
   `state.mjs ready`; readiness never crosses a milestone boundary.
2. Before spawning, flip the selected code ids to `in-progress`, atomically
   rewrite the backlog, then mirror the new state to their `goals:*` labels.
   The backlog write comes first: a crash between the two leaves a stale label,
   which Phase R corrects, whereas the reverse loses the state.
3. Spawn up to `MAX_BATCH` `builder` Worker Roles through the farm runner for
   code items. Partial success is the default: one failed item must not sink
   green peers. Each builder commits only its item branch.
4. Spawn a `researcher` Worker Role for each `research` item and a `builder`
   Worker Role for each `verify` item. Do not run ordinary research or
   verification in the parent; tests and builds are deterministic worker work.
5. Ingest only `results.json` and `<id>.digest.json`, each at most 30 lines.
   Never ingest worker or reviewer transcripts; `.log` files are for humans.
6. Rewrite the backlog and `handoff.md` atomically after every batch, then
   re-mirror every changed item's `goals:*` label. A failed source id retains
   its attempt count. Cap attempts at 2 per `source_id`, then escalate.

## Milestone gate

After all items in a milestone finish, run the cheap checks first and reject
mechanically before spawning a reviewer: no new test failures against baseline,
build passes, no conflict or TODO or stub markers, every deliverable exists.

Then spawn one read-only `reviewer` Worker Role for **integration scope only** —
whether the merged items compose, cross-item coverage gaps, and drift from the
spec, under the `structure` lens of Ship's review rubric: a special
case bolted into a flow that does not own it, feature logic in a shared path, a
helper duplicating one the repo already has. Item-level `over-build` and
`slop` were settled at the item gate and are not re-litigated here. Do not
reuse the same reviewer consecutively when the host exposes that choice, and
never use the maker's implementation. Record `PASS`, `PASS-WITH-FOLLOWUPS`, or
`FAIL` in `reviews/M<n>.json` and `last_reviewer` in the handoff. On `PASS` or
`PASS-WITH-FOLLOWUPS`, close the milestone in that same step.

**Continue straight into the next milestone.** A passing gate is not a reason to
stop and ask.

## T3: final gate

Mandatory. Spawn one read-only `adversary` Worker Role on the full cumulative
`MAIN_BRANCH` diff from the pre-ship merge base, reviewing merged paths as a
composed system under all three lenses of Ship's review rubric, and
closing with `net: -<N> lines possible.` Require `reviews/T3.json` with `SHIP`,
`SHIP-WITH-FOLLOWUPS`, or `BLOCK`.

Drain P0/P1 before completion; P2 may become labelled follow-up issues. A `BLOCK`
escalates to the human and **never pushes**.

## Delivery

On `SHIP` or `SHIP-WITH-FOLLOWUPS`, with the locked success criteria passing
after the final merge:

1. Write the session document to `$TMPDIR` and update the `handoff.md` resume
   cursor.
2. `/push-handoff` — commit and push. Invoking `/ship` is the push authority its
   Step 0 requires. Report the fetched remote SHA as proof.
3. Tracker closeout review. If `docs/agents/issue-tracker.md` exists, read the
   issues, pull requests, and commits this run produced. Post one comment on
   the parent spec or open PR using that file's commands (GitHub:
   `gh issue comment` / `gh pr comment`): SHA, T3 verdict, remaining
   follow-ups. Do not create tickets. Do not rotate `goals:*` labels. A failed
   tracker write is a report, not a reason to undo the push.

Release the lock.

## When to stop

`/ship` runs to T3 unattended. It stops for exactly three things:

- **T3 `BLOCK`** — report the P0 with its `file:line` evidence.
- **Attempt cap** — a `source_id` failed twice. Report both failures verbatim.
- **A hard blocker** — auth failure, unreachable Worker Role, unresolvable conflict.

On a genuinely contested call — two defensible designs and no evidence
separating them — do not decide silently. Stop and say: *"Contested: <the
fork>. Recommend `/council` on this before I continue."* `council` is only for
that contested fork, never for ordinary research, verification, or review.
When a finding is disputed rather than a design, spawn a fresh read-only
`reviewer` or `adversary` Worker Role. The user runs it; `/ship` resumes from
Phase R with the answer.

After at least two batches, the session may checkpoint and stop cleanly with
`Resume with Phase R`. Autonomy is multi-session, not infinite context. Keep
`handoff.md` irreducible: `main_branch`, `worktree_root`, `milestone_cursor`,
`last_reviewer`, current blocker, `updated_at`. Recompute everything else.

