CLAUDE.md ยท diff

git:20261030.6d3bb73 to git:20261030.918147d

1 added, 1 removed. Audit A to A.

# 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`); **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); 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.
+ - **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.