ship · diff

git:20260907.9f8623d to git:20260907.ce50520

8 added, 5 removed. Audit A to A.

---
name: ship
description: "Ship a build — pre-flight checks, deploy, document, verify, monitor. Use when deploying code, publishing packages, or releasing."
effort: medium
model: sonnet
keywords: [deploy, ship, release, publish, rollback, production]
task_strategies: [feature]
stream_affinity: [roadmap]
argument-hint: "[target or task-id]"
group: execution
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
- AskUserQuestion
- Task
- TaskCreate
- TaskList
- TaskUpdate
- ToolSearch
disable-model-invocation: true
status: experimental
growth_stage: seed
---
# Ship
Push a build out the door. Six steps: pre-flight, deploy, document, verify, monitor, rollback. Each step adapts to the project — detects test frameworks, deploy methods, and registries automatically. Manual override is always available.
## Invocation
```
/brana:ship — detect target from context
/brana:ship bootstrap — deploy the brana identity layer
/brana:ship t-123 — ship the work from a specific task
/brana:ship npm — publish an npm package
```
## Step Registry
On entry, create a CC Task step registry. Follow the [guided-execution protocol](../_shared/guided-execution.md).
Register these steps: PRE-FLIGHT, GATE-3, DEPLOY, DOCUMENT, VERIFY, MONITOR, ROLLBACK.
ROLLBACK is conditional — only executed if VERIFY or MONITOR fails.
## Rules
- **Never auto-deploy without user confirmation.** Pre-flight ends with an explicit gate.
- **Pre-flight failure blocks deploy.** Hard gate — no override.
- **Rollback is always optional and prompted.** Never auto-rollback.
- **Project detection is best-effort.** Always offer manual override via AskUserQuestion when detection is ambiguous.
---
## Steps
### Step 0: Goal injection
Set session orientation before any checks run:
- **If task_id known:** extract `AC:` lines from task context (same pattern as `build.md` Step 0 sub-step 0). If found:
- Call `/goal "ship {task-id} — Done when: {criteria joined with ' AND '}"`.
- Write `~/.claude/run-state/active-goal.json`:
```json
{"task_id": "{task_id}", "cwd": "{git_root}", "session_id": "$BRANA_SESSION_ID", "criteria": ["{criterion1}", "{criterion2}"]}
```
The Stop hook (`goal-completion.sh`) will auto-complete the task when criteria pass.
- **If no task_id or no `AC:` lines:** call `/goal "ship {target}: all checks pass, deployed, verified"` where `{target}` is the npm package name, task subject, or branch name. No `active-goal.json` write — narrative goal only.
- Skip for `bootstrap` invocation — the goal is implicit.
### Step 1: Pre-flight — Is this safe to deploy?
Run all safety checks before touching anything external.
1. **Uncommitted changes** — `git status --porcelain`. If dirty, warn and ask whether to proceed.
2. **Tests** — detect and run the project's test suite:
| Indicator | Command |
|-----------|---------|
| `Cargo.toml` | `cargo test` |
| `pytest.ini` / `pyproject.toml` [tool.pytest] / `tests/` | `uv run pytest` |
| `package.json` with `test` script | `npm test` |
| `Makefile` with `test` target | `make test` |
| None detected | Skip with warning |
3. **Build** — does it compile/bundle?
| Indicator | Command |
|-----------|---------|
| `Cargo.toml` | `cargo build --release` |
| `package.json` with `build` script | `npm run build` |
| `Makefile` with `build` target | `make build` |
| None detected | Skip |
4. **Environment config** — check for required env vars. Look for `.env.example`, `docker-compose.yml` env sections, or deployment config files. Flag any that are unset.
5. **Task status** — if a task ID was provided (`$ARGUMENTS` matches `t-\d+`):
```bash
brana backlog show <id>
```
Verify status is `in-progress` or `done`. If `blocked` or `pending`, warn.
6. **Gate** — summarize pre-flight results and ask:
```
AskUserQuestion: "Pre-flight passed. Deploy?"
Options: ["Deploy now (Recommended)", "Abort"]
```
If any check failed, change the prompt to include the failure summary and add a "Deploy anyway (force)" option.
**If user selects Abort → stop. Do not proceed to Step 1b.**
### Step 1b: Gate 3 — Adversarial pre-merge quorum
Run after pre-flight passes and user confirms deploy, before any external action.
Uses the native adversarial-hive-mind pattern — read [`../_shared/adversarial-hive-mind.md`](../_shared/adversarial-hive-mind.md) for the spawn/collect/confidence-tier mechanics (`hive-mind_*` MCP tools are bookkeeping-only under subscription — ADR-059 — the native Agent/Task fan-out does what they only claimed to).
Spawn **3 agents in one message** (`subagent_type: "brana:challenger"`), each with a ship-specific lens instead of the shared pattern's default trio:
- **Worker 1 (regression):** What existing functionality is most at risk? Name specific files or behaviors.
- **Worker 2 (security):** What security concerns does this change introduce or expose?
- **Worker 3 (completeness):** Is the implementation done? What was intended but not finished?
Provide each worker: the diff summary, relevant changed files, and task AC (if available).
Collect (caller synthesizes — no separate consensus tool): await all 3, merge and dedup findings. Quorum threshold: **majority (2/3)**. ≥2 workers flagging the same concern = HIGH confidence (blocking). 1 worker only = OBSERVATION (informational).
**Classify every HIGH finding before asking (ADR-094 decision 7, t-3329).** A finding is
**non-overridable** when the ship *removes or disables a safety or recovery mechanism* —
a backup, a snapshot, a lock, a gate, a validation, a restore path — *whose replacement has
not shipped*. "It's a documented trade-off", "the follow-up is already filed", "the ADR
accepted it" do not downgrade it: a documented risk is still a live risk and a tracked task is
not a mitigation. Origin: the 2026-09-07 ship carried ADR-091's untracking of the backlog
ledger without its snapshot/restore half (t-3287, HIGH by 2/3 reviewers); the override was
granted on exactly those grounds and the ship's own procedure wiped the ledger an hour later.
**Non-overridable HIGH finding:**
```
AskUserQuestion: "Gate 3: this ship removes a safety mechanism before its replacement lands — {finding}. Land the replacement first?"
Options: ["Fix before deploy", "Abort"]
```
No override option is offered. If Abort → stop.
**Any other HIGH finding:**
```
AskUserQuestion: "Gate 3 raised a blocking concern: {finding}. How to proceed?"
Options: ["Fix before deploy", "Override and deploy anyway", "Abort"]
```
If Abort → stop. If Override → proceed with finding noted, and the synthesis MUST state which
concrete operational paths were checked against the accepted trade-off — ship, close, runner,
scheduler, fresh clone, branch switch in the shared checkout — not just the steady state.
Fallback if Agent/Task cannot be spawned: see `adversarial-hive-mind.md`'s fallback section (Claude runs all three roles sequentially in main context; same gate logic applies).
### Step 2: Deploy — Push it out
Detect the deploy method from project files, then execute.
**Detection order** (first match wins):
| Indicator | Method | Command |
|-----------|--------|---------|
| `main` is branch-protected with required checks (`gh api repos/{owner}/{repo}/branches/main/protection` returns `required_status_checks`) | **Tier-2 PR ship** (thebrana, t-3023) | see *Tier-2 PR ship* below, then `./bootstrap.sh` from `main` |
| `bootstrap.sh` in repo root | Bootstrap | `./bootstrap.sh` |
| `railway.json` or `railway.toml` | Railway | `railway up` |
| `Dockerfile` | Docker | `docker build -t <name> . && docker push <name>` |
| `package.json` with `publish` script | npm publish | `npm publish` |
| `Cargo.toml` with `publish = true` (or no `publish = false`) | Cargo publish | `cargo publish` |
| `deploy.sh` in repo root | Custom script | `./deploy.sh` |
| None detected | Manual | AskUserQuestion for deploy command |
**Tier-2 PR ship** (`dev` → `main` through GitHub, ADR-060 tier 2). Direct pushes to `main`
are rejected by branch protection, so the ship *is* the PR:
```bash
git push origin dev
- PR=$(gh pr list --base main --head dev --state open --json number -q '.[0].number')
- [ -z "$PR" ] && PR=$(gh pr create --base main --head dev \
- --title "ship: dev→main $(date +%F)" \
- --body "$(git log --oneline main..dev | head -40)" --json number -q .number 2>/dev/null \
- || gh pr view --json number -q .number)
+ PR=$(gh pr list --base main --head dev --state open --json number -q '.[0].number // empty')
+ if [ -z "$PR" ]; then # `gh pr create` has no --json: it prints the URL; re-list for the number
+ gh pr create --base main --head dev \
+ --title "ship: dev→main $(date +%F)" \
+ --body "$(git log --oneline main..dev | head -40)"
+ PR=$(gh pr list --base main --head dev --state open --json number -q '.[0].number // empty')
+ fi
+ [ -n "$PR" ] || { echo "no open dev→main PR found after create — stop"; exit 1; }
gh pr checks "$PR" --watch # required: validate, rust, tests — refuse to continue on failure
gh pr merge "$PR" --merge # merge commit; GitHub refuses until checks are green
git branch --show-current # must print: dev — the shared checkout never switches (ADR-094 d5)
git fetch origin main:main # fast-forward local main BY REF; refuses non-ff; touches no working tree
git merge --ff-only main # on dev, in place: dev == main now (fold the merge commit back)
./bootstrap.sh # from-main guard accepts HEAD == main's tip — no checkout needed
git push origin dev
```
**Never `git checkout main` / `git checkout dev` in the shared main checkout** — not for a
ship, not for anything. It holds every concurrent session's live untracked/ignored state, and
git silently overwrites *ignored* files when checking out any ref that tracks the same path:
that exact sequence wiped the 3199-task backlog ledger on 2026-09-07 (ADR-094). Need another
ref materialised? `git worktree add ../thebrana-<ref> <ref>`. `validate.sh` Check 74 fails on
any `git checkout main|dev` command line in the skills, rules, guide, or bootstrap.
Record: the merged PR (`gh pr view "$PR" --json url,mergedAt,mergeCommit`) is the ship
record — put its URL in the task notes / changelog entry in Step 3.
**Run the detected command.** Capture stdout and stderr — they feed into the verify step.
If the deploy command exits non-zero, report the error and skip to Step 6 (Rollback).
### Step 3: Document — Record what shipped
1. **Task update** — if a task ID was provided:
```bash
brana backlog set <id> status completed
```
2. **Changelog** — if `CHANGELOG.md` exists, append an entry:
```markdown
## [version] — YYYY-MM-DD
- <summary of what shipped, derived from git log or task description>
```
3. **Version bump** — if applicable:
| File | Action |
|------|--------|
| `Cargo.toml` | Bump `version` field (patch unless user specifies) |
| `package.json` | Bump `version` field (patch unless user specifies) |
Ask user before bumping: `AskUserQuestion: "Bump version? Currently X.Y.Z" Options: ["Patch → X.Y.Z+1", "Minor → X.Y+1.0", "Major → X+1.0.0", "Skip"]`
4. **Commit** doc changes (changelog, version bump) if any were made.
### Step 4: Verify — Did it work?
Run post-deploy checks to confirm the deploy succeeded.
| Deploy type | Verification |
|-------------|-------------|
| CLI / binary | Run `<binary> --version` or `<binary> --help` |
| Web service | `curl -sf <health-endpoint>` if URL is known |
| npm package | `npm view <package>@latest version` |
| Cargo crate | `cargo search <crate> --limit 1` |
| Tier-2 PR ship | `gh pr view <n> --json state,mergedAt` shows MERGED, `git rev-parse HEAD main origin/main` all agree, `git branch --show-current` is still `dev`, the backlog ledger's task count is unchanged from before the ship (`brana backlog stats`), then `./bootstrap.sh --check` |
| Bootstrap | `./bootstrap.sh --check` if supported |
| Docker | `docker run <image> --version` or health check |
| Custom | Ask user for verification command |
Report result:
- **Success**: "Deploy verified — [details]"
- **Failure**: "Verification failed: [reason]" → proceed to Step 6
### Step 5: Monitor — Is it stable?
This step is **advisory** — print guidance, don't block.
| Deploy type | Guidance |
|-------------|----------|
| Web service | "Watch logs for 15 min: `railway logs` / `docker logs -f <container>`" |
| CLI / binary | Run a representative command to exercise the new version |
| npm package | "Check https://www.npmjs.com/package/<name> for published version" |
| Cargo crate | "Check https://crates.io/crates/<name> for published version" |
If the representative command fails or output looks wrong, flag it and suggest proceeding to Step 6.
### Step 6: Rollback (conditional) — Undo if needed
Only execute if Step 4 or Step 5 detected a problem.
```
AskUserQuestion: "Verification/monitoring detected issues. Rollback?"
Options: ["Rollback to previous version", "Keep current deploy", "Investigate first"]
```
**If user selects Rollback:**
| Deploy type | Rollback method |
|-------------|----------------|
| Git-based (bootstrap, scripts) | `git revert HEAD` |
| Railway | `railway rollback` |
| Docker | Re-tag previous image, push |
| npm | `npm unpublish <pkg>@<version>` (if within 72h) |
| Cargo | Cargo doesn't support unpublish — `cargo yank` instead |
| Custom | Ask user for rollback command |
**If user selects "Investigate first"** — stop and hand control back to the user.
---
## Project Detection Summary
The skill builds a deploy profile on entry by scanning the project root:
```
Scan: Cargo.toml, package.json, Dockerfile, railway.json, railway.toml,
bootstrap.sh, deploy.sh, Makefile, .env.example, docker-compose.yml
```
This profile drives all 6 steps. When detection is ambiguous (e.g., both `Dockerfile` and `railway.json` exist), ask the user which method to use.