ReferenceCLI

vx ship

Ship a finished mission through vexillum's own tribunal pipeline, opening a real pull request

Ship a finished mission through vexillum's own tribunal pipeline,
opening a real pull request.

Usage:
  vx ship <task-id> [--fix] [--max-rounds <n>] [--timeout <duration>]
                    [--title <text>] [--body <text> | --body-file <path>]

Runs, in order, inside the mission's own camp: lint, tests, an adversarial
code review of the diff, and a docs check - stopping at the first step that
fails and reporting it, without pushing or opening anything. Every step
runs synchronously; "vx ship" doesn't return until the whole pipeline
has settled, there's nothing external to track afterward.

The review runs in a brand-new headless session of the harness the mission
ran on (Claude Code by default) that has none of the author's context: it
reads the diff itself with git, assumes the change is wrong and tries to
break it, and reports structured findings (file, line,
severity, action, a concrete failure scenario, sibling sites) plus the
files it actually read. When the mission has a prompt, every component the
change introduced is also judged against it, followed by any instructions the
general gave afterward through 'vx prompt' and 'vx decide' (recorded on the
task as amendments), and anything none of them required is reported as a
warning whose remedy is removing it. Any finding of severity
error or warning blocks the ship (including those that need a human
decision); info findings do not block and are added to the pull request
body. A changed file the reviewer did not report reading, an answer that
is not valid findings JSON after retries, or a timeout all fail the step -
never a pass. Findings are printed in this command's output.

By default a blocking review only rejects. With --fix, findings the
reviewer marked auto-fix are handed to a headless fixer in the camp, its
work is committed, and lint, tests and the review run again with a new
reviewer that treats the fixer's commits as unreviewed code. Findings that
need a human decision (ask-user) are never fixed: the ship is rejected
with them. The loop is bounded by --max-rounds (fix rounds, default 2);
exhausting it without a passing review rejects the ship.

  --fix                 enable the review -> fix -> review loop
  --max-rounds <n>      maximum fix rounds with --fix (default 2)
  --timeout <duration>  absolute cap on each reviewer or fixer run, e.g.
                        30m (default 20m); exceeding it fails the step

Once every step passes, pushes the mission's camp branch to the real
remote ("origin") and opens the pull request itself with "gh pr create" -
deterministically, no soldier or agent judgment decides whether or when
this happens, since it's the one vexillum action with a real, irreversible
effect outside the machine. Requires "gh" ('vx doctor' reports
whether it's installed).

The pull request is public, and its text is produced with no manual step.
The adversarial review, which reads the whole diff and the commits with no
author context, also proposes a conventional-commit style title (about 72
characters) and a short factual description (what changed and why, 3 to 8
lines), written from the diff and never from the mission prompt. vx ship uses
them as the title and the What section. When the review omits them or returns
invalid ones, the title is the subject of the branch's only commit, or of its
newest feat, fix or docs commit, or of its first commit, and What is the
commit subjects as a bullet list. The body also has the diff stat and the
top-level areas touched, a Verification line (the tribunal steps that passed,
the review rounds and fix rounds), the review's info findings under Tribunal
notes, and a one-line footer naming the mission. Whatever comes from the camp
or the reviewer is sanitized first: lines with an absolute home path, a
localhost port or something that looks like a secret are dropped, as is any
line quoting the mission prompt; reviewer text that fails the check is not
published half-cut, the derived text replaces it.

Optional overrides, only when the proposed text is not what you want:

  --title <text>        use this pull request title
  --body <text>         use this text as the What section
  --body-file <path>    same, read from a file ("-" reads standard input);
                        cannot be combined with --body

A supplied body is sanitized like everything else; the diff stat,
Verification, Tribunal notes and footer stay.

A mission already shipped can be shipped again, to push follow-up commits
onto the same open PR - "done" and "shipped" are the valid starting states,
and a re-ship still runs the full tribunal pipeline first. A shipped
mission's soldier stays open until the PR is merged, so follow-up work is
requested with 'vx prompt <task-id> <text>' (the task goes
running, then back to done) and delivered with 'vx ship
<task-id>' again. Whenever a PR for the branch already exists, whether the
task is done or shipped, ship reuses it instead of opening a second one: it
pushes to the same branch and refreshes the PR's title and description from
the new review (or from --title and --body). A PR that is already merged or
closed is not reused: ship refuses before running anything.

A soldier that rebased or amended a branch ship already pushed makes the next
push non-fast-forward. After every successful push ship records the pushed tip
on the task. When a push is rejected as non-fast-forward it reads origin's tip
for the branch: if that tip is exactly the recorded one, the rewrite is
vexillum's own work and ship retries once with a lease pinned to that commit
('git push --force-with-lease=<branch>:<sha>', the mission branch only, never
the base branch or any other ref), printing one line that says it rewrote the
PR branch and from which commit to which. If the tip is anything else (somebody
else pushed) or no push is recorded (a task shipped before this was tracked),
ship never forces: it fails and says what origin holds and how to decide -
'git fetch origin <branch>' and inspect it, or authorize one manual lease push
and ship again. Once shipped,
land the PR with 'vx land <task-id>' rather than
'vx land'-ing the camp locally.