CLAUDE.md · diff

git:20260924.29dc561 to git:20261024.5b2fcb3

6 added, 5 removed. Audit A to A.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this repo is
Mavericks is a reusable operating model for agent-driven development. Not a deliverable product — a framework adopted by other projects. Contains:
- Core process docs (`docs/core/`)
- Node.js operator tooling (`scripts/`)
- Project artifact templates (`templates/`)
## Session start
```bash
./scripts/mavp-operator --agent
```
The session-start brief reports a *declared* permission mode via a `permission_mode` field — not a confirmed "active" mode, since the framework cannot independently verify it. `permission_mode_source` names where the value came from (`hook_payload` | `persisted_runtime` | `settings_file`); `permission_mode_verified` is `true` only when a same-session harness hook payload confirmed it (the `SessionStart` hook wired by `mavp-install.js`), and stays `false` on a harness whose payload omits the field — a designed-for outcome, not a defect. See the **Shared permission-mode default** note in `docs/core/BOOTSTRAP_GUIDE.md` (settings precedence, and a documented 2026-08 divergence from it) and DR-010 in `docs/core/DECISIONS.md`.
## Operational commands
```bash
./scripts/mavp-operator # operator dashboard — see docs/core/OPERATOR_DASHBOARD.md for panel reference
./scripts/mavp-operator --watch # dashboard watch mode (r/s/q)
./scripts/mavp-operator --snapshot # text snapshot for context
./scripts/mavp-operator --close-session # end-of-session ritual — auto-detects mode: interactive (TTY) runs per-task prompts + wave-goal + git-push prompt; non-interactive (agent/headless) prints a git-push reminder instead of prompting; in BOTH modes it first sweeps terminal entries out of TASK_STATUS.md's Active tasks section and computes wave completion from what remains — completed work (merged/deployed_dev/deployed_prod/runtime_verified) moves to "## Recently completed tasks" and is mirrored+archived in BACKLOG.md, while `deferred` moves to a "## Deferred tasks" section (created on demand) and `deprecated` to "## Recently completed tasks", both TASK_STATUS-only relocations that never touch the task's BACKLOG block; the deferred/deprecated skip set is explicit, so `planned`, `qa_passed`, in-flight and unrecognized statuses all still hold the wave open; a structural guard aborts the close (before any BACKLOG.md write, leaving it untouched) if the merge-mirror is ever handed a non-completed record or asked to promote a task whose BACKLOG block currently reads `deferred`/`deprecated` — adjudicate that BACKLOG/TASK_STATUS disagreement by hand, then re-run; creates the session commit (`chore: close session <date>`) whenever the validator exits 0 (healthy) or 1 (drifting) — staging tracked changes only via `git add -u` (new untracked files are never included) — and skips the commit ONLY on validator exit 2 (repair required), printing an explicit "session commit SKIPPED — validator exit 2 ..." line (see docs/core/BOOTSTRAP_GUIDE.md — "Close-session commit contract" for the full behavior); in non-interactive mode only, also prints a propose-only shipped-but-unbooked advisory naming any `qa_passed` task whose evidence commit is already reachable from HEAD (never `dev_done`/`ready_for_qa`, which would route around QA), suggesting `--set-status <id> merged` without ever writing state itself, and standing down by name (not silently) on a shallow clone — see docs/core/TASK_LIFECYCLE.md "Wave-completion announcements (T-445)"; use --interactive to force interactive mode; use --push to auto-push after session commit on non-interactive wave-complete close (interactive mode unaffected — it already prompts); prints RENAME_SESSION: at the end; in CLI: `/rename <value>`; in VSCode: hover session in sidebar → rename
./scripts/mavp-operator --handoff [--notes "..."] # capture mid-session context to HANDOFF.md for next session
./scripts/mavp-operator --new-task [--title "..." [--owner <role>] [--repo <name>] [--depends-on <T-NNN>] [--requires-ux <y/n>] [--criteria "..."] [--verification-type <type>]] # interactive task creation; non-TTY stdin requires --title (other flags fall back to prompt defaults) — refuses and writes nothing if missing
./scripts/mavp-operator --quick-task [--title "..." --problem "..." --repo "..."] # quick task skeleton (title + problem only); non-TTY stdin requires all three flags — refuses and writes nothing if any is missing
./scripts/mavp-operator --update-task # interactive task status update
- ./scripts/mavp-operator --set-status T-xxx,T-yyy status [--from <current_status>] [--commit <hash|HEAD>] [--branch <name>] [--needs-fix-rounds <N>] # batch status update across BACKLOG + TASK_STATUS; optional --from guard: only transitions tasks whose current status equals <current_status>, skips others with a warning; optional --commit merges "commit: <hash> branch: <name>" into each updated task's TASK_STATUS.md Evidence field — append, not clobber (prior evidence text, e.g. QA notes, is preserved); accepts a hex hash (7-40 chars) or the literal HEAD (auto-resolved to the current repo's short hash); non-hex/non-HEAD input is rejected before any git call; a format-valid hash not reachable from --branch (default main) prints a non-blocking warning but the write still proceeds (validator runs once at the end — no transient state warnings); optional --needs-fix-rounds <N> (N must match /^\d+$/, invalid or missing value exits 1 before any file is read or written) writes the TRUE needs_fix_rounds count into each updated task's Evidence field on any target status — replacing an existing digit value in place (never appending a second `needs_fix_rounds:` token, which would trip the validator's conflicting_needs_fix_rounds check) or inserting when absent; when omitted, behavior is unchanged: needs_fix_rounds: 0 is still auto-stamped only on a merged transition, only when the field is absent
+ ./scripts/mavp-operator --set-status T-xxx,T-yyy status [--from <current_status>] [--commit <hash|HEAD>] [--branch <name>] [--needs-fix-rounds <N>] # batch status update across BACKLOG + TASK_STATUS; optional --from guard: only transitions tasks whose current status equals <current_status>, skips others with a warning; optional --commit merges "commit: <hash> branch: <name>" into each updated task's TASK_STATUS.md Evidence field — append, not clobber (prior evidence text, e.g. QA notes, is preserved); accepts a hex hash (7-40 chars) or the literal HEAD (auto-resolved to the current repo's short hash); non-hex/non-HEAD input is rejected before any git call; a format-valid hash not reachable from --branch (default main) prints a non-blocking warning but the write still proceeds (validator runs once at the end — no transient state warnings); optional --needs-fix-rounds <N> (N must match /^\d+$/, invalid or missing value exits 1 before any file is read or written) writes the TRUE needs_fix_rounds count into each updated task's Evidence field on any target status — replacing an existing digit value in place (never appending a second `needs_fix_rounds:` token, which would trip the validator's conflicting_needs_fix_rounds check) or inserting when absent; when omitted, behavior is unchanged: needs_fix_rounds: 0 is still auto-stamped only on a merged transition, only when the field is absent; explicit `--commit`/`--needs-fix-rounds` writes persist on a task already at the target status (summary line `Evidence updated, status unchanged (N): ...`), a `--from`-skipped task receives no evidence write, and the auto-stamp still requires a genuine status transition (T-779)
./scripts/mavp-operator --rename-task T-xxx "New title" # atomically rename task title in BACKLOG + TASK_STATUS
./scripts/mavp-operator --rescope-task T-NNN [--status <s>] [--owner <role>] [--title "..."] # atomically re-scope or un-defer a task: moves its block between BACKLOG.md's Deferred Tasks <-> Active Wave sections (creating Deferred Tasks on demand if absent), applies status/owner/title changes, and keeps TASK_STATUS.md's sectioning symmetric in BOTH directions — ensures a matching Active tasks entry when activating (relocating an existing entry back out of whatever section holds it, or creating one when the task had none), and on `--status deferred` relocates an existing Active tasks entry OUT into the same "## Deferred tasks" section the close-session terminal-status sweep uses (created on demand), so a deferred task never lingers in Active tasks; both relocations move the block byte-for-byte — hand-edited Evidence/Notes survive verbatim, no skeleton regeneration, exactly one heading per task; preserves the task ID, fails fast on duplicate/missing ID; validator runs once at the end
./scripts/mavp-operator --update-status T-NNN <status> # atomically set a single task's status in BACKLOG + TASK_STATUS (single-task predecessor of --set-status)
./scripts/mavp-operator --merge-task # promote a qa_passed task to merged with evidence
./scripts/mavp-operator --quick-merge [--verification-type <artifact|runtime|unit>] [--owner <role>] # fast-track an XS change directly to merged (title + commit hash); use only for <=2 files, <=10 lines diff, no risk — see docs/core/ORCHESTRATION_RULES.md "XS fast lane (quick-merge)" for the enforced thresholds and attested conditions; both flags are batch-wide (apply to every item in one run — run the lane once per grouping for a mixed batch), default unchanged to runtime/developer, and refuse the entire run with exit 1 on an invalid value before anything is collected or written; visual/manual verification types are mechanically refused (human review required); --owner validates against docs/ARCHITECT_OUTPUT.md's owner_role enumeration minus main_agent (the lane always cites a sub-agent's commit, never the Main Agent's own)
./scripts/mavp-operator --sync-status # sync TASK_STATUS.md Status lines from BACKLOG.md Active Wave
./scripts/mavp-operator --integrate <commit|base..tip> [--task T-NNN] # cherry-pick a commit or range into the resolved project root, with the git operation itself pinned to that root regardless of the caller's cwd (removes the hand-typed, cwd-dependent cherry-pick class — see docs/core/ORCHESTRATION_RULES.md "Worktree integration — Main Agent"); runs guardMutatingRoot() FIRST (T-624/T-670 — refuses and NAMES the primary checkout on a never-a-project root or a linked worktree, never auto-retargets); refuses when a cherry-pick or merge is already in progress in the resolved root; prints one `integrated: <short-hash>` line per landed commit and, when `--task T-NNN` is given and resolves to exactly one task block, a ready-to-run `--set-status` suggestion — `--task` is optional; on conflict exits non-zero naming `git cherry-pick --abort`/`--continue` with no auto-abort; writes NO state artifacts itself, so integration and status-booking stay decoupled
./scripts/mavp-operator --archive-merged # mid-wave archival: moves merged/deployed task blocks out of BACKLOG.md's Active Wave (and their TASK_STATUS.md Active tasks counterparts) into the same archive destinations --close-session uses, without closing the wave; leaves in-flight tasks untouched; --close-session still surfaces these titles in wave_summary and its results table once the wave finally closes; validator runs once at the end
./scripts/mavp-operator --park-wave [N] --reason "text" [--until YYYY-MM-DD] # move ALL of wave N's (default: current wave) task blocks out of BACKLOG.md's Active Wave / TASK_STATUS.md's Active tasks into "## Wave N — Parked" / "## Parked tasks (Wave N)" sections (carrying the wave's trailing non-task sub-heading along with its blocks, so --unpark-wave restores it under its own heading), and append "Wave N — <reason>" to parked_waves; --agent then reports 0 active slices for that wave; --until additionally arms a wave-keyed rechecks[] entry due on that date (today or later, validated before any write) so the park resurfaces as a due recheck; validator runs once at the end
./scripts/mavp-operator --unpark-wave <N> # inverse of --park-wave: restores wave N's blocks (and its trailing sub-heading) to Active Wave / Active tasks byte-for-byte and removes its parked_waves entry; also auto-acks any --until recheck armed for that wave, printing "acked RC-N (parked-wave reminder)"; validator runs once at the end
./scripts/mavp-operator --set-strategy-note "text" # set wave strategy context note in PROCESS_STATE.json; persists across mid-wave --close-session runs, cleared only when the wave actually advances; empty string clears
./scripts/mavp-operator --validate # run the validator via the operator wrapper (equivalent to node scripts/mavp-validator.js "$(pwd)")
+ ./scripts/mavp-operator --check-archive (--range A..B | --staged) # read-only nothing-lost check for the archive-not-delete convention (.claude/rules/docs.md): every non-empty trimmed line a commit removes from a layer-0/1 doc (perimeter at the parent: CLAUDE.md, its @ imports, .claude/rules/, module context_docs) must reappear verbatim in a perimeter doc or in a docs/archive/ file that same commit added or modified, or be amended in place (an added perimeter line keeps >=80% of its words in order, or all but one; archive copies never count); --range walks first-parent commits, --staged judges the index (for a project's pre-commit.local); prints a summary (commits scanned, perimeter files, removed lines, found-in-working, found-in-archive, amended-in-place, missing); exit 0 nothing missing, 1 missing lines named, 2 usage or git error or a shallow clone in --range mode
./scripts/mavp-operator --check-sync # compare agent/skill files in known projects against mavericks source
./scripts/mavp-operator --worktree-report [--main-ref <ref>] [--mtime-threshold <ms>] [--json] # read-only: classify every linked agent git worktree into dirty / unintegrated / clean-and-integrated via PATCH-EQUIVALENCE (`git cherry`), never raw reachability — this project integrates by cherry-pick, so a worktree tip is unreachable from main by construction even once fully integrated; shared classifyWorktrees()/formatWorktreeHygieneAdvisory() in mavp-operator-lib.js also feeds the --close-session advisory and --prune-worktrees, so all three surfaces agree; when at least one worktree is genuinely prunable, both this report and the --close-session advisory (both modes) append a second, propose-only line naming the ready-to-run DRY-RUN prune command (`formatWorktreePruneSuggestion()` — never suggests `--yes`), so the operator no longer has to recall `--prune-worktrees` unprompted
./scripts/mavp-operator --prune-worktrees [--yes] [--main-ref <ref>] [--mtime-threshold <ms>] # remove ONLY clean-and-integrated worktrees (same classification as --worktree-report), additionally gated on an mtime safety window (default 1h) so a just-spawned live agent's still-clean worktree is never removed; defaults to dry-run (prints what would be removed) — pass --yes to actually delete the worktree + its branch; run the real (--yes) prune only with zero live sub-agents, since this is a repository-global exclusive-resource operation
./scripts/mavp-operator --install <target-dir> [--yes] # bootstrap Mavericks into a target project (operator wrapper for node scripts/mavp-install.js); non-TTY runs (e.g. agent Bash) auto-proceed creating missing files without prompting, `--yes`/`-y` does the same at a real TTY — `--strip` is unaffected and always refuses non-interactively since it's destructive
./scripts/mavp-operator --strip <target-dir> [--keep-artifacts] # remove Mavericks files from a project (pre-publish cleanup); prints a git-recoverability manifest before any prompt, then two-stage confirm — plumbing [y/N], state artifacts (BACKLOG/TASK_STATUS/PROCESS_STATE/EXECUTION_LOG/SKILL_PROPOSALS) require typing "delete" if any state path is git-irrecoverable; --keep-artifacts skips the state group; requires an interactive TTY
./scripts/mavp-operator --apply-decomposition [FILE] # apply architect decomposition block to BACKLOG + TASK_STATUS
./scripts/mavp-operator --arm-recheck T-NNN --due YYYY-MM-DD [--interval 8w] [--note "..."] # register a time-based post-merge recheck for task T-NNN; auto-assigns next RC-N id; copies task title for self-containment
./scripts/mavp-operator --ack-recheck RC-N [--rearm] [--due YYYY-MM-DD] # acknowledge and remove a recheck; --rearm reschedules instead (without --due: sets due = today + interval, requires interval on the entry; with --due YYYY-MM-DD, today or later: sets due to that explicit date, no interval required — the way to extend a --park-wave --until park past its original date); --due is only valid together with --rearm
./scripts/mavp-operator --version # framework version
./scripts/mavp-operator --help # show all flags
- node scripts/mavp-install.js <dir> # bootstrap a new project (--check to preview, --update to re-sync; both also activate/refresh Claude Code hooks by default — --no-hooks to skip; --hooks-only activates/refreshes hooks only, no framework-file sync — the safe narrow command for the canonical self-install case)
+ node scripts/mavp-install.js <dir> # bootstrap a new project (--check to preview, --update to re-sync, which also seeds any missing project-owned artifact from its template without overwriting, skipped on self-install; both also activate/refresh Claude Code hooks by default — --no-hooks to skip; --hooks-only activates/refreshes hooks only, no framework-file sync — the safe narrow command for the canonical self-install case)
node scripts/mavp-validator.js # validate artifact sync
node --check scripts/<file>.js # syntax-check a script without running it
```
Validator exit codes: `0` = healthy, `1` = drifting, `2` = repair required.
## Architecture
**Direct-reference model** — bootstrapped projects do NOT copy core scripts. Their `scripts/mavp-operator` bash wrapper delegates to this mavericks installation via `MAVERICKS_PROJECT_ROOT` env var. Only project-specific scripts (agent.js, close-session.js) live in the project.
**Core layer** (`docs/core/`) — task lifecycle, roles (incl. UX sub-agent), orchestration rules, QA handoff, bootstrap guide.
**Live state** — `BACKLOG.md`, `TASK_STATUS.md`, `PROCESS_STATE.md` / `PROCESS_STATE.json`. `PROCESS_STATE.md` is auto-generated from `PROCESS_STATE.json` — do not edit it manually. `EXECUTION_LOG.md` records agent spawning events and key decisions; update it when spawning sub-agents or closing a wave.
**Shared library** — `scripts/mavp-operator-lib.js` contains all parsing, rendering, and utility functions used by the operator scripts. Read it before adding new operator commands to avoid re-implementing existing helpers.
**Sub-agent role definitions** — `.claude/agents/` contains per-role behavior specs (developer, qa, ux, product-docs, technical-writer, security-reviewer, frontend-design, ui-designer, exa-researcher, architect, analyst). Read these before briefing a sub-agent.
**Skill reflection loop** — `docs/SKILL_OPTIMIZATION.md` defines the SkillOpt-inspired system that mines past task outcomes and proposes bounded edits to role specs. Run via `--reflect-skill <role>` at wave close. Proposals land in `SKILL_PROPOSALS/` and require human review before any spec change is applied.
**Rules files** — `.claude/rules/` contains three supplementary constraint files (backlog.md, docs.md, scripts.md) that extend the conventions in this file. They are authoritative for their respective domains.
**Bootstrap** — `node scripts/mavp-install.js <target-dir>` seeds a new project with the bash wrapper, project-specific scripts, and artifact templates, and activates the Claude Code hooks (`SessionStart`, `PostCompact`, `PostToolUse`) in `.claude/settings.local.json` by default. Use `--check` to preview, `--update` to re-sync agents/rules/hooks — idempotent, and it only ever replaces the installer-managed hook entries, never operator-authored ones. Pass `--no-hooks` to `--update` to skip the hooks merge for that run. See `docs/core/BOOTSTRAP_GUIDE.md` — "Claude Code hooks activation" for the managed-entry ownership rule and the canonical self-activation step. VSCode projects whose sub-agent spawns silently stall should check the **VSCode Agent permissions** convention below (a conditional diagnostic, not a required setup step).
- **Pre-commit hook** — `.claude/hooks/pre-commit` runs the validator on every `git commit`. Exit code 2 blocks the commit. It fires via `git config core.hooksPath .claude/hooks/`, set automatically by `mavp-install.js` (see `docs/core/BOOTSTRAP_GUIDE.md`); while `core.hooksPath` is set this way, git ignores `.git/hooks` entirely. Liveness check: `git config core.hooksPath`.
+ **Pre-commit hook** — `.claude/hooks/pre-commit` runs the validator on every `git commit`. Exit code 2 blocks the commit. It fires via `git config core.hooksPath .claude/hooks/`, set automatically by `mavp-install.js` (see `docs/core/BOOTSTRAP_GUIDE.md`); while `core.hooksPath` is set this way, git ignores `.git/hooks` entirely. An optional adopter-owned `.claude/hooks/pre-commit.local` is run by a stanza at the end of the hook (non-zero exit blocks the commit; install and `--update` never touch it or `.claude/rules/project-*.md` — see `docs/core/BOOTSTRAP_GUIDE.md` — "Project checks"). Liveness check: `git config core.hooksPath`.
## Key conventions
- **Artifact-first truth** — state transitions must be in markdown artifacts before they are real.
- **Mirror rule** — every status change in `BACKLOG.md` must be mirrored in `TASK_STATUS.md` before the turn ends. `scripts/mavp-operator-sync-status.js` (run by the PostToolUse hook) mirrors exactly three things: the `- **Status:**` field, the task heading title (rewrites a stale TASK_STATUS heading to match BACKLOG's title, emitting `sync-status: retitled T-NNN`), and skeleton-entry creation for any BACKLOG Active Wave task missing from TASK_STATUS's Active tasks section — skipping `deprecated` tasks and any task carrying a real `Superseded by:` value. It never mirrors `Notes`, `Evidence`, or any other TASK_STATUS-owned field — those stay hand-edited. **Mirror-edit race** — because this hook rewrites TASK_STATUS.md immediately after a BACKLOG.md edit, an agent holding a stale read of TASK_STATUS.md can hit a "modified since read" error; edit BACKLOG.md first, re-read TASK_STATUS.md before touching its non-status fields, and prefer the atomic ritual commands (`--set-status` / `--update-status` / `--rename-task`) over hand-editing both files. See `docs/core/TASK_LIFECYCLE.md` — "Avoiding the mirror-edit race" for the full workflow note.
- **Run validator** after every `BACKLOG.md` or `TASK_STATUS.md` change.
- **PostToolUse hook — silent means no repair required** — a PostToolUse hook runs the validator after every Edit on `BACKLOG.md` or `TASK_STATUS.md`. The hook itself always exits 0. Full validator output is printed to stderr ONLY when the validator itself exits 2 (repair required) — exit 1 (drifting) stays silent at edit time (that advisory is already surfaced by `--agent`, `--snapshot`, `--close-session`, and the pre-commit hook, so it does not need per-edit stderr noise). So silence now covers BOTH validator exit 0 (healthy) and exit 1 (drifting) — continue immediately without pausing or waiting for output. Only when the hook prints the full validator report (validator exit 2) should the agent stop, read it, and handle the repair.
- **Doc-sync advisory** — after each TASK_STATUS.md edit the hook also runs `mavp-operator-doc-sync-check.js`, which inspects recently merged tasks and emits stderr advisories when source file changes may require a doc update. When the advisory names a candidate doc, spawn `product-docs` or `technical-writer` with that doc as the target. When it says "no obvious doc reference found", use judgment based on the task type. Repeat advisories for the same task may be ignored after first review. See `docs/core/DOC_SYNC.md` for full details. Bootstrapped projects get this automatically — the installer composes the doc-sync fragment into the managed `PostToolUse` hook on fresh install and on every `--update` (see `docs/core/BOOTSTRAP_GUIDE.md` — "Claude Code hooks activation"); `templates/doc-sync-hook.fragment.json` is the manual fallback for non-installer setups.
- **Main Agent owns transitions** — sub-agents do not approve their own work.
- **`next_action` is a routing directive, not a narrative log** — never embed a volatile fact asserting current state (a version number the repo currently sits at, commit counts, push state, external repo/mirror state) in it; a version literal naming the *target* of the directive's own action (e.g. "bump to 0.44.3") is not banned — reference the source of truth for state claims instead and put cross-session narrative in `HANDOFF.md` (the wave strategy note is cleared by every `--close-session`, so it is not a cross-session home — see the `next_action` entry below). See the `next_action` entry under "PROCESS_STATE.json fields" below for the full contract and the shipped `next_action_unverified` / `next_action_volatile_facts` signals (DR-009: this rule is validator-blocking, not advisory-only).
- **Wave versioning** — `PROCESS_STATE.json` has a `wave` field. Incremented automatically by `--close-session` on wave complete.
- **Version bump** — before closing a wave with `git push`, bump `scripts/mavp-version.js` if any file under `scripts/` changed (patch bump). New framework capabilities warrant a minor bump. Doc-only waves need no bump. Shippable-version bumps also add a matching `CHANGELOG.md` entry — see `docs/PUBLIC_RELEASE_STRATEGY.md` §5 (canonical release-notes source; GitHub Release bodies are derived copies). The bump trigger and the release-note trigger are separate: a doc-only wave that changes `ship`-classified content still owes a `CHANGELOG.md` entry, written under `## [Unreleased]` when no bump accompanies it — see §5's per-wave bullet.
- **UX review** — optional per task. Set `requires_ux: true` in backlog to activate `ux_review` stage.
- **Security review** — optional per task. Set `requires_security_review: true` in backlog to activate `security_review` stage between `dev_done` and `ready_for_qa`. Full review is required when a task adds or modifies external inputs or outputs: API endpoints, file parsers, auth flows, or third-party integrations. A lightweight self-checklist is sufficient for internal refactors that introduce no new attack surface.
- **Config check** — optional per task. Set `requires_config_check: true` on any task that adds or modifies runtime config keys (env vars, feature flags, secrets references, external service credentials). Tasks with this flag require a `config_check:` block in QA evidence before `qa_passed`. The `config_check:` block must list each config key confirmed present and correct in the target environment. The validator warns when a `qa_passed` or `merged` task has `requires_config_check: true` but no `config_check:` line in TASK_STATUS evidence.
- **Verification types** — each task declares one: `artifact` (validator/diff check), `runtime` (script executes without error), `visual` (human review required; build passing is not enough), `manual` (human review of copy or flow). QA must match the declared type. Tasks with `artifact` or `unit` verification type do not require a QA agent pass — their built-in verification (validator run / test suite) serves as QA. For `runtime` and `unit` tasks, include `commit: <hash>` in the evidence block when merging — the validator will warn if it's absent. Tasks with `verification_type: runtime` may optionally advance to `runtime_verified` after merge to record confirmed live-environment behavior (informational only — not required by the validator).
- **`exploration` task type** — use `- **Type:** exploration` for internal research tasks that produce a docs artifact (no deliverable code): data analysis, architectural assessments, simulations, any research whose output is a document. Required fields: `- **Output doc:** <path>` (the document to be created/updated), `- **Owner role:** main_agent`, `- **Verification type:** artifact`. The validator warns when an exploration task is missing the `Output doc:` field.
- **`commit:` in evidence** — all tasks set to `merged` must have `commit: <hash>` in their evidence block in TASK_STATUS.md. The validator will block (exit code 2) on merged tasks without it. Format is exactly `commit: <hash>` — lowercase key, colon, space, then the hash. Do not write `Commit abc1234` (capitalized) or `commit abc1234` (no colon) — both will cause `merged_missing_commit_field`.
- **`infra:` in evidence** — for infra-only tasks with no code commit (AWS SSM parameters, Terraform apply, config-only changes), use `infra: <verifiable-ref>` as an accepted alternative to `commit:`. The reference must be verifiable — the validator accepts: (a) an AWS ARN (`arn:aws:ssm:...`), (b) a git commit hash (7–40 hex chars, e.g. the runbook or CHANGELOG commit), (c) a Terraform state serial (`serial/N` or `serial:N`), or (d) an SSM parameter version (`@vN`). Free text without one of these patterns is rejected. If the task also lists `manual_changes:` evidence, the `manual_changes:` rule (a codifying commit is required before `merged`) still applies unless the change is purely infra with no config file edits.
- **`artifact:` in evidence** — for `verification_type: artifact` tasks that produce no code diff (exploration tasks, initiative audits, research documents), use `artifact: <description>` as an accepted alternative to `commit:`. The description is free text naming the produced artifact (e.g. `artifact: docs/AUDIT.md`). This field is only accepted when the task's `Verification type` is `artifact`; for all other verification types `commit:` or `infra:` is still required.
- **Cross-repo evidence format** — single-repo tasks use `commit: <hash>`. Multi-repo tasks (declared with `- **Repos:** repo-a, repo-b`) require one evidence line per repo containing both `commit:` and the repo name: `commit: <hash-a> (repo-a)` and `commit: <hash-b> (repo-b)`. The validator warns when a merged multi-repo task lacks per-repo commit evidence.
- **`branch:` in dev_done evidence** — projects with branch-based deploy contours should populate `branch: <name>` (e.g. `main`, `develop`, `both`) in dev_done evidence alongside `commit:`. The validator warns when a dev_done task has a `commit:` line but no `branch:` line.
- **`repo:` field** — tasks in `in_progress` or later should declare `- **Repo:** <name>` (or `- **Repos:** repo-a, repo-b` for cross-repo tasks) in BACKLOG.md. The validator warns when this field is missing for in-flight tasks. The `--agent` JSON includes `repo` for each active slice.
- **`stale_risk:` field** — set `- **Stale risk:** true` on tasks that touch cached data, ML models, or long-lived config. The validator warns when such a task is in `in_progress` or later without `stale_verified: true` in the evidence block.
- **Recheck mechanism** — `rechecks` in `PROCESS_STATE.json` stores time-based, post-merge follow-up reminders: e.g. "retrain this model 8 weeks after merge" or "re-audit this config next quarter". An entry is either task-keyed (`task: T-NNN`, armed via `--arm-recheck`) or wave-keyed (`wave: N`, no `task` key, armed via `--park-wave --until` — see the **Parked waves** convention below). Use `--arm-recheck T-NNN --due YYYY-MM-DD [--interval 8w]` to register a task recheck; use `--ack-recheck RC-N` to dismiss it, or `--ack-recheck RC-N --rearm [--due YYYY-MM-DD]` to reschedule it (by its interval, or to an explicit date when `--due` is given — the only way to reschedule an interval-less wave-keyed entry). Every read-only surface judges due/overdue against the real clock, not `PROCESS_STATE.json`'s `last_updated` field, so a recheck due today always surfaces even if no state artifact was touched since it came due. Due rechecks surface in: the `--agent` JSON `due_rechecks[]` field (present only when entries are due, each flagged `overdue` when past due); a session-start callout; a `--snapshot` line; the operator dashboard "Due Rechecks" panel; and a non-blocking `overdue_recheck` advisory in the validator (info severity only — never blocks). Nothing fires between sessions — the framework has no daemon — so a team wanting a reminder outside a live session should run `./scripts/mavp-operator --snapshot` from an OS scheduler and grep its output for "Due rechecks:". Distinct from `stale_risk:` (see next note): `stale_risk` is a binary flag on an IN-FLIGHT task that guards staleness verification BEFORE merge; `rechecks` schedule a future revisit AFTER the task is already merged and archived. They are complementary — `stale_risk` covers the in-flight window; a recheck covers the post-merge interval.
- **`Touches:` field** — optional. List file paths a task will modify: `- **Touches:** path/to/file.js, path/to/other.md`. The `--snapshot` command warns when two active tasks in the same wave declare the same file. Fill it at planning time when you know the files involved.
- **`Root cause:` field** — optional. Reference a task that closes the underlying structural cause: `- **Root cause:** T-NNN`. Use when a task is a symptom of a deeper structural problem already tracked by another task. Purely informational — the validator does not check this field.
- **`Reopen trigger:` field** — optional, BACKLOG-only. Use `- **Reopen trigger:** <condition>` on `deferred` or `deprecated` task blocks to state the real-world condition under which the task should be reopened or a new task registered (e.g. `- **Reopen trigger:** reopen once T-665 shows whether non-bypass sessions actually occur`). Explicitly pull-consumed, not mechanically enforced: read it by running `grep -n "Reopen trigger:" BACKLOG.md` at wave open and during deferred-queue audits — there is no validator check, no runtime surfacing, and absence is never a finding. Normalizing existing prose-form activation conditions into this field is a Main-Agent state action, done outside of any single task.
- **`Origin:` field** — optional, BACKLOG-only (never mirrored into `TASK_STATUS.md`). Records how a task entered the backlog. `--apply-decomposition` stamps `- **Origin:** architect` on every task it registers — that path IS the mandatory architect gate. `--new-task` and `--quick-task` stamp `- **Origin:** manual` by default and print a whole-line advisory naming the architect gate whenever origin resolves to manual; pass `--origin architect` to attest the task was actually architect-decomposed despite being registered through one of these commands, which suppresses the advisory and stamps `architect` instead. This is an advisory signal by design, not enforcement: absence of `Origin:` on pre-existing tasks is never a finding, and no validator check is added — the field exists to give `scripts/mavp-skill-reflect.js` and future counting surfaces a grep-able trail of gate bypasses.
- **`Superseded by:` field** — optional. Use `- **Superseded by:** T-NNN` when a task is absorbed by another task (T-NNN). The superseded task is treated as terminal — the validator skips all state-validation checks for it (no `missing_in_task_status`, no evidence warnings, no status-mismatch checks). Distinct from `Root cause:` (which marks structural cause, not absorption). A superseded task does not need to appear in TASK_STATUS.md.
- **`manual_changes:` in evidence** — optional field in `TASK_STATUS.md` evidence. List any operations performed outside version control (CLI commands, direct config edits, DB patches). If `manual_changes:` is non-empty, a corresponding code/config commit codifying those changes is required before `merged`.
- **`needs_fix_rounds:` in evidence** — optional field in `TASK_STATUS.md` evidence. Record how many `needs_fix` cycles the task went through before `qa_passed` (e.g. `needs_fix_rounds: 2`). Fill when a task enters `needs_fix` at least once. Omit (or leave `0`) for tasks that passed QA on the first attempt. This is the primary signal for the skill reflection system (`mavp-skill-reflect.js`) to detect developer friction; it takes priority over keyword heuristics.
- **`validator_blocked:` in evidence** — optional boolean field in `TASK_STATUS.md` evidence. Set to `true` when the validator (exit code 2) ever blocked a commit on this task; set to `false` or omit when the validator never blocked. Used by the skill reflection system as a quality signal alongside `needs_fix_rounds:`.
- **Module registry** — `docs/MODULES.md` declares module types (e.g. `web-panel`, `antispam`). Tasks may declare `- **Module:** <id>`. The `--agent` JSON includes `module` and `context_docs` for each active slice. The validator warns on unknown module IDs.
- **Repo map** — `docs/REPO_MAP.md` declares repo entries (id, label, path, domain, deploy_path, downstream, docs), following the same project-owns-instance pattern as `docs/MODULES.md` — the framework only defines the schema in `docs/REPO_MAP.md`; each project maintains its own registry, seeded from `templates/REPO_MAP_TEMPLATE.md` by `mavp-install.js`. `parseRepoMap()` in `scripts/mavp-operator-lib.js` reads the registry and returns an empty map when the file is absent. The validator warns with `unknown_repo_id` when a task's `Repo:`/`Repos:` value is not a known ID, and skips the check silently when there is no repo map (or it declares no real entries).
- **`Blocked by:` field** — optional cross-repo relation: `- **Blocked by:** <repo>/T-NNN` (comma-separated for multiple). Distinct from same-repo `Depends on:`. The validator resolves `<repo>` to a local path via `docs/REPO_MAP.md`'s `path` field, reads that repo's BACKLOG.md/TASK_STATUS.md for the blocker's status, and gates the merge: `blocked_by_open` at FAILURE severity (exit 2) only when a `merged` task has a non-`merged` blocker — shipping ahead of an unmet dependency; WARNING (exit 1 at worst) when the blocked task is `qa_passed` or `ready_for_qa`, since a task correctly waiting on its blocker is not a violation (DR-005); and `blocked_by_unresolvable` at INFO severity when the repo id/path or the blocker task can't be resolved. `--agent` surfaces an additive `blocked_by: [{repo, taskId}, ...]` per active slice when declared. When the target repo can't resolve the blocker (T-456), the validator falls back to the validating repo's own backlog for hub-model projects that track all cross-repo tasks in one hub backlog, accepting the match only when the local task's Repo/Repos field includes the referenced repo id. Mirroring a hub-tracked task's status to reflect a downstream repo's actual progress is Main-Agent-owned, done via batch `--set-status`.
- **`Hold:` field** — optional (DR-005): `- **Hold:** <what> — <why> (<since>)`, e.g. `- **Hold:** prod — waiting on a coordinated deploy window (2026-07-24)`. Marks a task as deliberately held, not stalled. `--agent` surfaces a parsed `hold: {what, why, since}` per active slice; `--snapshot` lists held tasks separately from waits/blockers. A `Hold:` may downgrade ONLY the `blocked_by_open` advisory (`HOLD_DOWNGRADABLE_CHECKS` in `mavp-validator.js` — a whitelist, not a blacklist of protected checks) and never a FAILURE-severity finding — the `merged` × unmerged-blocker tier stays FAILURE/exit 2 even with a `Hold:` present. It can never downgrade `merged_missing_commit_field`, evidence-completeness, mirror/sync-status, duplicate-entry detection, `config_check`, or `stale_verified`. Absence of `Hold:` is never a finding.
- **Context prefetch bundle** — `--new-task`, `--quick-task`, and `--apply-decomposition` write a `.mavp/context/T-NNN.md` bundle at task registration (regenerated by `--update-task` and `--rescope-task`) containing the task block, its module's `context_docs`, its `Touches:` list, its repo-map entry, and its `Depends on:` / `Blocked by:` references. Built by `buildContextBundle()`/`writeContextBundle()` in `scripts/mavp-operator-lib.js`; degrades gracefully (omits a section) when the repo map or module registry is absent or has no matching entry. This is a **Main-Agent-only brief-composition input**, never a sub-agent read path: `.mavp/context/` is gitignored and does not exist inside worktree checkouts, so a brief must never instruct a worktree sub-agent to read that path. The Main Agent retrieves the bundle via `./scripts/mavp-operator --emit-bundle T-NNN` (read-only, prints to stdout) and injects the relevant content directly into the sub-agent brief text. `--agent` surfaces an additive `context_bundle` path per active slice only when the file exists on disk.
- - **Must-read set** — `--agent` surfaces an additive `must_read` array at session start: files changed since the previous `--close-session` commit (found via git by matching the `chore: close session ...` marker) unioned with `context_docs` already resolved onto in-flight tasks. Computed by `computeMustRead()`/`getFilesChangedSincePreviousCloseSession()`/`findPreviousCloseSessionCommit()` in `scripts/mavp-operator-lib.js`; omitted entirely when empty, and degrades silently (no throw) when git is unavailable. Rendered by the `session-start` skill as a "Must read" list.
- - **Artifact brevity lint** — two info-severity, never-blocking validator advisories keep state artifacts lean: `artifact_size_budget` fires when `CLAUDE.md` or `HANDOFF.md` (whole-file, flat budget) or the `BACKLOG.md` Active Wave / `TASK_STATUS.md` Active tasks sections exceed their budget (archived wave sections are never counted); `state_in_claude_md` fires when `CLAUDE.md` contains task-state-shaped lines (`### T-NNN` headings or `- **Status:**` fields — task state belongs only in `BACKLOG.md`/`TASK_STATUS.md`). The two Active-section budgets scale with the number of active tasks in that section: each resolves to `max(static_default, per_task_allowance × active_task_count)` (15 lines/task for BACKLOG Active Wave, 10 lines/task for TASK_STATUS Active tasks — see `mavp-validator.js` for the constants), so a legitimately large wave of many small tasks doesn't permanently trip the advisory. All budgets (including the two scaled ones, where an explicit override always wins over the computed value) are overridable per-field via an `artifact_budgets` object in `PROCESS_STATE.json` (e.g. `{"claude_md_max_lines": 400}`).
+ - **Must-read set** — `--agent` surfaces an additive `must_read` array at session start: files changed since the previous `--close-session` commit (found via git by matching the `chore: close session ...` marker) unioned with `context_docs` already resolved onto in-flight tasks. Computed by `computeMustRead()`/`getFilesChangedSincePreviousCloseSession()`/`findPreviousCloseSessionCommit()` in `scripts/mavp-operator-lib.js`; omitted entirely when empty, and degrades silently (no throw) when git is unavailable. The ledgers and live-state files (`MUST_READ_EXCLUDED_PATHS`: `BACKLOG.md`, `TASK_STATUS.md`, `PROCESS_STATE.json`, `PROCESS_STATE.md`, `EXECUTION_LOG.md`) are excluded by construction, and any remaining file whose decoded length exceeds `artifact_budgets.must_read_max_chars` (default 40000) moves to the additive `must_read_oversized: [{path, chars}]` field (emitted only when non-empty; split by `splitMustReadOversized()`). Rendered by the `session-start` skill as a "Must read" list plus a separate "Oversized (search, do not read whole)" list.
+ - **Artifact brevity lint** — three info-severity, never-blocking validator advisories keep state artifacts lean: `artifact_size_budget` fires when `CLAUDE.md` or `HANDOFF.md` (whole-file, flat budget) or the `BACKLOG.md` Active Wave / `TASK_STATUS.md` Active tasks sections exceed their budget (archived wave sections are never counted); `state_in_claude_md` fires when `CLAUDE.md` contains task-state-shaped lines (`### T-NNN` headings or `- **Status:**` fields — task state belongs only in `BACKLOG.md`/`TASK_STATUS.md`); `context_size_budget` (T-782) measures the always-loaded context in characters — `CLAUDE.md` plus its recursive `@` imports (max four hops, resolved relative to the importing file, code spans and fenced blocks skipped) plus `.claude/rules` files that carry no `paths:` frontmatter — via `collectAlwaysLoadedContext()` in `scripts/mavp-operator-lib.js`, and fires once when the total exceeds `always_loaded_max_chars` (default 160000; the message names the top five contributors) and once per imported doc, unscoped rule, or module `context_docs` file over `imported_doc_max_chars` (default 40000). `claude_md_max_lines` is unchanged. The two Active-section budgets scale with the number of active tasks in that section: each resolves to `max(static_default, per_task_allowance × active_task_count)` (15 lines/task for BACKLOG Active Wave, 10 lines/task for TASK_STATUS Active tasks — see `mavp-validator.js` for the constants), so a legitimately large wave of many small tasks doesn't permanently trip the advisory. All budgets (including the two scaled ones, where an explicit override always wins over the computed value) are overridable per-field via an `artifact_budgets` object in `PROCESS_STATE.json` (e.g. `{"claude_md_max_lines": 400}`).
- **`Prod prerequisites:` field** — optional. List infra/env items required before prod deploy: `- **Prod prerequisites:** prod CI workflow, ECR repo, secrets rotation`. The `--agent` deploy_queue surfaces these as `prod_prerequisites: [...]` per task, and the `next_action` deploy summary notes how many tasks have prerequisites outstanding.
- **Wave closure** — after the last task in a wave reaches `merged`, run the full test suite once, unfiltered, on main first — this is what catches a real failure before it reaches everyone else's CI, not a formality (`node scripts/run-tests.js`, enforced at push time by the canonical repo's pre-push hook; see `docs/core/ORCHESTRATION_RULES.md` — "Test-execution scope (worktree developers)") — then run `--close-session` then `git push`. The next wave must not open until the push is complete **and** CI on the pushed commit is green — confirm with a `gh run` check against that commit before starting the next wave's first task. Canonical `main` sat CI-red across two consecutive pushes (2026-08-21) because this contract stopped at "push is complete"; do not reopen that gap. A state-only commit (e.g. `chore: close session`) is skipped by the CI workflow's path filter and so has **no run at all** — absence of a run is not evidence of a green run; if the pushed tip has no run, walk back to the newest pushed commit that does have one and judge that run instead. Where possible, push the wave's content commits and the close-session commit together in a single `git push`: the triggered run then lands on the true tip (the union of changed files includes content, so the filter no longer skips it), which also removes the close-session-commit-lands-mid-run staleness race that caused the 2026-08-21 red at its origin.
- **Parked waves** — if a wave must remain open while a new wave starts, use `--park-wave [N] --reason "text" [--until YYYY-MM-DD]` (rather than hand-editing `parked_waves`): it relocates wave N's task blocks (and its trailing non-task sub-heading, so the same heading resurfaces on unpark) out of BACKLOG.md's Active Wave / TASK_STATUS.md's Active tasks into `## Wave N — Parked` / `## Parked tasks (Wave N)` sections (so the parked wave stops bloating session-start, size budgets, and next-action routing) and records `"Wave N — <reason>"` in `parked_waves`. A park may carry a resume date via `--until`, which arms a wave-keyed recheck (see the **Recheck mechanism** convention above) that surfaces as a due recheck on that date. Do not silently overlap waves. Use `--unpark-wave <N>` to restore the wave byte-for-byte when it resumes — this also auto-acks any `--until` recheck armed for that wave.
- **VSCode Agent permissions (conditional diagnostic)** — if a sub-agent spawn silently stalls or produces no output in the VSCode Claude Code extension (no dialog, no error message, no prompt — it just appears to hang or refuse), check whether `"Agent(*)"` is present in the `permissions.allow` array of `.claude/settings.local.json`. Some extension versions have been observed to silently auto-reject every Agent tool call without it. This is not a universal requirement: on 2026-08-15, `Agent(*)` was absent from both settings files and sub-agent spawns succeeded, so treat this as a cheap thing to check when a spawn misbehaves, not a mandatory setup step. Fix if it applies: add `"Agent(*)"` manually, or re-run `mavp-install --update` if the installer has been updated to include it.
- **Mandatory pre-push review** — the shipped framework default is `permissions.defaultMode: "bypassPermissions"` (see `docs/core/BOOTSTRAP_GUIDE.md` — "Shared permission-mode default"), which suppresses interactive prompts for every tool call. Under this mode, `--close-session` always prints the session-completed results table before any push happens, and `--close-session` never auto-pushes on its own: in non-interactive mode `--push` is ignored and a gate message is printed instead of pushing; in interactive mode the `[Y/n]` push prompt still requires an explicit human answer. Either way, a human must review the results table and personally trigger `git push` — this is the single human checkpoint in an otherwise fully autonomous default permission mode. Do not add any code path that pushes without this review step.
- **Lineage linkage — `EXECUTION_LOG.md` entry convention** — every `EXECUTION_LOG.md` entry must carry inline `T-NNN` and `DR-NNN` references for any task or decision it touches, so grepping a single `DR-NNN` id surfaces the decision record, every log entry that acted on it, and every task citing it. Pairs with the optional `Informed by:` / `Supersedes:` / `Tasks:` / `Session:` fields on decision records — see `docs/core/DECISIONS.md`. No graph-rendering tooling; this is plain grep over text artifacts.
- **Per-spawn outcome — `EXECUTION_LOG.md` entry convention** — every sub-agent spawn's entry also records `tool_uses: N/cap` and `outcome: completed|cap_hit|infra_failure`, making the truncation rate greppable instead of eyeballed. The `cap` denominator IS the role's frontmatter `maxTurns` at spawn time (`.claude/agents/<role>.md`); any other denominator must name its source inline. Several recent entries carry a denominator matching neither the role's frontmatter value at spawn time nor a stated source — the artifacts alone cannot settle whether that reflects convention drift or a cap set outside this documented mechanism; treat it as an open question, not a resolved one. `outcome` follows `docs/core/ORCHESTRATION_RULES.md` — "Cap-hit triage": `completed` when the report ends in the completion token — this token-present case was already correct and is deliberately unchanged, even when `tool_uses` sits at or over cap; `cap_hit` when the token is absent and `tool_uses >= cap`; `infra_failure` when the token is absent and `tool_uses < cap`. A convention over plain text, not a parser — harness-owned session JSONL carries no format contract to build one against. An architect spawn's model label cites that report's own "Model self-report" opening line (see `docs/AGENT_SPEC.md` — "Model self-report"), never the Main Agent's spawn intent — forward-only, since `EXECUTION_LOG.md` entries are frozen and never retro-edited once written.
- **Registration home — `EXECUTION_LOG.md` entry convention** — a log entry is narrative, never a registration surface. Any reopen trigger, accepted boundary, or carried-forward finding recorded in an `EXECUTION_LOG.md` entry must also land, in the same session, in the artifact its consumer actually reads: a `Reopen trigger:` field in `BACKLOG.md`, a `docs/core/GATE_LEDGER.md` row, or a registered task. No validator check and no hook — same pull-consumed posture as the `Reopen trigger:` field itself (see the `Reopen trigger:` field convention above).
- **RCA-to-codification** — every root-cause analysis document must end in a mandatory Codification section that routes each root cause to exactly one of: a `.claude/rules` edit proposal, a role-spec proposal via `SKILL_PROPOSALS/`, a memory-index entry, an armed recheck, or a mechanical enforcement change (hook/validator/test, filed as a developer task); the Main Agent registers a follow-up task per accepted routing. The memory route is residual, not the default, and the same table governs durable lessons noticed outside any RCA — see `docs/core/RCA_CODIFICATION.md` — "The portability tie-break" and "Codification outside an RCA" (DR-013). See also `templates/RCA_TEMPLATE.md`.
- **Publish-manifest registration** — every new git-tracked file must be classified in `scripts/publish-manifest.json` (ship/exclude) in the same task that creates it; see `.claude/rules/docs.md` / `.claude/rules/scripts.md` for the full rule and the `MANIFEST_REGISTRATION_NEEDED` token scope-forbidden roles use. The Main Agent runs `node scripts/check-publish-manifest.js` before booking `dev_done` on any task that created new files. Per DR-002 (`docs/core/DECISIONS.md`), the Main Agent may apply registration **entries** directly (a state-adjacent classification ledger action) but all other manifest changes go through a developer task. Enforcement is no longer only manual (T-401): `scripts/mavp-manifest-guard.js` gives an advisory creation-time signal via PostToolUse (see `templates/manifest-guard-hook.fragment.json`), and `.claude/hooks/pre-commit` runs `node scripts/check-publish-manifest.js --if-canonical` as a blocking commit-time backstop — both gated to the canonical private repo (inert in the public mirror / adopter repos) via the "every `exclude` key is git-tracked" heuristic. **A developer brief must never forbid this registration line, and must never route a developer's registration through the `MANIFEST_REGISTRATION_NEEDED` token** — the token exists only for roles whose scope forbids `scripts/` (`product-docs`, `technical-writer`); a developer always self-registers in the same commit, and the pre-commit backstop above makes this mandatory, not a style choice, since a worktree with an unregistered new file cannot produce a commit at all. See DR-007 (`docs/core/DECISIONS.md`) for the incident this closes and the parallel-conflict merge protocol that still applies when two developer tasks' registrations collide.
## Orchestrator checklist — before touching any file
The Main Agent is an orchestrator, not an implementer. Before writing code or editing docs directly:
**Before creating tasks — mandatory architect gate:**
Architect decomposition is required before any sub-agent is spawned for any task, with one sanctioned exception — the XS fast lane (`--quick-merge`); see `docs/core/ORCHESTRATION_RULES.md` — "XS fast lane (quick-merge)". The Main Agent's role at this stage is context provider: pass the raw task description and relevant codebase context to the architect. The architect is the decomposition owner — it reads the codebase, reasons about fit, and returns a structured design brief with a machine-readable task decomposition block. The Main Agent passes `initiative` and `wave_goal` verbatim into every architect brief. When the highest-value next step is an operator action rather than a task, `next_action` names the operator under its existing standalone-imperative form instead of manufacturing a sub-agent task.
- Spawn **architect** first. Provide the raw feature idea or problem statement plus any relevant file paths or prior context. Do not pre-scope the work — let the architect determine the correct task boundaries.
- For simple or well-understood tasks the architect will return a minimal single-task decomposition quickly. The gate is still required.
- If the feature additionally requires a technology choice, library/API selection, or external landscape research, spawn **analyst** first for that research, then pass the analyst brief to the architect as additional context.
- Neither architect nor analyst produces BACKLOG tasks directly. Their briefs inform the Main Agent before item 1 below.
- **Architect model spawn rule** — spawn architect with `model: fable` (Fable 5, primary); if unavailable, re-spawn with `model: opus` (latest Opus); never spawn architect below Opus. Unavailable = two consecutive loud spawn failures on `model: fable` (retry Fable once first); the architect's frontmatter `model:` is the no-override default enforcing the Opus floor, not that fallback — see `docs/AGENT_SPEC.md` — "Model selection" for both, and DR-015. Worker sub-agents run on the `sonnet` alias declared in their own spec frontmatter. See `docs/AGENT_SPEC.md` — "Model selection" (worker model-escalation table) and "Effort selection" (effort-selection table), the single source of truth for both policies.
1. Is the task registered in `BACKLOG.md`?
2. Is the sub-agent type identified (developer / product-docs / technical-writer / qa / ux / security-reviewer / exa-researcher / architect / analyst)?
3. Is the sub-agent brief drafted (see template below)?
If all three are yes — spawn the sub-agent, then add T-XXX to `active_slices` in `PROCESS_STATE.json`.
Task registration in `BACKLOG.md`/`TASK_STATUS.md` is Main-Agent-only and must never appear in a sub-agent brief — briefs reference an already-registered T-NNN, they never ask a sub-agent to create or edit one.
**Direct action is allowed only for changes to state artifact files: `BACKLOG.md`, `TASK_STATUS.md`, `PROCESS_STATE.json`, `PROCESS_STATE.md`.** Any change to code, CSS, docs content, or scripts must go through a sub-agent — no exceptions, no "quick fix" shortcuts.
When a sub-agent completes: remove T-XXX from `active_slices`.
**Worktree integration** — when integrating developer work from a harness worktree, always integrate by the agent's commit (cherry-pick/merge), never by copying files; and inspect `git status` before integrating to recover any orphaned uncommitted work; when a task must read current main state, pre-authorize `git merge --ff-only main` in the brief; record the on-branch hash the cherry-pick/merge itself produces as `commit:` evidence, never the sub-agent's worktree hash. Before integrating any worktree wave, also run `git status --porcelain` on the shared main checkout and restore any unattributable modification with `git checkout HEAD -- <path>` (not a bare `git checkout <path>`, which restores from the index) — this is a Main-Agent-side backstop, not a closed interception gap. See `docs/core/ORCHESTRATION_RULES.md` — "Worktree integration — Main Agent".
## Sub-agent brief template
Use this when spawning any sub-agent. Include all fields.
```
Role: [developer | product-docs | technical-writer | qa | ux | security-reviewer | frontend-design | ui-designer | exa-researcher | architect | analyst]
Slice: T-XXX — [title from BACKLOG.md]
Goal: [one sentence — what this sub-agent must achieve]
work_dir: [absolute path to target repo] # cross-repo only — OMIT for same-repo (mavericks) tasks; developer uses CWD (worktree root) by default
Adjacent docs read: [for cross-repo tasks: list each <repo>/CLAUDE.md you read before starting — found / not found. Omit for same-repo tasks.]
Module: [optional — module id from docs/MODULES.md, e.g. web-panel]
Repo: [optional — repo name(s) this task touches, e.g. example-service]
Stale risk: [true | false — set true if task touches cached data, ML models, or long-lived config]
Read current main: [optional — set when the task must READ current-main state (e.g. version numbers, files merged earlier this session) OR when the task's own registration commit postdates the session's starting HEAD (true of nearly every task registered this session — its own worktree can't see its contract otherwise); pre-authorizes `git merge --ff-only main` from the worktree root as the sub-agent's first step; harness worktrees branch from a stale base — see docs/core/ORCHESTRATION_RULES.md "Worktree integration — Main Agent". Omit otherwise.]
Base floor: [the target repo's base-branch head hash at spawn time (`git -C <target-repo> rev-parse --short <base-branch>`) — REQUIRED for cross-repo and branch-based-repo worktree spawns, recommended otherwise. The developer runs `git log --oneline HEAD..<floor>` before any edit and quotes the output: empty = proceed, non-empty = stale base (stop), `fatal: Invalid revision range` = wrong repo (stop). Harness worktree base AND placement are untrusted — see docs/core/ORCHESTRATION_RULES.md "GAP C"/"GAP E". When `Read current main:` is also set, the ff-merge runs first and this check second. Omit only when neither condition applies; when omitted the developer proceeds as today.]
Model: [opus | sonnet] # optional — only include when escalating a worker away from its sonnet default; see docs/AGENT_SPEC.md — Model selection
Effort: [medium | high | xhigh | max] # optional — only include when deviating from the session default; see docs/AGENT_SPEC.md — Effort selection
Turn budget: [role's maxTurns from docs/AGENT_SPEC.md's per-role table] # optional override/retry channel — most roles now self-state their budget in their own spec (see docs/AGENT_SPEC.md — "Budget awareness"); fill this on a retry after a cap-hit, or when deviating from the role's own stated number; see docs/core/ORCHESTRATION_RULES.md — "Cap-hit triage"
Test scope: [worktree developer only — seed a `node scripts/run-tests.js --filter <fragment>` baseline; before seeding, confirm the fragment substring-matches the basenames of the test files the task is expected to add or touch (a filename-level check, no execution needed), and prefer the widest subsystem fragment over the most specific script name — a too-narrow fragment misses sibling coverage (T-680: `publish-release` vs the wider `publish`) or excludes the task's own new test file entirely (T-679: `install` could not match `test-wrapper-*`, so it never ran in the worktree); the developer extends the seeded fragment with its own test files and grep-derived coverage and reports the delta; never instruct the full suite in a worktree — see docs/core/ORCHESTRATION_RULES.md — "Test-execution scope (worktree developers)"]
Files to modify: [explicit list]
What NOT to change: [boundaries — other files, other tasks]
Definition of done: [acceptance criteria verbatim from BACKLOG.md]
Report back: changed files + line ranges, confirmation criteria met, any blockers
Before exiting: commit all changes with a meaningful message, then end your final report with the completion token — literal last line, `MAVP_REPORT role=<role> task=<T-NNN|n/a> verdict=<done|blocked|needs_fix|pass|fail>` — see `docs/AGENT_SPEC.md` — "Report completion token".
```
## Task lifecycle states
`planned` → `in_progress` → `dev_done` → [`ux_review` →] `ready_for_qa` → `qa_passed` → `merged` → [`runtime_verified`] → [`deployed_dev` →] [`deployed_prod`]
UX: `dev_done` → `ux_review` → `ux_passed` → `ready_for_qa` (or `ux_needs_fix` → developer)
Security: `dev_done` → `security_review` → `security_passed` → `ready_for_qa` (or `security_needs_fix` → developer)
Deploy statuses (`deployed_dev`, `deployed_prod`) are optional. Projects without explicit deploy contours stay on `merged` as their final state. The `--agent` JSON output surfaces a `deploy_queue` array listing tasks that are `merged` but not yet deployed.
**Runtime verification** — `runtime_verified` is optional. Use it on tasks with `verification_type: runtime` when you want to explicitly record that the behavior was confirmed in a live environment after merge. Tasks may remain at `merged` without proceeding to `runtime_verified`.
**`deprecated` status** — for tasks rejected permanently (as opposed to `deferred` which may return). Tasks with `deprecated` status skip `missing_in_backlog` and `missing_in_task_status` validator checks, require no evidence, and do not appear in active counts. Use `deferred` when a task is postponed; use `deprecated` when it will never be done.
## Backlog rules
- Every status change in `BACKLOG.md` must mirror in `TASK_STATUS.md` and vice versa.
- Never mark `merged` without QA evidence in `TASK_STATUS.md`.
- Task IDs are sequential integers (`T-NNN`). Never reuse a retired ID.
- Use `--new-task` to add tasks — prevents drift from first write.
## PROCESS_STATE.json fields
```json
{
"initiative": "...",
"stage": "execution",
"wave": 1,
"wave_status": "planning",
"wave_goal": null,
"parked_waves": [],
"active_slices": ["T-001", "T-002"],
"next_action": "T-001 → developer → ...",
"blocker": null,
"stage_owner": "main_agent",
"last_task_id": 2,
"last_updated": "YYYY-MM-DD",
"deploy_contours": 0,
"wave_summary": "one-sentence summary written by --close-session at wave end",
"rechecks": []
}
```
`wave_status` — tracks architect review gate and wave lifecycle. Values: `planning` (default, wave being scoped) | `architect_reviewed` (architect review complete, tasks may proceed) | `execution` (tasks actively in progress). Waves with ≥3 planned tasks require architect review before the first task starts — Main Agent sets this to `architect_reviewed` after review. `--close-session` resets `wave_status` to `planning` when a wave advances (all tasks merged) — the ending wave's gate state must not carry forward onto the freshly opened wave (T-653). There is no `closed` value: the close and the wave-counter advance are one atomic write, so a wave never sits in a persisted "closed" state — `wave_summary` is the record of what the closed wave accomplished.
`wave_goal` — optional string. One-line definition of done for the current wave. Set at wave open or via `--close-session` prompt. Displayed in `--agent` digest and `--snapshot`. Default: null.
`wave_summary` — written automatically by `--close-session` at the end of each wave; one sentence describing what the wave accomplished.
`rechecks` — optional array of time-based post-merge follow-up entries. Absent or empty means no rechecks and changes no existing behavior. Each entry is self-contained so it survives task archival: `id` (e.g. `"RC-1"`, sequential, assigned at arm time), `task` (e.g. `"T-123"`; optional when `wave` is present instead), `wave` (optional integer, e.g. `2`; a wave-keyed entry armed by `--park-wave --until` carries no `task` key), `title` (task title copied in at arm time, or an explicit title for a wave entry), `due` (absolute date `"YYYY-MM-DD"` when the recheck comes due), `interval` (optional, e.g. `"8w"`; supports weeks `w` and days `d`; used by `--ack-recheck --rearm` when no explicit `--due` is given), `armed_at` (`"YYYY-MM-DD"` when the recheck was created), `note` (optional free text). Managed via `--arm-recheck`, `--park-wave --until`, and `--ack-recheck`; to extend a park past its original date, use `--ack-recheck RC-N --rearm --due YYYY-MM-DD` rather than hand-editing `due`. See the **Recheck mechanism** convention below.
`parked_waves` — list of waves that remain open while a new wave proceeds; each entry is a string with the wave number and a one-line reason (e.g., `"Wave 2 — blocked on external review"`). Maintained via `--park-wave`/`--unpark-wave` (see the **Parked waves** convention below), which also relocate the wave's task blocks — not hand-edited directly.
`active_slices` — derived automatically by `--agent` from TASK_STATUS.md: only tasks with in-flight statuses (`in_progress`, `needs_fix`, `dev_done`, `ux_review`, `ux_passed`, `ux_needs_fix`, `security_review`, `security_passed`, `security_needs_fix`, `ready_for_qa`, `qa_in_progress`) are included — the single shared source of truth is `IN_FLIGHT_STATUSES` in `scripts/mavp-operator-lib.js`. The JSON field is informational only; `planned`, `qa_passed` (awaits the Main-Agent merge ritual, not sub-agent work), and `merged` tasks are excluded from the output.
`next_action` — a **routing directive only**, not a narrative log. Write it as either `T-NNN → role → short imperative action` (e.g. `"T-001 → developer → implement the parser"`) or, when no task is active, one short standalone imperative (e.g. `"Open next wave"`). It must NEVER embed a volatile fact that has no invalidation trigger once written — a claim that ASSERTS current state (`"the repo is at 0.42.1"`, `"currently on v1.2.3"`), unpushed-commit counts, ahead/behind push state, or external repo/mirror version state are all banned regardless of position. A version literal is judged by **position**, not by its mere presence (T-628/DR-009): one that names the **action-target** of the directive itself — the "`<verb> to X`" construction (`"bump to 0.44.3"`) or a literal immediately followed, with no intervening word, by a noun from a closed enumerated set (e.g. `"0.44.3 bump"`, `"the 0.44.3 section"`) — is exactly the fact the next developer/writer needs to complete the directive, so it is NOT flagged. That noun set's source of truth is the `ACTION_TARGET_FOLLOWER_NOUNS` constant in `scripts/mavp-operator-lib.js` (read by both `classifyNextAction()` and the validator's remediation text) — consult it directly rather than treating any list written here as normative. Adjacency is strict: a version literal separated from a follower noun by an intervening word still flags — `"the 0.44.3 stable promotion"` still flags, because `promotion` is not the immediate next token after the literal. A literal preceded by a state-assertion word (`is`/`are`/`at`/`on`/`now`/`currently`/`already`/`still`) is a state claim and IS flagged even when it could otherwise read as an action target — the state-assertion check wins ties. Those state facts already have a source of truth (`scripts/mavp-version.js`, `git`, GitHub) — reference the source, don't copy the value into `next_action`. Multi-fact cross-session narrative (what changed, why, what to check next session) belongs in `HANDOFF.md` (via `--handoff --notes "..."`) only, not in `next_action` — the wave strategy note (via `--set-strategy-note "..."`) is an intra-wave field that `--close-session` clears only on wave advance (T-648), surviving mid-wave closes verbatim, so it is still not a cross-session home once the wave actually closes; a wave-79-relevant note has already been erased at close before once and was only recovered because the operator still held it in context. Two shipped signals guard this shape: `classifyNextAction()` (in `scripts/mavp-operator-lib.js`) classifies the string using the position rule above, and `--agent` surfaces the result as additive fields `next_action_unverified` (true when `next_action` is non-empty and does not begin with `T-NNN`) and `next_action_volatile_facts` (an array of the matched volatile substrings, e.g. state-asserting version numbers or commit-count phrases). The validator emits the same check as a **failure-severity** finding named `next_action_volatile_facts` (DR-009: escalated from info to blocking — a rule the operator channel can silently violate must not stay advisory-only) — it blocks (exit 2) on a `next_action` write that fails the check.
`deploy_contours` — integer. Controls deploy pipeline visibility in `--agent` output. `0` = no deploy pipeline; `merged` is the final state; `deploy_queue` is always empty and deploy warnings are suppressed. `1` = single contour, auto-deploy-on-merge; `merged` = already deployed; `deploy_queue` is always empty and no `deploy_pending` state is set. `2` = dev + prod contours (default behavior when field is absent); `merged` tasks appear in `deploy_queue` as `deploy_pending: true`. Set to `0` for projects like mavericks itself where there is no separate deploy pipeline; set to `1` for projects where merge triggers an automatic deploy.
`last_task_id` — integer, the highest T-NNN assigned so far. Increment before registering a new task to avoid ID collisions. The validator checks for duplicate task IDs.
**PROCESS_STATE.json is informational** — it is human-maintained and not validated for consistency with BACKLOG.md or TASK_STATUS.md. Do not use `next_action` or `active_slices` as authoritative truth; always verify against BACKLOG.md and TASK_STATUS.md directly.