war · git:20261104.8e648bd · 2026-11-04 · sha256 b0bd79244e3c42e3
war git:20261104.8e648bdA
Immutable. This exact content never changes and is served at /api/v1/blob/b0bd79244e3c42e3.
---
name: war
description: Execute a detailed multi-phase implementation plan with a team of agents — Work, Audit, Refine, then capture learnings. Breaks plan phases into GitHub issues, then per phase spins up fresh worker agents in isolated git worktrees, independent read-only auditors (severity-gated, unanimous), a serial refine/merge queue, and a write-scoped servitor that wraps phase learnings into memory; lands each phase on a working branch and opens one PR to a landing branch at the end. Use when the user runs `/war <plan>`, wants to autonomously implement a multi-phase plan or spec, or mentions WAR, gastown-style orchestration, or a worker/auditor/refinery/witness team.
---
# WAR — Work · Audit · Refine
On `/war` activation, print the following verbatim in the main chat before setup or delegation. Preserve the line breaks, emoji, curly quotes and ellipsis; output the text itself, without code fences:
```text
👁️👄👁️
“Look at me”
…
“I am the captain now”
```
You are the **Lead** (the Mayor). You orchestrate and gate; you **never implement code yourself**. WAR is a Claude-native re-implementation of Gas Town's worker/auditor/refinery/witness model built on `Agent`, the `Workflow` tool, git worktrees, and GitHub issues — no Go binary, no Dolt, no beads.
Full architecture: [references/design.md](references/design.md). Data contracts: [references/schemas.md](references/schemas.md). Gas Town lineage + tunables: [references/gastown-design-params.md](references/gastown-design-params.md). When re-baselining prompt-surface budgets (an operator act — never a worker's), read [references/budget-rebaseline.md](references/budget-rebaseline.md).
## Quick start
```
/war <plan-file> [--working <branch>] [--landing <branch>] [--campaign-ordinal <K>] [--afk] [--ace] [--config <path>]
```
`--ace` sets `run.ace = true` for this run (Lead-side, one-off override, same shape as `--working`/`--landing` — no code parser; `/war` is skill-driven). See the ace behavior in **Per phase → Audits/Refines** below. `--afk` likewise sets `run.afk = true` for this run (same Lead-side one-off-override shape) — the engine READS it at the absorb-by-citation unpark gate (#1879), so an `--afk` launch whose resolved config leaves `run.afk` false would otherwise take the interactive surface-never-unpark branch for the whole unattended run.
`--afk` sets `run.afk = true` for this run (Lead-side, one-off override over the config default, same shape as `--ace`/`--working`) — the engine reads it as the operator-presence gate on the absorb-by-citation unpark (#1879 RULING 1).
Example: `/war docs/implement/implementation_plan_A.md --working dev/planA --landing master`
## Setup (once)
1. **Load the run config (if any).** If `.claude/war/config.json` exists (or the user passed `--config <path>`), resolve it: `node ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/war-config.mjs <path> --fill-defaults`. If it exits non-zero, **stop and show the errors** (don't run with a broken config). Announce `using config from <path> (profile: <profile>)`. The resolved config supplies `agents`, `audit`, `run`, and any non-null `overrides`. With **no** config file, use built-in defaults — the `DEFAULTS` models from `war-config.mjs`, at session effort. The config is produced by `/war-room`.
2. Confirm a clean git repo with a GitHub remote and `gh` auth. Ask for the **working** and **landing** branches if not given (default working = current branch; landing = the repo's default branch). A non-null `overrides.workingBranch` / `overrides.landingBranch` wins over asking. This yields the **desired** working branch. **Crash-heal pre-flight (before `resolve-working-branch`):** scan `git worktree list` — when it shows a leftover `p*-publication` path or the desired working branch checked out anywhere unexpected, read [references/setup.md](references/setup.md) (§ Crash-heal pre-flight) and heal before resolving. Then resolve it against the launch context before use: run `bash ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/provision-worktrees.sh resolve-working-branch <desired> <slug> <date>` (the single tested owner of git-topology; never re-implement its checkout detection or branch cut in prose) and take its stdout as **the run's working branch**. When the desired branch is checked out in **any** worktree (the launch-worktree collision — un-advanceable by the refiner's push-first CAS), it returns a fresh dedicated `dev/<date>-<slug>` branch checked out nowhere — or, when a leaf branch `dev` blocks that namespace (D12, #1380), the flat fallback `war-<date>-<slug>` (same tip, ownership recorded; a resume reuses whichever owned candidate exists; a cut that still dies names the blocking ref + the `--working` remedy); otherwise it echoes `<desired>` unchanged. On both paths the subcommand first validates `war/<planSlug>` cuttability, dying actionably at Setup — naming the blocking leaf (`war` or `war/<slug>`) and the remedy (a different plan slug, or delete/rename the leaf) — before any phase launches (D14). Then bootstrap the resolved branch on origin before Phase 1: `bash ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/provision-worktrees.sh ensure-origin <resolved>` (idempotent — a branch already on origin is a no-op, never a force). **Announce** `using working branch <resolved> …` and **record** `<resolved>` in the ledger as the run working branch.
3. Detect the **declared gate**: `pyproject.toml` → `uv sync && ruff check && pytest`; `package.json` → its lint/test scripts; else **ask once** and record it. A non-null `overrides.gate` (the *declared base*) wins. Then **resolve it**: run `node ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/war-config.mjs --resolve-gate "<declared>"` and thread the **result** as the gate — this self-discovering command appends a bash-suite discovery loop so every `*.test.sh` in the repo is found and run on each invocation (re-detection is automatic; no manual re-detect step needed after an intra-phase merge adds a new suite). Never run a phase without the resolved gate. As a **belt-and-suspenders backstop**, the Workflow engine now **normalizes the dispatched `plan.gate` through the same idempotent composition at entry** (its gate composition point), so a missed pre-resolution can no longer ship a shell-blind gate — the Lead pre-resolution is the belt, the engine normalization the suspenders.
- **Opt-in docker gate (spec §4.3):** when any Dockerfile exists in the repo (basename `Dockerfile` / `Dockerfile.*` / `*.Dockerfile`, excluding `node_modules/` and `.git/` — the packaging floor's shared discovery expression, ADR 0006), read [references/docker-gate.md](references/docker-gate.md) and follow its daemon-probe → probe-build → include / trim / defer routing (`--afk` arms included) before finalizing the gate.
- **Test-floor pattern (ADR 0006 floor ⊆ gate).** Propose the run's **test-floor glob set** together with the gate — floor ⊆ gate is **one Setup decision**, so the operator confirms both at once (`--afk` takes the proposal) — though under `--afk` that confirmation is not the final word on the *value*: a proposal the sanity floor below rejects becomes a **pending proposal**, re-checked (and possibly adopted — never re-asked) at each phase launch (see the per-phase pending-proposal re-check under "Run one Workflow per phase"). Derive it from **named detection inputs**: the target's test-runner config — `package.json` (`scripts.test`, test-framework devDependencies), `vitest.config.*` / `jest.config.*`, `pyproject.toml` `[tool.pytest]` — **and** a `Glob` sample of the test files actually on disk (the convention really there, not just declared). Propose **single-star tokens** whose star is **leading** (e.g. `'*.test.ts *.test.tsx'`): under the floor's bash-`case` glob matcher `*` crosses `/`, so a **leading**-star token covers root **and** nested files, whereas a `**/`-prefixed token (`'**/*.test.ts'`) misses root-level files and a path-prefixed star (`'src/*.test.ts'`) can never match a root-level file at all. **Unknown convention → `null`** (today's hardcoded floor defaults, byte-identical). Record the confirmed value in `overrides.testPattern`; the floor always unions the gate's unconditional `*.test.sh` discovery, so no custom set can make it exceed the gate. **Non-goal:** the pattern is **never parsed out of the gate command** — it rides config, pinned beside the gate at Setup exactly as `plan.gate` does (an `--afk`-adopted value is threaded per phase, see the re-check below).
- **`--afk` sanity floor:** under `--afk`, before taking the proposed test-floor pattern, read [references/setup.md](references/setup.md) (§ `--afk` sanity floor) — the reject-to-`null` rule and the pending-proposal ledger record live there.
4. Resolve the **two memory roots** for this run (spec §4.1; the old either/or fallback is retired), **both to absolute paths at Setup**: the **local root** expands `~` to `~/.claude/projects/<proj>/memory/` (private, never committed, always creatable); the **repo root** (`docs/learnings/`) resolves against the launch checkout (read path unchanged). A non-null `overrides.learningsTarget` overrides the **repo-root path** — it is the read-path repo root (feeding the seats' `memory_query` `repo` value) **and** the Gate-2 promotion destination. Record both roots in the ledger. Thread **`memoryLocalRoot`** (the absolute local root — the servitor's *only* writable path) into every per-phase Workflow alongside the retained `learningsTarget` (the read-path repo root). **Omission producer:** when the step-5 memory probe reports memory disabled, `memoryLocalRoot` is omitted from the args and every phase's Wrap-up self-skips with a logged line (fail-open). The servitor writes *every* lesson file under the local root and routes each by `type` (§4.6); the Lead's post-servitor Gate 2 promotes the `type: project` files into the repo root per the `commitLearnings` decision.
- **Setup seed render (idempotent).** When the **resolved** repo root exists on disk and the local `MEMORY.md` is missing or carries no `[repo]`-marked rows, run once: `node ${CLAUDE_PLUGIN_ROOT}/skills/_shared/war-memory.mjs render-index --local <local root> --repo <resolved repo root>`. This projects the repo's published lessons into the local `MEMORY.md` so a fresh cloner's first `/war` (and the cloner's non-WAR sessions) sees them before phase 1 dispatches. **Runs even when `memory.retrieval` is false** — seeding is a render, not a retrieval; it is skipped only when the Node probe (step 5) reports memory unavailable (Node < 24 / no `node:sqlite`). Use the **resolved** repo root (honors `overrides.learningsTarget`), never a hardcoded `docs/learnings`.
5. Probe the memory CLI: `node ${CLAUDE_PLUGIN_ROOT}/skills/_shared/war-memory.mjs query 'probe' >/dev/null 2>&1`. On any non-zero exit (Node < 24 has no `node:sqlite` — the CLI prints `war-memory: requires node:sqlite (Node >= 24); memory features disabled`), record **memory disabled** and skip the prefetch (§below) and the post-servitor render/commit steps for the whole run. Memory **fails open** — a missing CLI never blocks a WAR run.
6. For a new run, mint `runId` with `node ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/new-run-id.mjs`; when relaunching or replaying, read [references/launch-identity.md](references/launch-identity.md). Create the run ledger `.claude/teams/<run-id>/ledger.json` (+ a rendered `ledger.md`). The ledger is the richest per-task record (SHAs, verdicts, worktree map), but **not the authoritative** resume source. On resume, the precedence is: **git branch state > GitHub issue labels > `ledger.json`**. Git wins because the refiner's push-first CAS never `--force`es a shared branch, so the integration/working branches are monotonic — a recorded merge is real iff its SHA is reachable on the branch; labels beat the ledger because they are remote-durable and human-visible (they survive a local wipe); the ledger is a **lagging view**. On resume, run the **reconciliation pre-flight** (see Resume procedure below) before reading the ledger or open issues and continuing.
## Decompose + approve — GATE (before any teammate launches)
1. Read the plan. Extract **phases** (prefer an explicit build-order/phase section, e.g. a "Build order"; else top-level sections) and propose the phase→task DAG. Also extract the plan's `## Commander's Intent` OR `## AI-Commander's Intent` section (either heading) **verbatim** and thread it as `args.intent` (string|null) into every per-phase Workflow, mapping its numbered End-state conditions to the phases that claim them (`phase.endState` — checked by the gate-audit pass). Stage each claimed condition as a row `{ condition, tag, check }` ([references/schemas.md](references/schemas.md)): `condition` is the verbatim condition text (extraction per ADR 0013/0014 — unchanged), `tag` is the condition's parsed D5 End-state tag (ADR 0044's End-state tag law: `check:` | `gate:` | `HARD at audit_sha` | `backstop:`), `check` is the executable command of a `check:`-tagged condition (`null` on every other row) — parsed from the FULL numbered condition (soft-wrapped continuation lines joined; the tag routinely sits on a continuation line), taking the backticked command span after the `check:` key and excluding trailing prose/punctuation. An untagged/bare condition normalizes to `{ condition, tag: null, check: null }` — the **judgment path**: gate-audit seats attest it from named observables, no command runs for it. The Lead parses tags, never mints them — a condition the plan left untagged stays on the judgment path (the legacy arm: a pre-tag-law plan stages every condition there and runs as before). Neither section present? Interactive: **ask at the approval gate** and transcribe the operator's answer. `--afk`: `intent = null` — the run degrades to literal plan behavior. The Lead **never** invents intent (ADR 0013); `/war-machine --afk` is the sole authoring surface allowed to (ADR 0014).
- When the plan carries a Target artifacts block (the marker is `TARGET_ARTIFACTS_MARKER` in `target-artifacts.mjs`), or the resolved config's `audit.roster` carries an evidence-type row, read [artifact-evidence-lead.md](references/artifact-evidence-lead.md).
2. Seed each task's **roster** from `audit.rosterPolicy`: `all` → every task gets the full config roster (`audit.roster`); `solo` → every task gets `[{ lens: <first config lens>, depth: 'neighbors' }]`; `auto` (default) → **you compose 1–5 seats per task from the lens catalog** (any predefined lens is eligible, plus mintable domain lenses), each with an explicit per-seat depth (`neighbors`|`deep`) and a one-line rationale for the choice — deeper/broader on high-blast-radius tasks, a lone `neighbors` seat on leaf/low-risk ones. **Seat tier, under every policy (ADR 0056, #2534):** pick each seat's `tier` the way you pick its lens and depth: `"low"` (`agents.auditor.low`, else the auditor model) for a routine seat, `"high"` (`agents.auditor.high`), lifted to the auditor tier when it ranks below it, for a seat that needs the stronger model, and state the choice in a one-line rationale. The floor: classify each seat as routine or boundary-heavy by the boundary rule in the Audit step of [references/design.md](references/design.md), give each boundary-heavy seat `tier: "high"`, and if its resolved model (its own `model`, else its tier's model) still ranks below opus, set `model: "opus"`, explicit config pins included (a `fable` tier stays). You can still hand-edit any task's roster at this gate. (`autoEscalate` still widens a lone seat's roster at runtime on a Critical/low-confidence finding — toward the auditor's `widen` nomination when it supplies a valid one (those seats on `tier: "high"`), else the default roster's lenses — unless it is `false`.)
3. Set **`requiresTest`** per task (default `true`). Set `false` only for tasks with no executable surface: pure docs/config edits, VERIFY no-ops, or prose-only contract tasks. When `requiresTest:false`, the refiner's test-floor check is skipped for that task and the post-merge gate-audit pass **auto-skips** it (no mapped tests ⇒ its HARD path is vacuous; the skip is logged, never silent). For a pure-docs task, propose a solo `{ lens: 'correctness', depth: 'neighbors' }` roster (D4) — "needs no test" does not imply "needs little review", so this stays a proposal the human approves at the gate.
- Also set **`requiresPackaging`** per task (default `true`; the refiner reads `r.task.requiresPackaging !== false`, independent of `requiresTest`). Set `false` only for a task that cannot add an image-shipped source file — pure metadata/docs edits, or a repo with no Dockerfile at all. When `false`, the refiner's packaging-floor check (`assert-packaging-in-diff.sh`) is skipped for that task; like the `requiresTest:false` skip it is **logged, never silent**.
- Thread each task's plan **`Files:`** list into the per-phase Workflow as **`tasks[].files`** (the plan's declared file paths, **not** the worker's reported diff — the diff does not exist at dispatch time). The template's first-pass worker dispatch reads it: an **all-`*.md`** task (non-empty list, every entry ends `.md`) runs its worker on the **docs** tier (`agents.worker.docs`); any non-`.md` entry — or an **absent/empty** list (the fail-safe: an undefined list never vacuously reads as docs-only) — keeps the base worker tier. Fix rounds dispatch on `agents.worker.fix` when configured, else the base worker; `--ace` on `agents.worker.ace`, else fix.
- **Done-when intake (the authoring template's done-when law made mechanical — ADR 0044).** Parse each task's `Done when:` bullet into the staged task shape as **`tasks[].doneWhen`** (string|null), alongside `requiresTest`/`deps` — the bullet's text AFTER the `Done when:` key, never the key itself, and the command inside its code span (backticks are markup, stripped exactly as the `Files:` list's are). A bullet parses as its **full bullet content**: soft-wrapped physical lines joined with single spaces until the next `- ` bullet or a blank line — the authoring template wraps its bullets, and a first-line-only parse truncates the command into a spurious `done-unmet` at merge. The template's no-command arm (`None — <basis>`) stages `doneWhen: null`, exactly like an absent bullet. A `requiresTest: true` task without a `Done when:` command is an **intake defect**: interactive runs **surface it at the approval gate** (the operator supplies the command or re-rules `requiresTest` — a real ruling, never silently defaulted); under `--afk` the Lead **refuses dispatch** (a hard stop before any teammate launches — the Lead never invents an acceptance command). **Legacy arm (checked first):** a plan with no `Done when:` bullets anywhere stages unchanged and the intake-defect rule does not fire — every task's `doneWhen` is `null`, dispatch proceeds, and downstream prompts stay byte-identical (the absent ⇒ `''` pattern).
- when a staged task's `Files:` rewrites a doc that restates machine-source-derivable facts (config/manifest/enum/version slot), read [references/touched-doc-accuracy.md](references/touched-doc-accuracy.md) — the guard / de-mirror / explicitly-defer trichotomy the plan must already show for each such fact (touched-doc accuracy duty, ADR 0025 amendment; a silent restatement is a plan defect to surface at the approval gate).
4. **Backstop extraction — the Lead is the single normalization point (spec §4.4).** Parse the plan's `## Deferred validations (backstops)` section (or its AI-declared variant) into `{ check, why, runner, source: 'plan' }` entries — an entry line that does not parse into the three fields is **carried whole as `check`, never dropped**. Merge in any Setup auto-recorded entries (§ step 3 docker-gate, `source: 'auto'`). Mark `aiDeclared: true` on the plan entries **iff** the heading was the AI-declared variant (`## Deferred validations (backstops — AI-declared)`, ADR 0014 provenance — an AI-declared waiver is never later surfaced as operator-ratified). Thread the merged result as **`args.backstops`** (array|null of `{ check, why, runner, source, aiDeclared? }`) into every per-phase Workflow; the Workflow passes it through to `handoff.backstops[]` untouched **save one exception** — it appends a deduped `source: 'auto'` entry for any **baseline gate debt** it classifies mid-phase (spec §8; see the Checkpoint's `gate_failed` routing). Lead-normalized `source: 'plan'`/`source: 'auto'` entries are never rewritten. A legacy plan with **no** backstop section → `args.backstops = null` **and** the phase report notes "no backstop section (pre-ratified-backstop plan)". Interactive runs **ask at the approval gate** to confirm the extracted backstops; `--afk` takes them as parsed.
5. **Assemble, thread, and record the run's adjudication set (spec §4.A, D3).** Assemble the run's adjudication set from (1) the plan's red-team report `## Adjudications` rows (`docs/red-team/<plan-slug>.md`, machine-readable block) — the existing producer, now instructed on the /war side too — and (2) **every scope finding you adjudicate at this gate** — especially a known spec delta routed to a follow-up issue — recorded **at the moment of adjudication** as a preformatted string row naming the delta, the ruling, the route (`#<issue>` where filed), and the adjudication moment — and (3) **the Checkpoint's ask rulings**: each parked ask the operator rules at the strike-list ruling gate (`## Checkpoint`) is minted as an adjudication row there and accumulates into the set threaded to every subsequent phase. Thread the assembled set as **`args.adjudications`** (array|null) into **every** per-phase Workflow of the run; record each row in the run ledger (the `ledger.json` top-level `adjudications` key — [references/schemas.md](references/schemas.md)). Interactive runs present the rows at the approval gate alongside the DAG (next step); `--afk` self-adjudicates and records without waiting (the standing afk posture). No adjudications ⇒ omit the arg — every prompt stays byte-identical to today. **Provenance discipline:** a row records a real ruling made at a named moment — the Lead never synthesizes a row to smooth over an unruled delta (spec constraint 6) and a row is **never mined from arbitrary prose** — rows come only from the three producers above; a row re-scores a known, already-routed delta and never waives a gate, floor, or backstop (ADR 0017).
6. Present the DAG to the user as an issues preview **and any per-phase Workflow patches** you intend to inject. The roster proposal prints each seat's lens, depth, tier and resolved model and effort (its own `model`/`effort`, else its tier's, with a high tier below the auditor model shown as the auditor tier), so the operator sees each panel's models before launch. **Wait for approval/edits.**
7. On approval, run `bash ${CLAUDE_PLUGIN_ROOT}/skills/_shared/gh-preflight.sh "<overrides.ghUser>"` (the gh-account preflight — asserts the active `gh` account equals `overrides.ghUser`, switching + re-verifying on drift, failing loud on an unrecoverable mismatch; empty/unset `ghUser` ⇒ no-op exit 0) **before the file-epics batch**, then file **all phase epics up front** (labels `phase:N`, `status:todo`) so the full scope exists before any teammate launches. Run the same preflight again **before each per-phase sub-issue batch** and break that phase into **task sub-issues just-in-time** at its start (so later phases absorb learnings/drift). Record everything in the ledger.
8. **Submodule router (Increment 2).** After extracting the DAG, call `submodulePaths(repoDir)` (from `skills/_shared/provision.mjs`) to get the declared submodule paths from `.gitmodules` — when any task's target overlaps one, read [references/submodule-flows.md](references/submodule-flows.md) (§ Submodule router): task classification + paired gitlink-bump proposals (human-approved, explicit `target repo` tags), the base-branch resolution order, the `/red-team`-time reachability precondition, and the `--afk` un-owned-submodule refusal.
## Run manifest (telemetry)
A **fail-open bookkeeping record** the Lead accumulates as the run proceeds — a sibling of the run ledger, but **telemetry, not a correctness record**: it is **never** read on resume. `/war-review` consumes it post-run to render the run's cost/effort + friction report (the closing-handoff step — see `## Finish`). Full field reference: [references/schemas.md](references/schemas.md).
**Where.** `.claude/war/runs/<runId>.json` under the **main checkout** — resolve the anchor exactly as the survey manifest does (never the invoking worktree's own `.claude/`, which a per-worktree session would strand):
```bash
MAIN=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
# manifest: $MAIN/.claude/war/runs/<runId>.json
```
**Stamps — fail-open, never resume input.** Initialize at run start (the top-level fields: the field table in [references/run-manifest.md](references/run-manifest.md)), stamp each phase at launch and on return, and stamp the run's `endedAt` at the end; every `startedAt`/`endedAt` is a real clock read at the stamped boundary, never a placeholder. Every write is **best-effort** — a failed write logs **one** line and the run proceeds unaffected; bookkeeping **never** blocks a run, and the manifest is **never** resume input (the resume ordering git > issue labels > `ledger.json`, [ADR 0008](../../docs/adr/0008-git-is-the-resume-source-of-truth.md), is untouched). When naming the file (`runId` form, untracked-ness) or stamping a phase boundary — the exact per-stamp field set, the harness-surfaced `workflowRunId`/`transcriptDir` capture rule (the Workflow launch envelope, never the return object), and the binding MUST-carry list — read [references/run-manifest.md](references/run-manifest.md); when a phase is relaunched, read [references/run-manifest.md](references/run-manifest.md) (§ Relaunch: re-stamp `workflowRunId` + `transcriptDir` together, archive the died attempt).
## Per phase (in DAG order)
**Prefetch prior lessons (spec §4.5; skipped when `memory.retrieval` is `false` or memory was disabled at Setup — those are the *only* skips).** The Lead may **not** elect to skip prefetch for its own context budget, time, or convenience: the query output is threaded into `args.memory` programmatically by the same step that assembles the rest of the phase args, so it **never needs to pass through the Lead's context**. (The fail-open clause below covers a genuine prefetch *failure* — a non-zero CLI exit — never a deliberate omission.) Immediately after this phase's just-in-time task decompose, in the same Lead Bash context, run **one batched** `node ${CLAUDE_PLUGIN_ROOT}/skills/_shared/war-memory.mjs query --queries <file> --local <local root>` (the `--queries <file>` format is JSONL — one `{"label":…,"text":…,"seat"?:…,"topK"?:…,"budget"?:…}` object per line; a plain-text line-per-query file is not JSONL and dies at the parse, #1408) (always pass `--local` — the Setup-resolved local root; the CLI never guesses a root from the cwd, so an un-flagged prefetch walks an empty corpus and logs nowhere; add `--repo <resolved repo root>` when the repo root resolved, so prefetch reads the repo's published lessons too — retrieval keeps failing open, and an absent repo root simply omits the flag) covering every prospective seat: per task, one worker query (`<task title> <plan slice> implementer pitfalls`) and one auditor query per roster seat (`<task title> <plan slice> <lens> <lens catalog line>`); one servitor query for the phase (all task titles + slices + "memory dedup capture"). The refiner gets none. Thread the emitted blocks into the Workflow as `args.memory` (`{ byTask: { <task-id>: { worker, seats: { <lens>: block } } }, servitor }`) — the template concatenates them like `intent`. **Retrieval fails open:** any prefetch failure ⇒ omit `args.memory` (empty ⇒ byte-identical prompts) + one ledger line `memory: prefetch failed (<reason>) — phase ran lesson-less`.
Run **one Workflow per phase** from [assets/workflow-template.js](assets/workflow-template.js) (dispatched via a run-scoped **staged copy** — see the staging step below), passing the approved phase DAG + config (incl. `learningsTarget`, `memoryLocalRoot`, `memory` from the prefetch above, **`agents` / `audit` / `run`** from the resolved run config, `overrides.ghUser` threaded as `args.ghUser` (default `''` — the gh-preflight no-op), and this phase's **resolved** `overrides.testPattern` — threaded into `args` like the resolved gate, so the template reads `plan.gate` / `plan.testPattern`) as the Workflow `args`. When launching under a campaign, read [references/sweep-exclusion.md](references/sweep-exclusion.md) (it builds `args.sweepExclude`). Thread `args.finalPhase` at every launch — `true` on the plan's last phase, `false` on every other (absent reads as final: the terminal pass files instead of carrying). Thread the prior phase return's `carriedPhaseClose` verbatim as `args.seededPhaseClose` (landed or held — see [references/resume-and-recovery.md](references/resume-and-recovery.md)). **Per-phase pending-proposal re-check:** when the ledger carries a pending rejected `testPattern` proposal (the `--afk` sanity floor's fallback note) and the value to thread is still `null`, read [references/setup.md](references/setup.md) (§ Per-phase pending-proposal re-check) before threading — adoption is monotonic and validator-gated there; otherwise thread the resolved value unchanged. **Stage the per-phase script first (ADR 0037 — per-phase dispatch identity).** Resolve the run's staged-scripts directory `$MAIN/.claude/war/runs/<runId>/` — the per-run directory **sibling of the run manifest** `<runId>.json` (same minted `runId`; see [references/launch-identity.md](references/launch-identity.md)), resolving `$MAIN` with the **same main-checkout anchor idiom the Run-manifest section documents** (no new anchor code); its untracked-ness **rides the existing `.claude/` `ensure-exclude`** the provisioning step writes — no `.gitignore` change. Then run the **landed** stager: `node ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/stage-workflow.mjs ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/workflow-template.js $MAIN/.claude/war/runs/<runId> <planSlug> <phase.id> [<campaignOrdinal>] [--force] [--args <file>]` — the `<campaignOrdinal>` comes from the `/war` invocation's `--campaign-ordinal <K>` **when present** (absent ⇒ omit it, and the `c<K>` basename segment is simply dropped; `/war` never scans campaign ledgers to derive its ordinal). It prints the staged **absolute path**; **dispatch that printed path as the Workflow `scriptPath`** — never `assets/workflow-template.js` directly. **Write-if-absent:** a `resumeFromRunId` journal replay **reuses** the existing staged script byte-untouched (approved injected stages and journal-replay stability survive; an existing file over the `scriptPath` cap is refused instead, never reused); a deliberate propagation of a shipped-template edit passes `--force` (a fresh, comment-stripped substitution). **Over-size args ride the staged script (`--args <file>`).** At real campaign size the assembled phase args cannot ride the Workflow tool call — the measured class is ~104.5 KB (`args.memory` blocks + verbatim plan slices + the Commander's Intent). For that class, write the assembled args JSON to `$MAIN/.claude/war/runs/<runId>/args-p<phase.id>.json` (a **sibling of the staged script**, untracked via the **same existing** `.claude/` `ensure-exclude` — no new ignore machinery), pass `--args <that file>` to the stager, then dispatch the staged script with **no** Workflow `args` at all: a dispatched `{}` is truthy and would beat the staged script's embedded `EMBEDDED_ARGS` fallback. Dispatched args, when passed, **always win** — the embedding is strictly the absent-args fallback, so small-payload launches are unchanged. **Decision rule:** campaign phases **default to `--args`** (assembled campaign args always carry the memory blocks plus verbatim plan slices — the measured over-size class); a small hand-built launch may stay inline. **Comment strip, size floor, resume and restage (#2099, ADR 0037):** the stager strips full-line comments from the staged copy and refuses one over the Workflow tool's `scriptPath` cap; when the stager exits non-zero, a restage is needed, an `--args`-staged script is resumed, or a resume dies at entry validation, read [references/staged-script.md](references/staged-script.md). Optionally (fail-open telemetry) record the staged path in the run manifest's phase-launch stamp — **never** resume input. The launch **must** supply `runId` and numeric `phase.id`, including explicit-path and empty-task launches. Thread the minted ID in the assembled `args` (inline or in the `--args` file) and supply `planSlug`/`worktreeRoot` when task paths need derivation (detailed reference: [references/schemas.md](references/schemas.md) "Provisioning args"). Missing identity or required derivation inputs cause `held:workflow-error` at entry, before any agent is dispatched. Explicit `branch`+`worktree` waives only path derivation (`planSlug`/`worktreeRoot`), never `runId` or numeric `phase.id`. A tasks-bearing launch that omits `plan.file` is refused the same way (`requires plan.file`, #1430 — zero agent spawns). The args provenance floor (#1413, D6-recalibrated) likewise screens the assembled `intent`/`backstops`/`adjudications` at entry for cross-plan leaks — the refusal contract, token derivation, and exemptions are canonical in the template's `ownTokens`/`foreignIds` region (summarized in [references/schemas.md](references/schemas.md)'s provenance-floor row); each floor applies only when its arg carries scannable text, so an intent-less launch stays legal. **Args-complete preflight (launch floor — run after assembling the args, before every dispatch).** Run `node ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/assert-args-complete.mjs <args-file>` (or pipe the inline args JSON on stdin) — it extracts the template's fallback-free prompt interpolations and checks the assembled args against them. Relay each `warning:` line it prints to the operator: a task with a boundary signal and no seat at opus or above (ADR 0056). A warning never blocks dispatch. For the inline class (no `--args` file exists), the safe route is still a file: write the assembled args to `$MAIN/.claude/war/runs/<runId>/` (the sibling directory the staging step already resolves) and pass the path; if piping instead, feed stdin via a quoted heredoc (`<<'JSON'`), **never** a single-quoted shell literal — the assembled args embed the Commander's Intent and plan-slice prose, which reliably contain apostrophes that break out of single quotes. Dispatch only on exit 0; when it exits non-zero or you injected an approved stage into the staged script, read [references/staged-script.md](references/staged-script.md). **Do not free-author orchestration** — use the template; only inject stages the user approved at the gate. The shipped template accepts `args` as either a plain object or a JSON string (it auto-parses strings), and auto-namespaces all agent seats under the `work-audit-refine:` prefix — override by setting `args.agentPrefix` — so the Lead no longer needs manual namespacing or args-inlining workarounds. The Workflow:
- cuts `integration/phase-N` off the working branch;
- **Works** — fresh `war-worker` agents (one per task) in per-task worktrees off the integration tip, wave by wave (barrier between dependency waves);
- **Audits** — independent read-only `war-auditor` seats review the pinned SHA, one seat per entry in the task's **roster** (1–5 distinct-lens seats, each at its own depth). **Critical/Major block**; every **Minor/Nit routes by auditor-owned disposition** (ADR 0013) — `absorb` (fixed in-phase by the per-task ace or phase-close sweep when `phaseClose:true`; a fully specified one defaults to `absorb`, `phaseClose:true` outside the task diff), `follow-up` (a `war-followup` issue — needs a `barrier` tag and why it is not absorbable), `note` (phase report + servitor feed, never an issue), or `ask` (a decision-shaped Minor/Nit only the operator can rule — mandatory `question` + `fork`; parks on the return's `asks[]` and is ruled at the Checkpoint strike-list gate, never filed unruled, never demoted by machinery); omitted → Minor becomes follow-up, Nit becomes note, `ask` is never a default; approval **unanimous on one SHA** (re-confirm when HEAD moves) — unanimity needs no odd parity, even seat counts are legal. A split → **one rebuttal round** → resolve, fix or escalate (**rebuttal first, then fix round when all surviving blockers have a suggested_fix**; a fix-less survivor escalates; a mandate conflict preserves an operator `ask` while held; a ruling and re-audit precede approval). When composing a roster, read [references/design.md](references/design.md) (§4 step 3);
- **Refines** — `war-refiner` rebases each approved task onto the integration tip, re-runs the gate, and merges **serially** (the queue). A gate/audit failure routes a batched `FIX_NEEDED` to a fresh fix-worker on the same worktree (≤ `round_limit=6`, then escalate `audit-blocked`);
- **`--ace` (default on via `run.ace`; every preset inherits it on).** With `run.ace`, an **approved** task with **zero blockers** and **≥ 1** `absorb`-disposition finding (legacy `autoFixable:true` reads as `absorb` for one release — deprecated) gets a pre-merge ace **batch** fix (one batch commit, on the task's **absorb budget**, never `fixRounds`) + a panel **re-audit at the new SHA**. The per-task ace serves only non-`phaseClose` absorbs passing the **narrowed** release-slot backstop (`plugin.json`/`marketplace.json` refused — README and other shared files are no longer refused); `phaseClose:true` routes to the **phase-close queue** instead. If the re-audit re-approves, the polished tip merges; on regression the batch enters the bounded **ace bisection** ladder (canonical: `aceBisect` in `workflow-template.js`) — named culprits excised, the remainder re-applied as ONE subset (blind halving only on ambiguous attribution), subsets serial at the tip, depth ≤ 2, same-file findings never split; each subset commit charges one `absorbRounds` slot, carries the `Ace-Subset:` + `Ace-Charge:` trailers, and its dispatch preflights the bisection range for that trailer (never the tip alone). Failed subset tips are **forward-reverted** (`git revert`, never `reset --hard`), only finally-failing subsets demote (subsets still queued at a spent budget ride to the sweep — logged, by design), salvaged subsets merge, and the **originally-approved work still lands** — ace **never** turns a mergeable task into a hold or escalate. A listed row in a file another task, plan or the release slot owns goes to the sweep before dispatch, and polish fixes sibling sites only in its own files and the listed rows' files ([fix-round-doctrine.md](references/fix-round-doctrine.md)). **Budget-bounded re-entry (ADR 0013 amendment).** A fresh `absorb` born at a WAVE-SIDE re-audit (plain, bisection-subset, or a re-entry batch's own) dispatches another ace-style batch on the same machinery (canonical: `aceReentry`) while `absorbRounds < run.absorbRounds` — that **absorb budget** is those three sources' SOLE bound. A fourth source, the merge-slot pin-transfer **mismatch** re-audit, never re-enters: its absorbs route straight to the sweep (`routeReauditMinors`' `noReentry` opt), never budget-gated. Budget spent ⇒ `phaseClose: true` → sweep (a discarded sweep carries the queue on a non-final phase, demotes to `follow-up` on the final); a forward-reverted finding never re-enters; every demotion logged. **Durable-record rule (D3):** absorbed findings are recorded via the ace **commit message** (citing each finding's title + rationale) and the `aced` list in the phase report — **not** as GitHub issues. **Residual rule:** a regressed or ineligible absorb **demotes one step** (→ `follow-up` with a `DEMOTE_REASONS` prefix, logged; a fileless absorb takes its severity default). With `run.ace` off a defaulted absorb routes `phaseClose:true` to the sweep; the sweep and the terminal pass run regardless. Only `follow-up`-routed findings file as `war-followup` — zero unrouted findings on every exit path, nothing drops silently.
- **Sweeps (phase-close, ADR 0012)** — after the land decision is computed and before the land dispatch, iff the phase **would land** and the `phaseCloseQueue` is non-empty: one worker in a `p<N>-polish` worktree cut at the post-merge integrated tip fixes ONLY the queued findings and each sibling site of their defect class (one commit, gate green, no version-slot literals, no ad-hoc seam hunting), a **full default-roster panel** re-audits the polish SHA, the refiner merges it at the serial queue's tail, then the one-hop terminal pass runs — anything else **discards** (fail-open: polish branch + worktree left in place, queue carried on `carriedPhaseClose` on a non-final phase or demoted to `follow-up` on the final, the pre-polish tip lands unchanged);
- **Lands** — `war-refiner` merges `integration/phase-N` → working `--no-ff` (one phase commit) and pushes working;
- **Wraps up** — once the phase lands, `war-servitor` (write-scoped to the threaded absolute `memoryLocalRoot` — its only writable path) records durable learnings to the local memory root;
- returns `{ landed, escalated, minorsFiled, asks, aced, notes, landResult, servitorResult, auditLog, landDecision, carriedPhaseClose, handoff? }` — `landDecision` ∈ `landed` | `held:escalation` | `held:nothing-merged` | `held:land-failed` | `held:phase-incomplete` | `held:workflow-error` | `held:submodule-pr`; `servitorResult` is null unless the Workflow landed the phase itself; `handoff` (the machine-readable debt map the next phase's decompose reads) is present on `landed` **and** `held:escalation` (degraded), omitted on `held:workflow-error` and the other holds.
Then run `bash ${CLAUDE_PLUGIN_ROOT}/skills/_shared/gh-preflight.sh "<overrides.ghUser>"` **before this per-phase gh-write batch** (this write executes under `## Per phase`, *before* the `## Checkpoint` floor's own embedded preflight, so it needs its own — without it an account flip here silently drops the epic label/comment), then update issues + ledger (including the phase-level `handoff` field, written by the Lead from the Workflow return), and **mirror the phase report + escalations as a comment on the phase epic issue**.
**Retired-token sweep (#930):** when a phase lands (a phase you completed manually counts; a held phase gets no record line), read [references/retired-token-sweep.md](references/retired-token-sweep.md), run the sweep, and write its `retired-token sweep:` record line.
**Lead evidence bindings (phase-close + Gate 2; ADR 0041).** Close-out and publication claims follow the audit evidence-precedence doctrine's four claim-shape ladders — `content-at-pin`, `execution`, `history`, `authority` — and its floor rules: a working-tree or done-report reading is **never the top rung**, and prefetched lessons are priors, **never evidence**. Skeleton only here — the full ladders live on `agents/war-auditor.md`; the doctrine record is ADR 0041. Three bindings: (1) evidence for close-out claims is gathered **at the plan's branch tip in a dedicated worktree**, **never the main checkout** — the just-landed-tip reads in the retired-token sweep's reference and the transient publication worktree below are this binding's instances; (2) remote truth comes from `ls-remote` (ADR 0008 — cited, not restated); (3) a claim the Lead threads into a dispatch prompt becomes **rung 1 of `authority`** for the receiving agent, so the Lead grounds it at `content-at-pin`/`execution` first, or marks it **unverified**.
**Post-servitor publication (Gate 2, spec §4.6; skipped when memory was disabled at Setup).** The servitor wrote every lesson under the absolute local root; the Lead is the **sole repo-root writer**, and it publishes via a transient publication worktree (the post-checkout-guard means no working-branch checkout exists in the launch session — a working-branch commit there is impossible). On a landed phase, in order:
0. **Entry heal scan.** Scan `git worktree list` for a leftover `p*-publication` worktree (a prior crash): clean ⇒ `bash ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/provision-worktrees.sh remove-publication-worktree <path>`; dirty ⇒ escalate to the operator (same clause as Setup step 2's crash-heal pre-flight).
1. **Reconciliation assertion (D), before any publication work.** Every path in `servitorResult.files_written` must be **absolute and** under the absolute `memoryLocalRoot` (prefix check). Any stray path ⇒ **fail loud**: record it in the phase report and escalate — never silently accept or drop it.
2. **Promotion (I — copy-with-marker).** Select the `type: project` files from the reconciled set. With `memory.commitLearnings` on and a non-empty selection:
- Provision a transient publication worktree on the working branch: `bash ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/provision-worktrees.sh ensure-publication-worktree <worktreeRoot>/<runId>/p<N>-publication <working>`.
- Copy the selection into `<pub>/docs/learnings/` (honoring a non-null `overrides.learningsTarget` as the repo-root path). A same-slug repo file is **overwritten** — overwrite-on-promote is the recurrence-update mechanism.
- Run `node …/war-memory.mjs lint` over that directory in the publication worktree. Publication **fails closed**: a lint-flagged file is **not** promoted, stays in the local root, and is reported — never dropped; if every file is flagged ⇒ no commit, `remove-publication-worktree`, done.
- Commit `docs(learnings): phase N` in the publication worktree, **plus the CLAUDE.md pointer duty** (below, same commit).
- **Pre-push staged-file check (never skip).** **Refresh first:** `git fetch origin <working>` in the publication worktree — remote-tracking refs are only as fresh as the last fetch, and an unrefreshed left boundary would condemn (and revert) commits origin already has. On a non-zero fetch exit: do not probe, do not push — escalate, leaving the worktree in place for inspection (the flow's standing posture). Before pushing, enumerate **every unpushed commit** and its file set in the publication worktree — `git log --name-only --format='commit %H' '@{upstream}'..HEAD` — and confirm every path in every listed commit is under the resolved promotion destination (default `docs/learnings/`, or a non-null `overrides.learningsTarget`) or is `CLAUDE.md` (the pointer duty). **One deterministic fallback, not a choice:** when `git rev-parse --abbrev-ref '@{upstream}'` fails (the transient publication worktree has no upstream configured), probe `git log --name-only --format='commit %H' origin/<working>..HEAD` — the working branch's remote counterpart, the fork point against the `ensure-origin` push target. **ANY** commit in the probed range carrying any other path is **condemned** — the whole range, never the tip alone: it means stale tracked files were staged from a stale checkout (the recorded incident staged stale version slots and silently reverted a landed release while lock-step stayed green) — do **not** push. **Neutralized-pair exemption:** a commit is *not* condemned when a later commit **in the same range** reverts it — linked by git's own `This reverts commit <sha>.` body token — and that reverting commit is likewise exempt, its file set being those same escaping paths by construction. The pinned probe's `--format` suppresses commit bodies, so establishing that link takes one extra read: `git log --format='%H%n%b'` over the same range shows each candidate's body. **Undo routing.** When the condemned commit **is** `HEAD` **and** is either the only one condemned **or** the just-authored `docs(learnings): phase N` commit among several condemned (the shield below routes that case here first), undo it in the publication worktree: `git reset --hard HEAD~1` — a commit provably **unpushed** (origin has never seen it). This is the **sanctioned exception to the no-`reset --hard` shared-branch doctrine, scoped to an unpushed commit in the transient Lead-owned publication worktree** — a chosen carve-out, not a doctrine conflict; any uncommitted state there is discarded by design, since the worktree exists only for this commit and is removed next step. Only then run `remove-publication-worktree`, re-provision, and re-commit exactly as today. When the condemned commit is **not** `HEAD`, or **more than one** commit is condemned, never rewind — `git revert` each condemned commit instead (never rewind published or built-upon history) — with ONE shield: the just-authored `docs(learnings): phase N` commit is itself **never reverted** (reverting it would unpublish the promotion while the flow marches on to the push and the marker stamp below — a successful push that falsely claims publication, the exact outcome ADR 0022 §(I) rejects). When that just-authored docs commit is among the condemned, it takes the `reset --hard HEAD~1` carve-out above **first** (it sits at `HEAD`, provably unpushed), the revert routing then applies only to the condemned commits beneath it, and the pass ends in the same `remove-publication-worktree` + re-provision + re-commit. A conflicted revert is `git revert --abort` + escalate, leaving the worktree in place for inspection (the flow's standing push-failure posture). **The undo pass terminates — push or escalate, never a loop:** re-probe the range afterwards, reading commit bodies for the exemption as above; the exemption makes each neutralized pair clean, so a clean re-probe proceeds to the `ensure-origin` push below with the promotion genuinely in the pushed range (the docs commit survived untouched, or was freshly re-committed after a reset). **Bounded, both arms:** if the re-committed docs commit is condemned again, do not loop; escalate (the staleness is not worktree-local); likewise a re-probe that condemns any commit again after a completed revert pass — beyond the exemption's neutralized pairs — escalates rather than re-entering this routing.
- Push via `bash …/provision-worktrees.sh ensure-origin <working>` (push-first CAS, never force). On **any** failure (deliberately classification-free): fetch and replay the docs commit onto the fetched tip in the publication worktree, **retry once, then escalate** — leave the worktree in place for inspection (never-lose-unmerged-commits; the Setup/Gate-2 heal scan is the standing recovery at the next entry).
- `remove-publication-worktree <pub>` (fail loud).
- **Only after the successful push**, stamp each promoted local original with the marker — nested under `metadata:`, exactly `metadata.promoted: <workingBranch>@phase-<N>` — and **never delete it**. Promotion is a copy-with-marker: the local row stays visible to `render-index`/prefetch/`/lessons-learned` until the campaign PR merges; the marked copy is retired by a future `/lessons-learned` pass once the merge is confirmed.
3. **Regenerate the projection last.** Run `node ${CLAUDE_PLUGIN_ROOT}/skills/_shared/war-memory.mjs render-index --local <local root> --repo <resolved repo root>` (`--local` is the Setup-resolved local root and is **required** — the CLI fails loud rather than guess `<cwd>/memory`; regenerates the local `MEMORY.md` projection **with** the repo's `[repo]`-marked rows folded in — the `--repo` flag is only added when the repo root resolved; without it a repo-adopted store would silently drop every `[repo]` row from the regenerated projection). Run it **last** so the projection keeps the just-promoted rows. This is the Lead's own work — the servitor never touches branches.
- **CLAUDE.md pointer duty (append-if-absent, same commit).** In that same `docs(learnings): phase N` commit — now authored in the publication worktree — when `commitLearnings` is on and the promotion selection is non-empty, ensure the target repo's `CLAUDE.md` carries the ratified pointer line so non-WAR coders inherit the lessons ambiently. If `CLAUDE.md` already contains the pointer, do nothing; if the line is absent, **append** it (create `CLAUDE.md` if the file does not exist) — **never** rewrite or reorder existing operator content. The ratified pointer line, byte-identical:
> 📚 **Durable engineering lessons live in `docs/learnings/`** — one fact per Markdown file, provenance-tagged frontmatter. Before changing a subsystem, read the lessons that name it (plain Read/Grep, or ranked retrieval via the `work-audit-refine` plugin's `war-memory` query).
## Resume (on restart after a crash or interruption)
On resume — before reading the ledger or open issues and continuing — read [references/resume-and-recovery.md](references/resume-and-recovery.md) (§ Resume reconciliation pre-flight) and run the reconciliation pre-flight: precedence **git branch state > GitHub issue labels > `ledger.json`** (ADR 0008; the journal is off-ladder — [references/design.md](references/design.md) §6), repairs flow **one-way toward git**, and an unexplained commit **halts**. When submodules are in play — a recorded `submodule_merge_sha`, or `landDecision:'held:submodule-pr'` — also read [references/submodule-flows.md](references/submodule-flows.md) for the gitlink reachability check and the `held:submodule-pr` sub-procedure.
## Checkpoint (between phases)
Post a phase report — *landed (task→issue#, SHA) · the rendered **handoff block** (tipSha · polish merged/discarded/skipped · absorbed (sha → finding titles) · follow-ups filed (issue + why-not-absorbable) · asks parked (question + fork + task/seat/sha — the strike-list ruling gate's input) · notes · End-state condition statuses · intentPresent) · **Unexecuted backstops** (mandatory line rendering `handoff.backstops[]` — every deferred validation this phase did not run: each `check · why deferred · runner`, `source: 'auto'` entries flagged as Setup-recorded, and an **AI-declared** entry rendering its marker so it is never shown as operator-ratified; literal `None`/`— none —` when the array is empty, and the "no backstop section (pre-ratified-backstop plan)" note when it is `null`) · learnings captured · escalations needing your decision (with options) · deferred/blocked · gate result + working pushed@SHA · next phase?* — and **wait for the user's go**. Under `--afk`: post + `PushNotification`, then proceed. **Hard escalations (audit-blocked, unresolvable conflict, plan-contradiction) always halt regardless of mode.** Promote any ADR-worthy deviation to a real `docs/adr/` entry.
- **Manifest stamp (telemetry, fail-open).** Before posting the phase report, complete the Run-manifest section's on-phase-return stamp (`endedAt`, dispatch counts, task terminal statuses, `land`, envelope aggregates); a skipped stamp is the "unfinalized phase record" friction row `/war-review` reports; fail-open discipline unchanged (a failed write logs one line and never blocks the advance).
- **Issue-lifecycle floor — gate the DAG advance (§4.1).** Before advancing the DAG past this phase, run `bash ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/assert-issues-filed.sh assert <ledger.json> <phase-id>` (it runs `gh-preflight.sh` itself first, so an account flip cannot fake its exit). It confirms the ledger's `epic_issue` and every `tasks[].issue` exist on live `gh`, and — on a phase being **landed** — that the epic is `state:CLOSED` **and** carries `status:done`. **Route by exit code:** `0` ⇒ verified, advance; `1` ⇒ blocks the advance, surfacing the named route (`issues-missing` naming the missing/null issue, or `done-but-open`) — treat as an escalation to resolve before proceeding; `2` ⇒ a gh/network/ledger-parse **tooling error**, escalate as such, **never a silent pass** (exit 2 never collapses into 1). On a **landed** phase, close the epic via the floor's atomic mode — `bash ${CLAUDE_PLUGIN_ROOT}/skills/war/assets/assert-issues-filed.sh --close-epic <n> --sha <sha>` (one call does `+status:done` / `−status:in-progress` **and** `gh issue close --reason completed` with the landed-SHA comment) — **instead of** separate label/close commands, so `status:done` can never outlive an open epic.
- **Follow-up filing floor — gate the DAG advance (D4, #1331).** Before advancing the DAG past a handoff-emitting phase (`landed` / `held:escalation`), every `handoff.followUps[]` entry must carry a **non-null `issue`** — the Workflow's `file-followups` dispatch normally stamps them (**consolidation precedes filing**: the engine collapses duplicate rows into `seats[]`-corroborated candidates pre-dispatch and asserts the returned `clusters[]` manifest — canonical: `FOLLOWUP_FILING_RESULT` in `assets/workflow-template.js`); any null ⇒ file it **now**, in a gh-write of its own (this floor fires at the Checkpoint, after the per-phase gh-write batch has closed, so run `gh-preflight.sh` for this write), **under the same discipline** — one issue per root cause, an open `war-followup` match gets a **corroboration comment**, never a new issue — and stamp the number into the ledger's phase `handoff` record. **Never advance over a null `issue`.**
- **Ask ruling gate — the strike list, gate the DAG advance absolutely (#1550, ADR 0013 amendment).** When `handoff.asks[]` is non-empty, **one gate rules all parked asks in a single pass**: render the strike list — one row per ask, its `question` + `fork` (the decision needed and its two branches) + task/seat/sha provenance, plus, on a citation-matched row, its `citationPrefill` (the engine's matched standing row + match rationale + executed sha + recommended ruling, #1879): the operator ratifies the prefill or records a dissent as the ruling — never auto-unparked past a present operator, and a confirm-via-prefill is recorded as a citation-informed ruling in the same telemetry channel as an `--afk` citation resolution (the aced record's citation stamp) — and put it to the operator with the phase report. The advance floor is **absolute**: never advance the DAG over an unruled ask — interactively this binds as a hard wait (the run does not proceed until every row carries a ruling; no severity, count, or staleness exception). **Ruled-ask execution (ADR 0013 amendment 2026-08-27):** a ruled ask whose fix is fully specified **executes in this run** — injected into the next phase's decompose as a small first-class task carrying the ruling and its `suggested_fix`, or, in the final phase, ONE bounded polish-style post-land dispatch at the working tip. **Ruled-ask filing parity** is now filing-on-non-execution: file an issue **only** on **cannot-execute** (no vehicle, cross-task, budget) or execution-failure (regression ⇒ revert), the ruling recorded in it either way — never silently, never as default litter; when filed, it files Lead-side in a gh-write of its own, executed after this gate's ruling (the per-phase gh-write batch has already closed, so run `gh-preflight.sh` for this write), under the same discipline as engine-filed follow-ups — one issue per root cause carrying an `## Evidence artifacts` section, dedup-checked first against the engine-filed `handoff.followUps[]` rows and open `war-followup` issues (a match gets a corroboration comment, never a duplicate) — and mint each ruling as an adjudication row (the third producer, Decompose step 5) in **standing-row format** (today's ruling is tomorrow's `--afk` citation source), recorded in the ledger and threaded to every subsequent phase. Unruled asks are **never filed** — the engine already excludes them from consolidation and the file-followups dispatch; the Lead never re-adds one. **`--afk` posture:** a parked ask matching a standing adjudication row resolves by citation (the matched row quoted as the ruling) and may carry an **actionable absorb** outcome executed through the re-entry vehicle, its record carrying the matched **row-id + match rationale**; the row must cover the finding's NAMED trade-off, ambiguity is no-match; a no-match **demotes to follow-up with the question preserved** in the issue body — visibly not loss-free, flagged in the phase report; the unmatched ask files as a `follow-up` with `demote:ask-unruled-afk` on the body prefix line, question preserved (D13); a suppression row is minted **only from an operator ruling, provenance-marked** — `--afk` never mints one.
- **Never-waive rule (§4.4 — Lead prose).** A validation that is in **none** of: the resolved gate, a deterministic floor (test / packaging / submodule), or the plan's backstop section (a plan-declared or Setup-auto-recorded deferred validation) **may not be waived in prose mid-run**. Do not narrate it away as "skipped" or "not applicable" in a phase report — **escalate to the operator** instead (interactively wait; under `--afk` this is a hard escalation, since only the operator can ratify a new waiver). This is the behavioral fix for the silent-prose-waiver failure the whole feature exists to prevent — a deferred validation is legitimate only once it is a ratified backstop, never as an ad-hoc mid-run sentence.
- **Fail-closed phase classification (§4.2 — apply on every phase notification, before any land/hold/advance decision).** On each phase Workflow notification, derive `landDecision` as follows:
1. If `status !== "completed"` (the Workflow timed out, was killed, or returned a non-`completed` notification) → classify as `held:phase-incomplete`.
2. Else if the result is missing, unparseable, or `landDecision` is not in the known set (`landed`, `held:escalation`, `held:nothing-merged`, `held:land-failed`, `held:phase-incomplete`, `held:workflow-error`, `held:submodule-pr`) → classify as `held:workflow-error`.
3. Else → use `result.landDecision` as returned.
Never skip this gate — a phase that failed to run must never silently advance the DAG.
- **Outcome handling (§4.3):** when the derived `landDecision` is any `held:*` hold — or a task returned `env-blocked`, a merge/land `gate_failed` arrives (with or without a `gate_failure_class`), or you complete a phase's land manually — read [references/resume-and-recovery.md](references/resume-and-recovery.md) (§ Checkpoint outcome handling) and follow the matching arm: terminal vs retryable holds, the `held:land-failed` root-cause recipes, the escalation-completion land, `env-blocked` evidence gating, `gate_failed` class routing, manual-land hygiene, the exactly-once learnings capture after a manual land, the Recovery-relaunch entry points, and the held-partial-phase recovery runbook.
## Finish
When all phases land, run `bash ${CLAUDE_PLUGIN_ROOT}/skills/_shared/gh-preflight.sh "<overrides.ghUser>"` **before opening the PR** (the Finish-PR gh-write site), then open **one PR** working → landing, body summarizing phases landed, learnings, escalations resolved, and follow-up issues filed. The body carries a mandatory **"Unexecuted backstops"** line (§4.4) — the union of every phase's `handoff.backstops[]` (plan-declared plus Setup-auto-recorded deferred validations), each rendered `check · why deferred · runner`, `source: 'auto'` entries flagged, and any **AI-declared** entry rendering its marker (never surfaced as operator-ratified); literal `None` when no phase deferred anything. This is the final land-time surface for every validation the run consciously left un-run. Report the PR URL. **Closing handoff:** point the operator at **`/war-review`** as the post-run step — it reads this run's manifest (`.claude/war/runs/<runId>.json`), renders the telemetry + friction report, and offers one confirmed friction issue.
## Invariants (never violate)
- The Lead never edits code — only decomposes, gates, surfaces escalations, talks to the human. Keep your own context lean: push detail into the ledger + issues, not your chat history.
- `war-auditor` is read-only (file tools plus a fail-closed guard restricting Bash to read-only git — `hooks/validate-auditor-git.sh`); `war-servitor` is the only reviewer-side role that writes, and only under the threaded absolute local memory root (repo-root publication is the Lead's Gate-2 promotion — the servitor never writes `docs/learnings/`) (confinement is the capability allowlist — no Bash, only Read/Grep/Glob/Write/Edit plus the read-only memory query tool — so its sole write path is Write/Edit; the PreToolUse scope hook gates those by `agent_type` to the local memory path-pattern, and denies mutation of any pre-existing untagged memory file, [ADR 0002](../../docs/adr/0002-scope-by-agent-type.md)); workers stay inside their worktree (the hook confines a `war-worker` write to a dir bearing a `.war-task` marker); `war-refiner` owns **all** merges, one at a time, never `--force`/`reset --hard` on shared branches. The **one force exemption** (ADR 0012): a worker may `git push --force-with-lease` ONLY its own task branch, and ONLY after a dispatch-rebase diverged it from its pushed remote — never any other ref, never for any other reason. The **refiner**, not the worker, also owns worktree provisioning — it runs `provision-worktrees.sh` to cut the **plan-namespaced** integration branch and `git worktree add` each task ([ADR 0001](../../docs/adr/0001-explicitly-managed-worktrees.md), [ADR 0003](../../docs/adr/0003-plan-namespaced-branches.md)).
- Never merge a task with an open Critical/Major finding, without a passing gate, or before unanimous audit. Never merge un-audited. Never let a worker satisfy the gate by deleting/skipping tests. **Baseline carve-out (spec §8):** the "passing gate" clause admits exactly one exception — a `gate_failure_class:'baseline'` red (the same failure **proven pre-existing at the classification base**, its debt **recorded as a `source: 'auto'` backstop**) may proceed; an `introduced` red — or an unclassified/absent-class one, the permanent fail-safe — **never** does. (The delete/skip-tests prohibition is absolute and has no carve-out.)
- **The phase-close sweep is fail-open — it may only improve the tip.** It dispatches only when the phase would already land; a re-approved polish merges at the serial queue's tail, anything else **discards** and the pre-polish tip lands exactly as it would have (a discarded sweep recomputes nothing — never a hold). Discard leaves the polish branch + `p<N>-polish` worktree in place (never-lose-unmerged-commits; reaping is a human act) and demotes the queue to `follow-up`. Every demotion on the terminal-disposition ladder — ace bisection subsets included — is logged; nothing drops silently.
- When judging or authoring a revert of a phase-close polish commit, read [references/resume-and-recovery.md](references/resume-and-recovery.md) (§ Phase-close polish reverts — never self-justifying).
- **Deterministic test-floor** — the **refiner** runs `assert-test-in-diff.sh` at merge-task, pre-merge, for every task where `requiresTest:true`. This is a standing-instruction surface in `agents/war-refiner.md`, independent of the dispatched merge-task prompt threaded in `workflow-template.js` (memory `standing-instruction-vs-dispatched-prompt-coverage-split` — both surfaces must be kept in sync). A `no-test` result routes the bounded fix-worker + full re-audit sub-loop; the Lead never bypasses it.
- Models/effort come from the resolved run config (`/war-room`); **defaults** are `war-config.mjs` `DEFAULTS.agents`, never restated here. `audit.autoEscalate` (default on) still widens a lone-seat roster on a Critical/low-confidence finding — toward the seat's `widen` nomination when it is valid (a non-empty list of distinct lens names, never a `RESERVED_LENSES` or `WIDEN_BARRED_LENSES` member → those lenses at `deep` on `tier: "high"`), else the default roster's lenses (the byte-identical default-roster-union fallback) — a union that keeps the lone seat, never duplicates, caps at 5. The < 3× single-agent cost target is a design aspiration judged per run from `/war-review` totals, never a per-preset claim.
- **Every landed phase captures learnings exactly once**, by whichever path landed it: the in-flow Wrap-up when the Workflow lands (`landDecision === 'landed'`), else a Lead-driven `war-servitor` pass after a manual land. Skip if `servitorResult` is already set.
- At every diagnosis moment with **no mechanical classifier** — live validation, resume anomalies, `held:*` adjudication, the Lead's own manual operations — run the **self-confound gate**: enumerate and rule out your own and concurrent actors' mutating actions before blaming a subsystem, and keep validation **single-path** over shared mutable state (never manual + automated bring-up back-to-back; re-provision before switching — the incident's root act). For merge/land gate reds the refiner's `gate_failure_class` base re-run **is** that action-provenance evidence — consume it, don't re-litigate it. No **hypothesis promotion** — a memory/lesson write, a `war-followup` issue, a fix plan or spec, a sub-agent fan-out — without primary evidence plus a stated falsifier; a hypothesis that must survive compaction is a labeled ledger/phase-report **hypothesis** note, never a memory lesson (the D8 valve: record or escalate, then proceed).