CLAUDE.md ยท git:20261030.a46c4f0 ยท 2026-10-30 ยท sha256 ce44164ab6f39b32
CLAUDE.md git:20261030.a46c4f0A
Immutable. This exact content never changes and is served at /api/v1/blob/ce44164ab6f39b32.
# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ๐ **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). In this repo the plugin ships at `skills/_shared/`, so that ranked query is `node skills/_shared/war-memory.mjs query '<terms>' --repo docs/learnings` (needs Node >= 24; plain Read/Grep works without it). ## What this repo is WAR (Work ยท Audit ยท Refine) โ a Claude Code **plugin**; this repo *is* the plugin (`.claude-plugin/plugin.json` + `skills/` + `agents/` + `hooks/`). It orchestrates multi-phase plan execution with worker / auditor / refinery / servitor agents over git worktrees and GitHub issues. There is no application build: developing here means editing skill prose (`skills/*/SKILL.md`), the per-phase Workflow engine (`skills/war/assets/workflow-template.js`), shell hooks/floors, the memory CLI, and their tests. `CONTEXT.md` is the glossary (ubiquitous language); `docs/adr/` records the binding decisions, numbered sequentially โ `ls docs/adr/` for the current head. ## Commands Node โฅ 24 required (`node:sqlite`/FTS5). No package.json or Makefile; the repository gate lives under `skills/war/assets/`. ```bash # Full repository gate (configured in .claude/war/config.json): # overlap Node tests with a bounded shell-suite pool; keep all tests and per-suite logs node skills/war/assets/repo-gate.mjs # Tune each pool (defaults: 2 Node files and 2 shell suites, each accepts 1..32) WAR_GATE_NODE_JOBS=2 WAR_GATE_SHELL_JOBS=4 node skills/war/assets/repo-gate.mjs # All JS tests (quoted glob REQUIRED โ a directory arg fails on Node 24) node --test 'skills/**/*.test.mjs' # One JS test / one shell test (shell tests are bash-3.2-safe and cwd-independent) node --test skills/war/assets/war-config.test.mjs bash hooks/validate-worktree-scope.test.sh # All shell tests โ anchor to hooks/ and skills/; a repo-root find picks up # ~100 stale duplicates under .claude/worktrees/ for f in $(find hooks skills -name '*.test.sh' | sort); do bash "$f" || exit 1; done # Redaction lint (exactly what CI runs โ the only thing CI runs) node skills/_shared/war-memory.mjs lint docs/learnings/ # Memory queries / index render โ ALWAYS pass --local; the cwd fallback # silently creates a stray ./memory dir in the repo node skills/_shared/war-memory.mjs query '<terms>' --repo docs/learnings node skills/_shared/war-memory.mjs render-index --local "$CLAUDE_MEMORY_LOCAL" --repo docs/learnings ``` Local plugin iteration: `claude --plugin-dir /path/to/WorkAuditRefine`, then `/reload-plugins` after each edit (local paths resolve to version `unknown`, so reloads always pick up changes). **Releasing:** a version bump must update all four slots together or the release is a silent no-op โ `.claude-plugin/plugin.json` `version`, `.claude-plugin/marketplace.json` `metadata.version` **and** `plugins[0].version`, and the `README.md` `## Status` line (replace-in-place, no badge). `skills/war/assets/version-slots.test.mjs` locks the four slots in lock-step (fail-closed โ a partial bump is a red test, and it also guards the README Releasing prose against the "three files" undersell) **and enforces a monotonic floor**: `plugin.json`'s version must be โฅ the highest version in a bounded first-parent window of slot-touching history, so a coherent all-four-slots *downgrade* โ which lock-step passes โ goes red until a Lead/operator restore commit lands. The floor fails open only outside a usable git context (no git, non-zero git exit, or an empty window), never on a broken parse. Version literals inside plans/roadmaps are non-authoritative โ resolve the next free patch from the slots at land time. ## The pipeline is gospel: interview โ plan โ red-team โ war **One interview, one merged artifact.** The pipeline's execution artifact is the **merged plan** (`docs/plans/YYYY-MM-DD-<slug>.md`): Part 1 the ratified *decision record* โ `## Context` with evidence-tagged claims (D4 tag forms), `## Pivotal constraints`, `## Resolved design tree`, a required `## Assumptions ledger` โ and Part 2 the decomposed phases, flat H2s carrying today's exact extraction headings: `## Commander's Intent` (Purpose / Method / numbered individually-checkable End state โ plan slice is the floor, intent is the ceiling), `## Build order`, per-task `Files:` / `Plan slice:` / `requiresTest` / `requiresPackaging` / `deps` / `target repo`, and a required `## Deferred validations (backstops)` section (explicit `None` allowed). A **design spec** (`docs/specs/YYYY-MM-DD-<slug>-design.md`) is an *input shape* only โ how `/survey-corps` synthesis and externally-brought drafts arrive; it carries **no dispatch structure**, `/war` cannot execute one, and conversion turns it into the merged plan (legacy spec + plan pairs are grandfathered in place). Roles are strict: **`/war-strategy` owns the interview and converts** (doctrine at `skills/war-strategy/references/plan-interview.md` โ bare invoke runs the plan-authoring interview itself, terminating only on a merged plan, with the Grill Me family a recommended front door, never a dependency; invoked with a draft it reviews for war-shape gaps and converts into the merged shape); **`/red-team` validates and never converts** (proves plan claims in throwaway sandboxes; a merged plan's Part 1 is its own source of truth; report at `docs/red-team/YYYY-MM-DD-<plan-slug>.md`; patches the plan in place until a proceed verdict โ CLEARED, or ADJUDICATED when a patched blocker was not re-verified (ADR 0043)); **`/war` executes**. Outer loop: `/survey-corps` (issues โ specs + `.claude/aot/` manifest) โ `/war-machine` (specs โ merged plans + roadmap; never launches, never red-teams) โ `/war-campaign` (stack-and-plow stacked PRs, ADR 0011) โ `/aftermath` (evidence-gated cleanup). `/war-campaign` and `/aftermath` are never auto-invoked (`disable-model-invocation: true`). Templates live inline in `skills/war-strategy/SKILL.md` ยง2, locked by its structure test. **Code-boundary decomposition** (how plans are carved): (1) parallel tasks in a phase must be file-disjoint โ same-file tasks rebase-conflict at the serial merge; (2) dependency โ `deps` wave edge in the same phase (worker's first act is a rebase onto the integration tip), phase edges only for what must be *landed* first; (3) one task = one repo (submodule content + gitlink bump = two tasks, two phases); (4) release/version bump is its own trailing phase. Never use deps/waves to dodge a same-file collision. ## Execution architecture (what a /war phase actually does) Every task worktree is cut from one **frozen phase base** (the integration tip at the Provision barrier) โ waves order *when* workers run, never *what base they see*. Per phase: Provision (refiner-owned worktrees, `run.provision` steps; failure = `env-blocked`, worker never spawned) โ Work+Audit per wave (fresh workers implement; a 1โ5 seat, distinct-lens, read-only auditor roster judges the pinned SHA; Critical/Major block; approval is **unanimous** on one `audit_sha`; bounded fix rounds share `run.roundLimit`=6) โ Refine (serial merge queue: rebase in the task worktree, floors, gate, merge) โ post-merge gate-audit (`execution-evidence` lens) โ Land (push-first CAS in the run-scoped `_refinery` worktree โ the push *is* the CAS; never force-push; `land_stale` โ `conflict`) โ phase-close coherence sweep (fail-open polish of `absorb` findings) โ servitor records learnings โ machine-readable `handoff` block. Enum discipline: task status `merged` is terminal; `landed` is phase-level only. `HARD_ESCALATION_REASONS` and `KNOWN_LAND_DECISIONS` are canonical in `skills/war/assets/land-decision.mjs` and **hand-mirrored** inside `workflow-template.js` (the Workflow sandbox cannot import) โ change both copies and the drift-guard test together. Never add `held:workflow-error` to `HARD_ESCALATION_REASONS` (ADR 0005). Floor scripts exit 0/1/2: 1 = the named route (`no-test`, `unpackaged`, `done-unmet`, and `budget-uncited` โ the in-band `floor_route` marker riding `no-test`, now also a hard escalation reason), 2 = git error and must never collapse into the floor status. Resume: **git branch state > issue labels > `ledger.json`** (ADR 0008). Repair records toward git, never git toward records; an unexplained commit halts. Dead phases resume, never re-run. Prompt surfaces are split: standing instructions in `agents/*.md`, dispatched prompts string-built in `workflow-template.js` โ a change to auditor/refiner behavior must update **both in the same commit** (they drift silently otherwise). ## Guard architecture (hooks/) Confinement is by `agent_type`, capability-first (ADR 0002): **auditor** โ no writes at all; Bash fail-closed to an allowlisted read-only git verb set (`validate-auditor-git.sh`; `fetch` deliberately excluded; one `-C <path>` peeled without widening verbs); MCP fail-closed to the memory query tool alone (the `MEMORY_TOOL` literal in `validate-evidence-auditor-mcp.sh`), while the evidence auditor keeps the REA allowlist beside it; **worker** โ writes only under a `.war-task`-marked worktree ancestor (sibling-worktree writes are an accepted residual), Bash writes only advisorily warned, MCP limited to the memory query tool beside the REA allowlist; **servitor** โ no Bash at all (the primary confinement), MCP limited to the memory query tool, Write/Edit only into the local memory root (repo-root publication is a Lead Gate-2 promotion), and mutation of any pre-existing untagged memory file is denied, and every new fact Write must carry a valid `metadata.provenance` (`validate-servitor-provenance.sh`); **helper** โ Bash only (no `tools:` beyond it), fail-closed to the auditor's read-only git verb set, for the tiny `audit-pin` and `diff-probe` dispatches (ADR 0057); **refiner/main** โ the scope hook fail-opens: refinery isolation is *prompt-enforced*, backstopped by `refinery-surface.test.sh`, and the refiner is denied every MCP tool. A `..` path segment is denied for everyone; for the four memory-tool seats the hook also denies a memory query `local` outside `*/.claude/projects/*/memory`. Merge-path floors (`assert-test-in-diff.sh`, `assert-packaging-in-diff.sh`, `assert-no-submodule-mutation.sh`, `assert-done-when.sh`, `assert-budget-raise-cited.sh`) run refiner-side pre-merge; the diff-discovery floors' discovery patterns must mirror `resolveGate` in `war-config.mjs` (`assert-done-when.sh` executes the task's acceptance command and `assert-budget-raise-cited.sh` inspects budget-file value lines plus commit trailers โ neither discovers files; the test floor suite's fail-closed classification census already records both exemptions). ## The memory system (two-root, files-canonical) One durable lesson = one Markdown file: `name`/`description` + nested `metadata:` (`type`, `provenance`, `keywords`). Two roots (ADR 0015): the **local root** (`~/.claude/projects/<project-slug>/memory/`, untracked, personal) and the **repo root** (`docs/learnings/`, committable, human-reviewed). Routing is `metadata.type`: `project` โ repo root iff `memory.commitLearnings` (default false โ opt in via `/war-room`) *and* the fail-closed redaction lint passes (home paths, emails, handles, credential shapes โ demoted to local, reported, never dropped); `user`/`feedback`/untyped โ local, always. - **Provenance ladder** (ADR 0007): `user-confirmed` > `code-verified` > `agent-unverified` โ records *how established*, drives recall ranking and archive-eviction order. The servitor verifies referents on write (found โ `code-verified`; absent โ `agent-unverified` + absence note). - **`MEMORY.md` is a generated projection** โ written only by `war-memory render-index` into the local root, atomically; never hand-edit it, never commit it. Budgets: hard 200 lines / 24,400 bytes (render refuses), advisory 17,000 bytes โ crossing the advisory line is the signal to run `/lessons-learned tighten` (the operator-gated projection-shrink + usage-scored eviction pass that restores the store to below the advisory line). Projection size is driven by frontmatter `description` lines, not lesson bodies. - **Temperature is location**: hot = root, cold = `archive/` โ archived lessons stay queryable forever by every seat; archiving is a move + note, **never a deletion**, and it removes only the always-loaded row. A row stays only if it passes the always-loaded test (ADR 0055). Archive by explicit slug, never `archive --candidates` (it archives *all* budget candidates). - **Retrieval**: FTS5 index built in-memory per invocation (nothing on disk to commit or corrupt); `metadata.keywords` weigh ~8ร body โ a lesson without keywords is under-retrievable. `keywords` must be nested under `metadata:` (a top-level `keywords:` is silently not indexed). - **In a run**: the Lead prefetches top-K lessons per seat into worker/auditor/servitor prompts at phase launch (fails open); `hooks/record-lesson-use.sh` logs each lesson a worker or auditor opens or names in `lessonsUsed` (ADR 0055); the servitor writes new lessons post-land; with `commitLearnings` opted in (off by default), the Lead lints and commits `docs(learnings): phase N`. `/lessons-learned` is the housekeeping pass (staging copy + atomic swap via `safe-swap.sh`); its `migrate`/`evict` modes adopt/undo the repo root. - The pointer line at the top of this file is **ratified and byte-identical across surfaces** โ never reword it. ## Doctrine placement: the hot/cold law (ADR 0042) New doctrine defaults to a `references/` file plus a trigger pointer on the operative surface โ inline placement is reserved for tier-1 (every-invocation) doctrine. The pointer shape is fixed: `when <trigger>, read references/<file>` โ the trigger is the skeleton, and a pointer without a trigger is a defect. Eviction is a byte-identical move, never a rewrite; budgeted surfaces carry advisory/hard byte lines with ratchet-down semantics (lowering is a normal PR; raising cites ADR 0042's justification rule in the commit body). ## Known traps - The workflow-template tests are split into topic files (`workflow-template.<topic>-<n>.test.mjs`) sharing `workflow-template.test-harness.mjs`, so `node --test` runs them in parallel. When adding a workflow-template test or fixture, or grepping for a test name, read that harness file header first. - `docs/learnings/` on a branch/PR not yet merged โ `render-index --local`-only silently drops every `[repo]` row โ pass `--repo` whenever the dir exists. - LEGACY spec + plan pairs share a slug but different suffixes; `/red-team`'s legacy arm greps such a plan's source-spec line โ keep it there. A merged plan's Part 1 is its own source of truth. - Findings route by auditor-owned `disposition` (`absorb`/`follow-up`/`note`/`ask`), orthogonal to severity; a fully specified Minor/Nit defaults to `absorb` (`phaseClose:true` outside the task diff) and `follow-up` needs a `barrier` tag; `ask` is never a default โ an unruled `ask` is never filed, only ruled at the Checkpoint strike-list gate. A validation in neither the gate, a floor, nor the backstops section may not be waived in prose โ escalate (ADR 0017). - Intent is never Lead-invented; the sole synthetic-intent exception is `/war-machine --afk`'s `## AI-Commander's Intent` heading, and any heading-extraction surface must recognize **both** intent headings (ADR 0014). - Anchor references by named construct, not line number โ line numbers rot across the serial merge queue.