zuvo:plan
Create a detailed, task-by-task implementation plan. Every task follows the TDD protocol: RED (failing test) -> GREEN (minimal code) -> Verify -> Commit.
Input Resolution
Determine the planning input. Two modes: spec-driven (from brainstorm output) or inline (from user description).
Step 1: Look for a spec
- If the user provided an explicit path (e.g.,
zuvo:plan docs/specs/my-spec.md), use that file - Otherwise, search:
Glob("docs/specs/*-spec.md") - If multiple specs exist, present the list and ask the user which one to plan against
Step 2: Determine mode
- Spec found with
status: Approved→ spec-driven mode. Read spec in full. This is the source of truth. - Spec found without
status: Approved→ Print: "Spec exists but is not approved. Using it as reference in inline mode." Treat spec as context, not authority. → inline mode. - No spec found → inline mode.
Inline mode requirements
The user's message (argument to zuvo:plan) IS the planning input. Extract:
- Goal: what they want built
- Scope: which files/areas are affected (explore codebase if not stated)
- Constraints: any stated requirements
If the user's description is too vague to plan against (less than one sentence, no clear deliverable), ask ONE clarifying question. Do not block — a couple of sentences is enough to plan from.
Set planning_mode: "inline" or planning_mode: "spec-driven" — this affects the plan document header and review phase.
Artifact Detection
Check if a plan already exists:
Glob("docs/specs/*-plan.md")— look for existing plans- Spec-driven mode: match by
spec_idfield. If a matching plan exists, ask the user whether to revise or start fresh. - Inline mode: skip this check (no spec_id to match against)
- If no plan exists, proceed to Phase 1
Mandatory File Loading
Phase 0 — Bootstrap (load before any work)
CORE FILES LOADED:
1. ../../shared/includes/codesift-setup.md -- READ/MISSING
2. ../../shared/includes/env-compat.md -- READ/MISSING
3. ../../shared/includes/quality-gates.md -- READ/MISSING
4. ../../shared/includes/tdd-protocol.md -- READ/MISSING
5. ../../shared/includes/session-state.md -- READ/MISSING
6. ../../shared/includes/acceptance-proof-protocol.md -- READ/MISSING
7. ../../shared/includes/provided-artifact-supremacy.md -- READ/MISSING
8. ../../rules/file-limits.md -- READ/MISSING
9. ../../shared/includes/run-logger.md -- DEFERRED (completion)
10. ../../shared/includes/retrospective.md -- DEFERRED (completion)
Resolve these paths relative to the currently loaded skills/plan/SKILL.md. If a Phase 0 file read fails, mark it as MISSING. Deferred files are loaded when their phase begins, not at startup.
Phase 0.1 — Retro checkpoint marker (run this bash at bootstrap)
Write a run-marker so an abandoned plan is captured at the next zuvo skill start, and sweep any prior orphans. Ungated — never blocks plan.
# >>> zuvo:retro-marker (plan Task 7 — passive checkpoint capture)
_RS=$(command -v retro-stub 2>/dev/null || ls ~/.claude/plugins/cache/zuvo-marketplace/zuvo/*/scripts/zuvo-home/retro-stub 2>/dev/null | head -1)
_ZH="${ZUVO_HOME:-$HOME/.zuvo}"
_RSK="${SKILL:-plan}"
_RPR="${PROJECT:-$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")}"
_RSHA=$(git rev-parse --short HEAD 2>/dev/null || echo "-")
# Sweep PRIOR orphans FIRST — before writing this run's marker — so this
# run's fresh marker is never swept as its own orphan.
[ -n "$_RS" ] && "$_RS" --sweep >/dev/null 2>&1 || true
if mkdir -p "$_ZH/run-markers" 2>/dev/null; then
{ printf 'start_ts=%s\nskill=%s\nproject=%s\nsha7=%s\nbranch=%s\nsession_id=%s\nrepo_root=%s\n' \
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$_RSK" "$_RPR" "$_RSHA" \
"$(git branch --show-current 2>/dev/null || echo -)" "${ZUVO_SESSION_ID:-$_RSHA}" \
"$(git rev-parse --show-toplevel 2>/dev/null || pwd)" \
> "$_ZH/run-markers/$_RSK-$_RPR-$_RSHA-$$-$(date +%s).marker"; } 2>/dev/null || true
fi
# <<< zuvo:retro-marker
Capture is best-effort, not guaranteed: the block is intentionally
ungated (never blocks/fails plan), so if ZUVO_HOME is not writable no
marker is written and plan proceeds normally. When a marker is written,
append-runlog clears it on a clean terminal retro; if plan is abandoned
first, the next skill's --sweep emits an ABANDONED stub.
Phase 0.2 — Arm the stall-recovery watchdog
Follow the ARM section of ../../shared/includes/stall-recovery.md with skill=plan: seed
zuvo/context/plan.heartbeat (status: running, resume: zuvo:plan) and — if CronCreate is
available — arm a */3 * * * * cron tagged [zuvo-watchdog skill=plan project=…] that runs
zuvo-watchdog-check and re-invokes zuvo:plan on a RESUME verdict. This closes the measured gap
where a plan turn killed by an API error sat DEAD for 168 minutes until the user re-prompted
(2026-07-16, rs_be) — the watchdog resumes it in ~3 min. Idempotent (skip if this run's tag is in
CronList); if CronCreate is absent print the /loop 3m zuvo:plan fallback line and continue.
Never block on watchdog setup. Disarm on clean completion (right after the run-log append):
write status: done to the heartbeat and CronDelete the id from its cron_id: line (belt:
CronList → delete any job whose prompt contains [zuvo-watchdog skill=plan).
If 1-2 files missing: Proceed in degraded mode. Note which files are unavailable in the final output. If 3+ files missing: Stop. The plugin installation is incomplete.
Execute the CodeSift setup procedure from codesift-setup.md. Note the repo identifier for agent dispatches when CodeSift is available.
Provided-Design Check (BEFORE Phase 1 — run even without a brainstorm spec)
Run provided-artifact-supremacy.md. zuvo:plan is frequently invoked directly on a user-supplied design (a downloaded prototype, HANDOFF.md, mockup, "match this 1:1") with NO brainstorm spec — that is the exact case that failed on 2026-05-30. When a design artifact is present:
- The artifact is the SOURCE OF TRUTH for WHAT to build; the repo is HOW. Do NOT downgrade it to "reference-only" and ground the architecture on your own repo reading.
- Read it IN FULL, extract a
## Design Constraintschecklist (DC-1…), quoting everydo not/never/single/one/no <X>/must. Carry it into the plan; every architecture decision that touches a DC cites it. - If the artifact contains working code (
.jsx/.tsx/component files, a runnable prototype) → inventory and read those files; the plan's tasks PORT them component-by-component into the target stack, not re-derive the behavior from prose. A 7000-line working prototype is ground truth, not a sketch to reinterpret. - Any architecture decision that contradicts a DC is a HARD-STOP
[DEVIATION: DC-N says "<quote>" but I propose <X> — confirm], never a repo-rationalized "decision". The plan-reviewer (Phase 3) FAILs a plan whose architecture contradicts a DC.
Phase 1: Architecture Analysis
Dispatch follows ../../shared/includes/execution-policy.md through env-compat. Reuse existing
authorization within that policy; session restrictions take precedence. Run each required gate
and report its actual independence or an unmet requirement.
Light mode (small scope): If the planning input is inline (no spec doc), scope is ≤5 tasks (excluding test fixtures/data files), no new public contract is introduced, AND the orchestrator has CodeSift on an indexed repo with the feature spanning ≤7 files, the Team Lead MAY perform Phase 1 analysis directly (CodeSift + Read) and SKIP the Architect/Tech-Lead/QA sub-agent fan-out. Rationale: for a tiny/light scope, spinning up 3 sequential sub-agents costs more than the Team Lead analyzing directly with CodeSift + Read on an already-indexed repo (the fan-out agents are now general-purpose with CodeSift, so they are no longer crippled — but for ≤7 files the direct pass is still cheaper). Record Phase 1: direct (small/light scope) in ## Review Trail. plan-reviewer (Phase 3) + cross-model validation still run normally. Spec-driven and >5-task / new-contract plans stay on the mandatory full fan-out below.
Dispatch 3 agents SEQUENTIALLY. Each agent receives the output of the previous agent(s) as input context. The 4th step is performed by you (the main agent) as Team Lead synthesis.
The sequential order is mandatory because each agent's analysis depends on what came before: the Architect maps the terrain, the Tech Lead makes decisions based on that map, and the QA Engineer assesses testability of those decisions.
Pass the prior reports in full when practical. If you must compress for token budget, preserve concrete file paths, symbols, risk rankings, and open questions. Do not reduce a prior report to generic prose.
Dispatch-type note (Claude Code): these sub-agents dispatch as general-purpose, NOT Explore — because Explore has NO mcp__codesift__* access and would re-explore the repo with grep, slower and less accurate (same reason zuvo:review uses general-purpose). general-purpose HAS CodeSift, so the tables' "Token budget for CodeSift calls" genuinely applies: instruct each agent to run the CodeSift preload and use search_text / search_symbols / get_file_tree / codebase_retrieval / trace_call_chain for its analysis. Keep them READ-ONLY BY INSTRUCTION — Read + CodeSift only, never Edit/Write/git commit (they produce reports, not code); general-purpose can write, so the read-only discipline is enforced by the prompt, not the agent type. Where CodeSift is genuinely unavailable, they fall back to Read/Grep/Glob; for doc-only re-reviews, do the analysis inline instead of dispatching.
Model policy — planning runs on the STRONGEST model
Planning is the most reasoning-critical step in the pipeline: a weak plan cascades into weak execution
across every task. So the Architect / Tech Lead / QA / plan-reviewer sub-agents dispatch on Opus
(the strongest tier — each platform resolves the "Opus" label to its top model), NOT Sonnet. Override
with --model only when you deliberately accept a cheaper/faster plan.
Session-model preflight (WARN — never let a weak model quietly author a plan). Team Lead synthesis
(Step 4) and light-mode direct analysis run on the MAIN SESSION model, which zuvo cannot change (it is
the CLI you launched). If that session model is NOT a top-tier reasoning model — e.g. a codex-5.4 /
mid-tier / generation-behind model is driving — print, before authoring:
[MODEL WARNING] Planning is running on <session-model>, not a top-tier model. A plan sets the ceiling
for the entire build — every execute task inherits its quality. Re-run planning from your STRONGEST
agent (Claude Opus / the newest codex / etc.) for a materially better plan.
Proceed only if the user continues, but the warning must be loud. Detect the driver from the ambient
env (CLAUDE_MODEL, CODEX_MODEL, the codex config.toml model=, ANTIGRAVITY_MODEL); if unknown,
warn generically that planning should be on the strongest available agent.
🔒 CODEX: ANALYSIS ROLES INLINE — because they are same-model, not because dispatch is absent.
Codex HAS sub-agents (~/.codex/agents/, [features].multi_agent), and this repo's Codex build
generates their profiles. What it does not have is a second MODEL: a codex thread reviewing or
analysing a codex author shares its blind spots, so the roles below are performed HERE — read the
agent's instruction file and do that analysis in this context.
Mechanical workers are the other case and DO dispatch (see env-compat.md), with one bounded wait.
The old wording said SINGLE-AGENT ONLY and forbade every dispatch; agents quoted it as the reason
they would not dispatch anything at all, which is not what it was measuring.
Agent 1: Architect
Read agents/architect.md for full instructions.
Dispatch parameters:
| Field | Value |
|---|---|
| Model | Opus (strongest — planning is reasoning-critical; --model overrides) |
| Type | general-purpose (read-only: Read + CodeSift only, no Edit/Write) |
| Input | The spec document (spec-driven) or user description + codebase context (inline) |
| Token budget | 5000 for CodeSift calls |
Expected output: Architecture Report containing component boundaries, data flow, interfaces, dependency graph, and a Mermaid diagram.
Wait for the Architect report before dispatching the next agent.
Agent 2: Tech Lead
Read agents/tech-lead.md for full instructions.
Dispatch parameters:
| Field | Value |
|---|---|
| Model | Opus (strongest — planning is reasoning-critical; --model overrides) |
| Type | general-purpose (read-only: Read + CodeSift only, no Edit/Write) |
| Input | The planning input (spec or user description) AND the Architect's report |
| Token budget | 5000 for CodeSift calls |
Expected output: Technical Decisions Report containing pattern selections, library choices, trade-offs, and file structure.
Wait for the Tech Lead report before dispatching the next agent.
Agent 3: QA Engineer
Read agents/qa-engineer.md for full instructions.
Dispatch parameters:
| Field | Value |
|---|---|
| Model | Opus (strongest — planning is reasoning-critical; --model overrides) |
| Type | general-purpose (read-only: Read + CodeSift only, no Edit/Write) |
| Input | The planning input (spec or user description), Architect's report, AND Tech Lead's report |
| Token budget | 5000 for CodeSift calls |
Expected output: Quality Assessment containing testability review, CQ pre-check, test strategy, and risk areas.
Wait for the QA Engineer report before proceeding to Team Lead synthesis.
Step 4: Team Lead Synthesis (Main Agent)
You are the Team Lead. You do NOT dispatch a sub-agent for this step. Read agents/team-lead.md for the synthesis procedure, then execute it yourself.
Using all three agent reports plus the original spec, decompose the work into ordered TDD tasks. This is the core deliverable of the plan skill.
Phase 2: Plan Document
Write the plan document to docs/specs/YYYY-MM-DD-<topic>-plan.md using today's date and a topic slug derived from the spec.
Plan Document Structure
# Implementation Plan: [Feature Name]
**Spec:** [path to spec document | "inline — no spec"]
**spec_id:** [spec_id from the spec's header | "none"]
**planning_mode:** [spec-driven | inline]
**source_of_truth:** [approved spec | inline brief | inline brief (unapproved spec is context only)]
**plan_revision:** 1
**status:** Draft | Reviewed | Approved
**Created:** [date]
**Tasks:** [count]
**Estimated complexity:** [standard/complex mix summary]
## Architecture Summary
[Condensed from Architect report — component list, key interfaces, dependency direction]
## Technical Decisions
[Condensed from Tech Lead report — chosen patterns, libraries, file structure]
## Quality Strategy
[Condensed from QA Engineer report — test approach, risk areas, CQ gates to watch]
## Coverage Matrix
[Deterministic authority -> task mapping]
| Row ID | Authority item | Type | Primary task(s) | Notes |
|--------|----------------|------|-----------------|-------|
| AC1 / G1 | [spec AC, deliverable, goal, or constraint] | requirement / deliverable / constraint | Task 2, Task 5 | [context-only note if needed] |
## Review Trail
- Plan reviewer: revision 1 -> APPROVED | ISSUES FOUND ([short summary])
- Cross-model validation: executed -> clean | findings fixed | skipped ([script reason])
- Status gate: Draft | Reviewed | Approved
## Task Breakdown
### Task 1: [Short descriptive name]
**Files:** [list of files to create or modify, with full paths]
**Surface:** backend-logic | api | db | db-data | ui | integration | config | docs
**Complexity:** standard | complex
**Dependencies:** none | Task N, Task M
**Failure:** halt | skip-and-continue | degraded:<one-line description>
[MUST be its own line — never appended to the Dependencies line, even after a
`·` bullet. `verify-plan-dag`'s Dependencies parser truncates at `(`/`>` and
hard-exits 2 on a non-numeric token, so a `degraded:` description containing
a comma would be tokenized as a dependency and crash the linter.]
**Execution routing:** default implementation tier | deep implementation tier
- [ ] RED: [test goal — what behavior to assert, which file, which assertions]
- [ ] GREEN: [implementation targets — symbols to add/change, invariants, interfaces]
[Optional: scaffold snippet ≤20 LOC if pattern is non-obvious]
- [ ] Verify: `[exact shell command]`
Expected: [exact expected output or pattern]
- [ ] Acceptance Proof: [for each AC# this task satisfies, copy the proof block from the spec inline]
- AC#: [id from spec]
- Surface: [matching surface]
- Proof: [exact procedure — command, HTTP call, DB query, browser interaction script]
- Expected: [exit code, response shape, DOM state, screenshot match]
- Artifact: `zuvo/proofs/task-1-<ac-id>.<ext>`
- [ ] Commit: `[commit message describing behavior added]`
### Task 2: [...]
...
Whole-feature Smoke Proofs
After all tasks complete, zuvo:execute runs these end-to-end proofs before declaring the feature COMPLETED. Catches structural bugs that span multiple tasks (e.g., round-trip data loss across encode/transform/decode in three tasks).
Copy each smoke proof from the spec's ## Whole-feature Smoke Proofs section. If the spec marked smoke as "Not applicable" (internal-only subsystem, no user flow), repeat the justification here.
## Whole-feature Smoke Proofs
- **SMOKE1 — [name]**
- Preconditions: [fixtures, env, seeded data]
- Proof: [full end-to-end script]
- Expected: [invariants the entire flow must preserve]
- Artifact: `zuvo/proofs/smoke-<flow-name>.<ext>`
- **SMOKE2 — ...**
Task Authoring Rules
- Scope per task = a MILESTONE, not a micro-step. (HARD — 2026-07-17 post-mortem.) A task is a coherent, independently-committable slice of the feature taking roughly 20-60 minutes of implementation — NOT a 2-5-minute micro-step. Micro-steps (individual RED/GREEN pairs, small refactors, wiring) are grouped INSIDE the task as an ordered internal checklist; they do NOT get their own task, review cycle, proof artifact, or commit. Target 5-10 tasks per plan; >12 is a planning smell that requires explicit justification in the plan header or a scope split (rule 17). The 37-task questions-cutover run measured 37h wall / 51 commits / 171 proof artifacts / 12+ full suite runs — per-micro-step gating multiplied a fixed overhead into days. Each task still represents one logical unit of work. If it would take longer, split it.
- Boundary size: A task touching more than 5 files, more than one new public surface, or more than two system boundaries is oversized by default. Split it unless you can justify why the files are inseparable. Test fixtures and test-data files (
.json/.html/.csvundertests/fixtures/or equivalent fixture dirs) do NOT count toward the 5-file boundary. Count only production code plus their direct test files, and state the fixture count separately. - Task intent over exact code: RED steps include test intent, target assertions, and file path. GREEN steps include symbols to add/change, invariants to maintain, interfaces to implement, and reuse obligations. Include scaffold code only when the pattern is non-obvious, and keep scaffolds at or below 20 LOC. Do NOT write the full implementation.
- Exact verification: The Verify step must include an exact shell command whose exit code proves the claimed invariant. If the expected output mentions a specific value or behavior, the command must assert that value or behavior rather than merely running a script.
- Acceptance Proof per task (MANDATORY): Every task must list its
Acceptance Proof:block — copying the spec's per-AC proof inline sozuvo:executecan run it without re-resolving from spec. Tasks without proofs are rejected by plan-reviewer. See../../shared/includes/acceptance-proof-protocol.mdfor surface taxonomy and proof shapes. Verify (rule 4) is an implementation-detail check (does my function compile and pass unit tests); Acceptance Proof is a behavior check (does the AC actually work). Both required — they catch different defects. - Surface field (MANDATORY): Every task declares one Surface (backend-logic / api / db / db-data / ui / integration / config / docs). Determines proof shape and verification primitive. UI surface enables browser-tool requirement at execute time.
- Coverage matrix: Every Coverage Matrix row must appear in at least one task's Acceptance Proof field. No orphan requirements, deliverables, or constraints. In addition: every spec AC must be covered by at least one task's Acceptance Proof — Coverage Matrix and AC list must both be exhaustively mapped.
- Whole-feature smoke proofs: Copy the spec's
## Whole-feature Smoke Proofssection into the plan verbatim. If the spec marked smoke "Not applicable", repeat the justification. Smoke proofs run after all per-task proofs at execute Phase Final. When smoke proofs apply: (a) the plan MUST include a final numbered task that authors the smoke-test runner file (thezuvo/proofs/smoke-*artifact named in the template), with the smoke proofs listed in that task's Acceptance Proof block — otherwise execute Phase Final hits a missing file; (b) every smoke proof MUST map to at least one task's RED sub-suite (a runnable, possibly-mocked end-to-end exercise) so smoke regressions surface during execute rather than only at the end. - Dependencies: A task can only depend on tasks with a lower number. No circular dependencies. Dependencies must reflect real ordering, not preference. Dependency declaration must trace concrete reads — a task's Dependencies MUST list every prior task whose output (file/symbol/schema column/env var) its RED/GREEN actually reads or imports; transitive coverage is not sufficient; conversely reject declared deps the task does not consume. Common offenders: a feature-flag task that reads a schema column, an orchestrator that reads a schema column, an Acceptance Proof that invokes a higher-numbered symbol. Task numbers are a partial order:
zuvo:executeruns in dependency order, so numbers need not encode priority — only that dependencies point backward. Place the riskiest cross-boundary unit as early as its dependencies allow so adversarial review does not flag deferred risk. - Complexity rating:
standardmeans 1-3 files, existing patterns, one system boundary, and no new public contract.complexmeans 4+ files, 2+ system boundaries, new patterns/contracts, cross-cutting concerns, or high-risk hotspot files. The complexity rating determines which implementation tier the execute phase will use: default for standard, deep for complex. - File limits: Use
../../rules/file-limits.mdas the planning default. In particular: utilities/helpers <=100 lines, controllers/services <=300 unless the rule explicitly allows more, components <=200/300, hooks <=250. If the plan would exceed these limits, split the task. - Test files: Every task that creates production code must include a test file. If a task is docs-only or config-only, say so explicitly in the RED step instead of implying a missing test.
- Serialize same-file siblings: Two tasks that both edit the same shared file (a barrel/
index.ts, a route registry, a DI container, a migrations index) MUST NOT be parallel — make one depend on the other sozuvo:executenever dispatches them concurrently onto the same file (lost-edit / merge-clobber hazard). State the serialization in their Dependencies. - Feasibility spike for novel infra: When the plan introduces infrastructure with real unknowns (a new queue, a new auth provider, a protocol the codebase has never used), make the FIRST dependent task a small de-risking spike that proves the core mechanism works, before tasks build on it. A spike that fails reshapes the plan cheaply; discovering the dead-end at task 12 does not.
- Conditional/decision tasks are always-executed gates: A task whose body is "decide X, then maybe do Y" must be modeled as an always-run gate task that prints an explicit
[DECISION: <X>] → SKIPPED|COMPLETEmarker, never as a task that silently no-ops.zuvo:executemust see it ran and what it decided. - Data Model verification before migration/schema tasks: Before authoring any task that adds a migration or reads an existing table, grep-verify the spec's
## Data Modelcolumn names / types / nullability against the LIVE schema (the actual migration/model files), not the spec's memory. A schema-drift mismatch caught at plan time is one edit; caught at execute adversarial review it is a CRITICAL + a ~600s round. - Scope split / max-3-PR rule. (HARD — 2026-07-17 post-mortem.) If the plan exceeds ~10 milestones, spans more than one deliverable boundary, or is a migration/cutover: SPLIT it into sequential PLANS shipped as separate PRs (canonical cutover shape: compat → runtime cutover → legacy removal, max 3). A single long-lived branch that drifts from main for days ends in conflict hell (measured: 37h isolated branch → divergence + merge conflicts). Each split plan lands on main independently before the next starts.
- Reality pre-check — verify what ALREADY EXISTS before authoring tasks. (HARD — 2026-07-16
invalid plan.) Before writing the Task Breakdown, check the CODEBASE for each spec item:
CodeSift (
search_symbols/search_text/find_references) or Read the target modules. Every task MUST cite the gap it fills (file/symbol that is missing or wrong TODAY). A task whose target behavior already exists is NOT authored — it becomes a one-linealready-implementednote in the plan header. The rs_be antifraud plan authored T1-T18 for code that was already implemented; the whole plan was invalid and the execute run burned hours discovering it. - Literal-string adversarial dispositions: A plan-review/adversarial finding that targets a literal string (a log message, an error-text constant, a fixture value) with no behavioral consequence is almost always a false positive — disposition it as
FP: literal-string, no behavior changein the Review Trail rather than churning the plan to satisfy it. - Failure strategy (MANDATORY field, HARD default): Every task declares a
**Failure:**field with exactly ONE of three tokens:halt,skip-and-continue, ordegraded:<one-line description>— never more than one value. The template's**Failure:** halt | skip-and-continue | degraded:<one-line description>line is notation for "pick one"; that pipe-union appears ONLY in the template and must never be copied verbatim into an authored task. A task with no**Failure:**line ishalt— byte-identical to today's behavior, so every pre-existing plan is still valid. A**Failure:**line that IS present but carries no value (blank after the label) is a plan defect, not a silenthalt— omitting the line entirely meanshalt; writing it blank is an authoring error, distinct because one is a legacy plan and the other is a typo.skip-and-continueis legal only when no other task in the plan lists it on a**Dependencies:**line — because rule 9 already requires Dependencies to trace concrete reads, so "B depends on A but A is optional" means B fails mid-RED against a symbol A never wrote, trading a cleanBLOCKED_BY_DEPENDENCYfor a worse, harder-to-diagnose failure downstream. Fordegraded:<one-line description>, the description is free text on its OWN line, must not contain a·bullet, and must be treated as unread past thedegraded:marker — discarded whole, never tokenised or split on commas — so a later linter has no license to invent a stricter parse of that free text. The enum changes a task's OUTCOME at execute time only — never the task graph: there is no alternative task, no fail branch, no rerouting. Choosingskip-and-continueordegradeddoes not add, remove, or reorder any task or dependency edge in this plan. - Failure strategy — AC coverage (advisory, plan-reviewer-enforced, not mechanical): A task may
declare
skip-and-continueonly if every AC in its Acceptance Proof block is also claimed by at least one other task, or its Acceptance Proof is explicitlynone — <reason>. This cannot be checked mechanically because the Coverage Matrix's "Primary task(s)" cell (rule 7) is free text — the plan-reviewer must verify it by reading the matrix. Skipping this check lets a plan authorize a run that reaches## Execution Completewith an unproven AC.
Phase 3: Plan Review
Phase 3.0 — Deterministic DAG lint (run FIRST, after any renumber, before the reviewer)
Before dispatching the reviewer, run the deterministic dependency-DAG linter on the plan file. It catches circular deps, forward references (a task depending on a higher number), and missing-task references mechanically — cheaper than a ~600s adversarial round.
# >>> zuvo:plan-dag-check
_VD=$(command -v verify-plan-dag 2>/dev/null || ls ~/.zuvo/verify-plan-dag 2>/dev/null | head -1 \
|| ls ~/.claude/plugins/cache/zuvo-marketplace/zuvo/*/scripts/zuvo-home/verify-plan-dag 2>/dev/null | head -1)
if [ -n "$_VD" ] && [ -x "$_VD" ]; then
"$_VD" "docs/specs/YYYY-MM-DD-<topic>-plan.md" || { echo "[PLAN-DAG FAIL] fix the reported dependency/cycle/forward-ref before review"; }
else
echo "[PLAN-DAG SKIP] verify-plan-dag not installed — DAG checked by reviewer only (warn-only)"
fi
# <<< zuvo:plan-dag-check
Also check the plan's TAIL actually reaches the reviewer. adversarial-review caps its input
(50K chars in --mode plan) and trims to a whole-section boundary — so on a long plan the LAST
sections silently fall outside the reviewed text, and those are exactly Acceptance Criteria,
Verification and Rollback. A reviewer that never saw them reports no findings about them, which
reads identically to "they are fine". Compare wc -c on the plan against the cap; if it exceeds,
either split the plan (preferred — see the 3-document split) or run a SECOND pass with
--files pointing at the tail sections alone, and record in ## Review Trail which sections each
pass covered. Never let "no findings" stand for a section that was never sent.
Fail-loud on exit 1 (fix the dependency/cycle/forward-ref in the plan, then re-run). Warn-only if the script is missing. Re-run after ANY task renumber. In the same pass (no extra dispatch), scan the plan text once for: unresolved placeholders (YYYY-MM-DD/<topic>/TBD file paths), tasks over the rule-2 file boundary with no stated justification, and AC#s cited in tasks but absent from the Coverage Matrix — each is a one-edit fix here versus a full reviewer revision.
Dispatch the plan reviewer agent to verify the plan against the spec.
Agent: Plan Reviewer
Read agents/plan-reviewer.md for full instructions.
Dispatch parameters:
| Field | Value |
|---|---|
| Model | Opus (strongest — planning is reasoning-critical; --model overrides) |
| Type | general-purpose (read-only: Read + CodeSift only, no Edit/Write) |
| Input | The planning input (spec or user description) AND the plan document |
Expected output: Review verdict — either APPROVED or ISSUES FOUND with specific items.
Review Loop
- If APPROVED on the current revision: append the verdict to
## Review Trail, then proceed to cross-model validation. - If ISSUES FOUND: revise the plan to address the issues, increment
plan_revision, update## Review Trail, then re-dispatch the reviewer on the new revision. - Maximum 3 review iterations. After 3, present the remaining issues to the user and let them decide whether to accept the plan as-is or provide guidance
Cross-Model Validation (MANDATORY — do NOT skip)
After the plan-reviewer converges, run cross-model validation on the plan file. This catches task bloat, hidden ordering violations, and AC orphans.
timeout 540 ~/.zuvo/adversarial-review --mode plan --files "docs/specs/YYYY-MM-DD-<topic>-plan.md" --json \
> zuvo/context/adversarial-plan.json 2> zuvo/context/adversarial-plan.err
The explicit timeout 540 is required, not defensive (or run the pass in the background, if
your harness offers that). It must stay above the script's own per-provider ceiling plus its
grace (450 + 15 = 465 as of 2026-09-06) with margin to spare for writing the artifact — if the
outer kill lands first, the run dies with NO artifact, which is worse than any timeout. Raise
both together or neither. A 5-provider
--mode plan pass measured 178s on a 7-task plan and grows with plan size, while the default
Bash tool timeout is 120s — so the pass is killed mid-run, the JSON capture is empty or truncated,
and the round is spent for nothing. This is a measured failure, not a hypothetical.
Read findings from zuvo/context/adversarial-plan.json, never from live stdout (truncated stdout cost 4 recovery calls in one field run). Belt: each pass also appends per-provider SUMMARY lines to ~/.zuvo/adversarial.log — if the capture file is lost, the last SUMMARY row gives status/providers.
If adversarial-review is not in PATH: ~/.zuvo/adversarial-review (stable; the versioned cache path breaks after any release)
Wait for complete output. Then apply fix policy:
- CRITICAL (missing dependency, task requires nonexistent file) → fix in plan, increment
plan_revision, re-run plan-reviewer - WARNING that changes task size, ordering, dependencies, verification, or coverage → fix in plan, increment
plan_revision, re-run plan-reviewer - WARNING that does NOT change execution semantics or coverage → append as note to the affected task and record it in
## Review Trail - INFO → ignore
What "re-run plan-reviewer" does and does NOT mean. A fix applied for an adversarial finding
re-runs the plan-reviewer (cheap, same-model) — it does not automatically earn another
adversarial-review pass. A second cross-model pass happens only when the revision materially
changed what gets built (tasks added/removed/reordered, ACs or coverage changed); a wording or
note-level edit does not. Deciding this per-run is what produced the repeated-pass loops.
Stop rule: the 3-iteration cap governs the initial plan-reviewer loop only. An adversarial-driven revision gets exactly ONE re-review of the current revision. Once the current revision holds a reviewer APPROVED verdict and a cross-model pass with zero CRITICAL findings, disposition remaining WARNINGs in ## Review Trail and STOP — never spin another revision solely for optional reordering, task splitting, or duplicated wording.
Hard budget (deterministic — this prose cap did NOT compose across the 3-document split and produced a 6-hour run). ~/.zuvo/adversarial-review --mode plan enforces a per-plan round budget in the tool itself: after ZUVO_PLAN_ROUND_BUDGET passes (default 8) within a 30-min window, it REFUSES to run — prints PLAN REVIEW BUDGET EXHAUSTED, emits {"status":"budget_exhausted"}, and exits 7 WITHOUT calling any provider. This is shared across ALL documents of a split plan, so 3 docs × unbounded re-reviews can no longer accumulate. On exit 7 / status: "budget_exhausted":
- Do NOT retry, do NOT wait out the window, do NOT
ZUVO_PLAN_BUDGET_OFF=1to keep looping. The budget firing means the loop has already done more review than a plan warrants. - Finalize the CURRENT revision: disposition every outstanding WARNING in
## Review Trail(apply the trivially-correct ones inline, record the rest as accepted notes), set the plan status (Reviewed, orApprovedper the interactive/async rules), and hand it to the user with a one-line[BUDGET: N plan-review passes — residual warnings dispositioned, not re-looped]. ZUVO_PLAN_BUDGET_OFF=1exists ONLY for a human who deliberately wants an unbounded session; an agent must never set it to escape the breaker.
Status handling (D2+D3+D4, 2026-05-17): parse --json output (or the script's text-mode summary banners) for non-ok status:
status: "single_provider_only"(exit 3) —--rotate/--multirequested but only 1 provider remains after host exclusion. Re-invoke with--singleand record in## Review Trail:Cross-model validation: single-provider-only (note: install additional provider for diversity). Plan may still advance to Reviewed — single-provider validation is a real signal, just narrower.status: "timeout"(exit 124) — ALL providers timed out. RecordCross-model validation: skipped (timeout)and proceed.status: "suspended"(exit 125) — the host slept mid-run; no provider was actually given a chance. Re-invoke the pass once (it does not count against the plan-round budget in spirit, though the breaker still counts it — if the budget is exhausted, recordCross-model validation: skipped (host suspended)and finalize). Never record this as a provider or infrastructure failure.status: "partial"(exit 0) —timeout_count > 0but at least one provider returned. Record in## Review Trail:Cross-model validation: partial (N/M providers; M-N timed out). Apply fix policy to the findings that did come back. Surfacetimeout_countto user so reduced coverage is visible.
Cross-call rotation: if a second adversarial pass is needed after rev bump, capture providers_used_list[0] (array field) from pass-1 JSON and pass it via --exclude-last <name> on pass-2 to force a different provider's perspective on the revised plan. (Use the array field — the string providers_used cannot be indexed with [0].)
Do not set status: Reviewed unless the current plan revision has:
- a plan-reviewer
APPROVEDverdict, - a cross-model validation result or explicit script-generated skip reason, and
- a populated
## Review Trailreflecting both checks.
User Approval
The plan follows a strict state machine:
Draft → Reviewed (reviewer converged + cross-model recorded) → Approved (by user only)
Interactive mode: Present the final plan. The user must explicitly approve. Update status to "Approved" only on user confirmation.
Async mode (Codex App, Cursor): A converged reviewer verdict plus cross-model validation moves the plan to Reviewed status (NOT Approved). Print: "Plan is in Reviewed status. Review the task breakdown, then change status to Approved before running zuvo:execute."
zuvo:execute MUST check for "Approved" status. It will not start from "Draft" or "Reviewed".
Active Plan Pointer
After the plan reaches Approved status, write the active plan pointer using the WRITE protocol from session-state.md:
mkdir -p zuvo/plans
Write zuvo/plans/active-plan.md with status: pending. If the plan is only Reviewed, do not write the pointer yet. This keeps zuvo:execute aligned with the same approval gate as the plan header.
Write status: and plan: as plain lines, never inside an HTML comment — a git hook parses this file and fail-opens silently if it cannot (see the format contract in session-state.md). Verify with scripts/zuvo-phase.sh status.
Output
The final plan document at `docs/specs/YYYY-
…(truncated)