okstra 0.111.0 → 0.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.kr.md +2 -2
  2. package/README.md +2 -2
  3. package/bin/okstra +7 -1
  4. package/docs/for-ai/README.md +2 -2
  5. package/docs/for-ai/skills/okstra-brief.md +2 -3
  6. package/docs/for-ai/skills/okstra-container-build.md +2 -3
  7. package/docs/for-ai/skills/okstra-inspect.md +5 -12
  8. package/docs/for-ai/skills/okstra-rollup.md +3 -4
  9. package/docs/for-ai/skills/okstra-run.md +3 -5
  10. package/docs/for-ai/skills/okstra-schedule.md +2 -7
  11. package/docs/for-ai/skills/okstra-setup.md +1 -1
  12. package/docs/kr/architecture/storage-model.md +1 -2
  13. package/docs/kr/architecture.md +2 -2
  14. package/docs/kr/cli.md +4 -2
  15. package/docs/project-structure-overview.md +5 -3
  16. package/package.json +1 -1
  17. package/runtime/BUILD.json +2 -2
  18. package/runtime/prompts/coding-preflight/overview.md +3 -1
  19. package/runtime/prompts/launch.template.md +1 -5
  20. package/runtime/prompts/lead/context-loader.md +2 -4
  21. package/runtime/prompts/lead/convergence.md +11 -240
  22. package/runtime/prompts/lead/okstra-lead-contract.md +70 -34
  23. package/runtime/prompts/lead/plan-body-verification.md +240 -0
  24. package/runtime/prompts/lead/report-writer.md +7 -7
  25. package/runtime/prompts/lead/team-contract.md +15 -17
  26. package/runtime/prompts/profiles/_common-contract.md +1 -37
  27. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  28. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  29. package/runtime/prompts/profiles/final-verification.md +1 -1
  30. package/runtime/prompts/profiles/implementation-planning.md +2 -2
  31. package/runtime/prompts/profiles/implementation.md +1 -1
  32. package/runtime/python/okstra_ctl/path_hints.py +1 -0
  33. package/runtime/python/okstra_ctl/paths.py +3 -0
  34. package/runtime/python/okstra_ctl/render.py +8 -0
  35. package/runtime/python/okstra_ctl/set_work_status.py +147 -0
  36. package/runtime/python/okstra_ctl/team_reconcile.py +1 -1
  37. package/runtime/skills/okstra-brief/SKILL.md +32 -176
  38. package/runtime/skills/okstra-brief/references/reporter-confirmations.md +71 -0
  39. package/runtime/skills/okstra-brief/references/tracker-recursion.md +90 -0
  40. package/runtime/skills/okstra-container-build/SKILL.md +7 -20
  41. package/runtime/skills/okstra-graphify/SKILL.md +8 -8
  42. package/runtime/skills/okstra-inspect/SKILL.md +27 -43
  43. package/runtime/skills/okstra-rollup/SKILL.md +6 -6
  44. package/runtime/skills/okstra-run/SKILL.md +7 -16
  45. package/runtime/skills/okstra-schedule/SKILL.md +64 -419
  46. package/runtime/skills/okstra-setup/SKILL.md +25 -223
  47. package/runtime/skills/okstra-setup/references/project-config.md +188 -0
  48. package/runtime/templates/reports/schedule.template.md +2 -2
  49. package/runtime/templates/worker-prompt-preamble.md +1 -1
  50. package/runtime/validators/validate-run.py +7 -7
  51. package/src/cli-registry.mjs +14 -0
  52. package/src/commands/inspect/set-work-status.mjs +31 -0
  53. package/src/commands/lifecycle/check-project.mjs +68 -56
  54. package/src/commands/lifecycle/install.mjs +1 -0
  55. package/src/commands/lifecycle/preflight.mjs +82 -0
@@ -41,11 +41,11 @@ Configure this in the `convergence` block of `task-manifest.json`. If the block
41
41
  | Setting | Default | Description |
42
42
  |------|--------|------|
43
43
  | `enabled` | `true` | If `false`, skip the convergence loop and use the existing consensus/divergence method |
44
- | `maxRounds` | phase-aware: `1` for `requirements-discovery`, `2` otherwise (range 1–3) | Maximum number of re-verification rounds. Discovery's routing/missing-input outputs gain little from a second round; other phases (especially `error-analysis`) keep `2`. Lead resolves the effective value when the manifest omits the key and records it in `config.maxRounds` of the convergence state artifact. |
44
+ | `maxRounds` | phase-aware: `1` for `requirements-discovery`, `2` otherwise (range 1–3) | Maximum number of re-verification rounds. Discovery's routing/missing-input outputs gain little from a second round; other phases (especially `error-analysis`) keep `2`. Lead resolves the effective value when the manifest omits the key and records it in `config.effectiveMaxRounds` of the convergence state artifact. |
45
45
  | `verificationMode` | `"lightweight"` | `"lightweight"` or `"full-reanalysis"` |
46
46
  | `adversarial` | phase-aware: `true` for `requirements-discovery` / `error-analysis` / `implementation-planning`, `false` otherwise | When `true`, Phase 5.5 runs in **adversarial mode** (see §"Adversarial Verification Mode"): verifiers actively try to refute each finding, the burden of proof sits on the claim, and `verificationMode` is forced to `"full-reanalysis"` scoped to the finding's cited evidence. Resolved by `scripts/okstra_ctl/render.py` `_build_convergence_block` and recorded in `config.adversarial` of the convergence state artifact. |
47
47
 
48
- **Auto-disable rule (BLOCKING).** Convergence requires ≥2 analyser workers to produce a meaningful consensus tally. When the active profile's `Required workers:` block (see `prompts/profiles/*.md`) resolves to fewer than 2 analyser workers — e.g. `release-handoff` (zero analyser workers, lead-only) — the lead MUST treat `convergence.enabled` as `false` for that run regardless of manifest configuration, skip Phases 5.5 and the plan-body verification round, and record `finalState: "converged"` with `totalRounds: 0` and an explanatory note in `config` (e.g. `"autoDisabled": "fewer-than-two-analysers"`). The plan-body round inherits the same rule via its `gating=false` advisory path.
48
+ **Auto-disable rule (BLOCKING).** Convergence requires ≥2 analyser workers to produce a meaningful consensus tally. When the active profile's `Required workers:` block (see `prompts/profiles/*.md`) resolves to fewer than 2 analyser workers — e.g. `release-handoff` (zero analyser workers, lead-only) — the lead MUST treat `convergence.enabled` as `false` for that run regardless of manifest configuration, skip Phases 5.5 and the plan-body verification round ([plan-body-verification](./plan-body-verification.md)), and record `finalState: "converged"` with `totalRounds: 0` and an explanatory note in `config` (e.g. `"autoDisabled": "fewer-than-two-analysers"`). The plan-body round inherits the same rule via its `gating=false` advisory path.
49
49
 
50
50
  ## Finding Category
51
51
 
@@ -58,7 +58,7 @@ Configure this in the `convergence` block of `task-manifest.json`. If the block
58
58
 
59
59
  ## Convergence Algorithm
60
60
 
61
- **Majority definition (BLOCKING).** "Majority" means *strictly greater than half* of the non-error votes for that finding (`verification-error` votes are excluded from both numerator and denominator). Ties — including the 1-AGREE / 1-DISAGREE case in a two-analyser roster — are NOT a majority: in intermediate rounds the finding is **carried forward**; in the final executed round the finding is classified `contested`. This rule applies identically to the plan-body verification round (§"Plan-body verification mode") where the same verdict tokens are reused.
61
+ **Majority definition (BLOCKING).** "Majority" means *strictly greater than half* of the non-error votes for that finding (`verification-error` votes are excluded from both numerator and denominator). Ties — including the 1-AGREE / 1-DISAGREE case in a two-analyser roster — are NOT a majority: in intermediate rounds the finding is **carried forward**; in the final executed round the finding is classified `contested`. This rule applies identically to the plan-body verification round ([plan-body-verification](./plan-body-verification.md)) where the same verdict tokens are reused.
62
62
 
63
63
  ### Round 0: Parse worker results
64
64
 
@@ -196,7 +196,7 @@ Active only when `config.adversarial == true` (default for `requirements-discove
196
196
 
197
197
  In adversarial mode the verifier's job inverts: instead of confirming a peer's finding, the verifier **tries to break it**, and the burden of proof sits on the claim — a finding survives only if refutation attempts fail.
198
198
 
199
- ### Scoped full-reanalysis (BLOCKING)
199
+ ### Scoped full-reanalysis
200
200
 
201
201
  Adversarial mode forces `verificationMode = "full-reanalysis"`, but the re-analysis is **scoped to the evidence the finding under attack cites** (the file paths / line ranges / log lines in its `originEvidence`), plus the immediately surrounding context. The verifier MUST NOT re-read the whole task brief, instruction-set, or `final-report-template.md`. This keeps the documented "single largest avoidable cost in requirements-discovery, error-analysis, and implementation-planning" (see §"Reverify prompt: required-reading suppression") bounded while making the refutation real rather than a text-only argument.
202
202
 
@@ -258,19 +258,19 @@ Agent(
258
258
  prompt: "<re-verification prompt with findings batch>",
259
259
  name: "<role-slug>-reverify-r<N>",
260
260
  subagent_type: "<same as initial execution>",
261
- run_in_background: true, # no team_name — removed in CC v2.1.178; joins the session's implicit team
261
+ run_in_background: true, # no team_name — implicit team; see team-contract Operating Rule 0
262
262
  model: "<same as initial execution>",
263
263
  mode: "auto"
264
264
  )
265
265
  ```
266
266
 
267
- - Claude / in-process worker: spawn with `Agent(name: ..., run_in_background: true)` and no `team_name` teammates join the session's implicit team (split-pane when `$TMUX` is set)
267
+ - Claude / in-process worker: spawn with `Agent(name: ..., run_in_background: true)` per [team-contract](./team-contract.md) rule 0 (split-pane when `$TMUX` is set)
268
268
  - CLI (Codex / Antigravity) worker: inject `**Pane role:** worker-reverify-r<N>` (the Agent `name` minus the `<cli>-` prefix) into the reverify prompt body so the tmux trace pane reads `<cli>-worker-reverify-r<N>` per [okstra-lead-contract](./okstra-lead-contract.md) "Agent `name` on dispatch".
269
269
 
270
270
  For `tmux-pane` backend runs, do not use the Agent snippet. For each reverify round, write a jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json` with `dispatchKind: "reverify-r<N>"` and worker entries containing `workerId`, `provider`, `role` (set to `worker-reverify-r<N>` for the pane title), `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths`; then run `okstra team dispatch --project-root <dir> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>` followed by `okstra team await --project-root <dir> --run-manifest <path>`.
271
271
 
272
272
 
273
- **Completion detection per round (BLOCKING).** Each round dispatches a variable set (1..N) of reverify workers asynchronously; the `Agent(name: ..., run_in_background: true)` calls return `Spawned successfully` immediately, which is NOT completion. Lead MUST detect each round's completion via the self-scheduled polling protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)", with the pending set reconstructed from that round's dispatched workers' Result Paths — do NOT restate the algorithm here. Lead MUST NOT treat the spawn ack as completion and MUST NOT end its turn with a prose "waiting" statement.
273
+ **Completion detection per round (BLOCKING).** The spawn acks are NOT completion detect each round's completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)", with the pending set reconstructed from that round's dispatched workers' Result Paths. Do NOT end the turn with a prose "waiting" statement.
274
274
 
275
275
  ### Required reverify-prompt anchor headers (BLOCKING)
276
276
 
@@ -290,18 +290,18 @@ Assigned worker prompt history path: <Project Root>/<Prompt History Path>
290
290
  2. `team-state-<task-type>-<seq>.json` → `workers[].usage.cliModel` for that role (initial run's actual execution value)
291
291
  3. The `**Model:**` line of the initial Phase 4 prompt for that role (read from its persisted prompt-history file)
292
292
 
293
- If none of the three is available, **abort the reverify dispatch for that role** and record a `contract-violation` event via `okstra error-log append-observed`. Do NOT guess, do NOT fall back to training-data defaults — for codex this would silently produce `o4-mini` instead of the assigned `gpt-5.5`-class model, which is a real bug class observed in production.
293
+ If none of the three is available, **abort the reverify dispatch for that role** and record a `contract-violation` event via `okstra error-log append-observed`. Do NOT guess, do NOT fall back to training-data defaults — for codex this silently produces `o4-mini` instead of the assigned `gpt-5.5`-class model.
294
294
 
295
295
  For Codex/Antigravity wrapper subagents, the `**Model:** <role>, <modelExecutionValue>` line is what their wrapper extracts to pass into the underlying CLI's `--model` flag. Omitting it forces the wrapper to fall back to its own training-data knowledge of the CLI's historical default.
296
296
 
297
- ### Reverify prompt: required-reading suppression (BLOCKING)
297
+ ### Reverify prompt: required-reading suppression
298
298
 
299
299
  Reverify prompts MUST NOT inject the Phase 2 `[Required reading]` clause:
300
300
 
301
301
  - **Lightweight mode**: the clause directly contradicts the "Do NOT re-analyze the original source materials" instruction below. Including it forces workers to re-read the entire instruction-set per round per worker (3 workers × 2 rounds × 5+ files in the worst case) for no quality gain.
302
302
  - **Full-reanalysis mode**: workers DO need to re-read source materials, but only the analysis-worker file list (no `final-report-template.md`). If lead chooses to inject a reading clause here, it MUST mirror the audience-scoped enumeration in [okstra-lead-contract](./okstra-lead-contract.md) Phase 2 (no template).
303
303
 
304
- This is the single largest avoidable cost in `requirements-discovery`, `error-analysis`, and `implementation-planning` runs. Treat as BLOCKING.
304
+ This is the single largest avoidable cost in `requirements-discovery`, `error-analysis`, and `implementation-planning` runs. Treat as mandatory.
305
305
 
306
306
  ### Lightweight Re-verification Prompt
307
307
 
@@ -591,233 +591,4 @@ If `convergence.enabled: false`, this contract is skipped. Phase 6 operates usin
591
591
 
592
592
  ## Plan-body verification mode (implementation-planning only)
593
593
 
594
- This section defines a **second, independent** convergence round that fires only for `task-type = implementation-planning`. The round verifies the *consolidated plan* that the report-writer worker has authored, not the worker findings that were already reconciled earlier.
595
-
596
- ### Lifecycle position (BLOCKING)
597
-
598
- Plan-body verification runs **after** finding convergence and **after** the report-writer draft is written. Sequence inside a single implementation-planning run:
599
-
600
- ```
601
- Phase 4 workers produce independent analyses (Findings F-001…)
602
- → Phase 5.5 FINDING convergence (this contract, sections "Convergence Algorithm" through "Convergence State Artifact")
603
- → Phase 6 report-writer authors final-report draft (consolidated Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback)
604
- → PLAN-BODY VERIFICATION ROUND ← new — described below
605
- → User Approval gate (top-of-report `- [ ] Approved` marker is rendered only when this round's Gate result is `passed` or `passed-with-dissent`)
606
- → implementation phase (separate run)
607
- ```
608
-
609
- Plan-body verification MUST NOT replace, precede, or be conflated with the Phase 5.5 finding convergence above. They are two distinct rounds with different inputs (findings vs. consolidated plan body), different ID schemes (`F-*` vs. `P-*`), and different state files.
610
-
611
- ### MUTUAL EXCLUSION (BLOCKING)
612
-
613
- The finding queue (Phase 5.5) and the plan-item queue (this section) are **disjoint**:
614
-
615
- - A finding-convergence reverify prompt MUST NOT contain any `P-*` item.
616
- - A plan-body verification prompt MUST NOT contain any `F-*` finding.
617
- - The two rounds write to **different state files**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (findings, see §"Convergence State Artifact") vs. `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (plan items, see §"`plan-body-verification.json` schema").
618
- - Aggregation logic (verdict counting, classification) MUST NOT carry votes from one queue into the other.
619
-
620
- Mixing the two queues — for example, parsing a Phase 6 draft's Stepwise Execution Order step as if it were an `F-*` finding — is a contract violation. Future Claude reading this contract: if you find yourself tempted to "just reuse the finding queue for plan items, they're similar enough", stop. They are not similar enough; the verdict semantics differ (see §"Plan-body verdict semantics" below).
621
-
622
- ### Configuration
623
-
624
- Plan-body verification is configured under `convergence.planBodyVerification` in `task-manifest.json`:
625
-
626
- | Setting | Default | Description |
627
- |---------|---------|-------------|
628
- | `enabled` | `true` | If `false`, the round is skipped and the top-of-report Approval marker is rendered unconditionally (legacy behaviour). |
629
- | `maxRounds` | `1` | Upper bound. Plan-body verification is consistency / completeness checking, not fact checking — additional rounds rarely help. Range 1–3. |
630
- | `gating` | `true` | If `true` (default), `majority-disagree` blocks the Approval marker. If `false`, the round is advisory-only and the marker always renders. |
631
-
632
- Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (`_build_convergence_block`). The ctx knob `OKSTRA_PLAN_VERIFICATION=false` flips `planBodyVerification.enabled` to false.
633
-
634
- ### Plan-item extraction (Round 0 equivalent)
635
-
636
- From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, lead extracts plan items with the following prefixes (see also `templates/reports/final-report.template.md` §5.5.9):
637
-
638
- | Prefix | Source sub-section | One row per |
639
- |--------|--------------------|-------------|
640
- | `P-Opt-<N>` | `4.5.1 Option Candidates` | one Option (its File Structure list + interfaces + blast radius) |
641
- | `P-Step-<N>` | `4.5.4 Stepwise Execution Order` | one step (path + command + success signal) |
642
- | `P-Dep-<N>` | `4.5.5 Dependency / Migration Risk` | one dependency row |
643
- | `P-Val-<N>` | `4.5.6 Validation Checklist` | one checklist item |
644
- | `P-Rb-<N>` | `4.5.7 Rollback Strategy` | one rollback path |
645
- | `P-Req-<N>` | `4.5.8 Requirement Coverage` | one requirement coverage row |
646
-
647
- `4.5.2 Trade-off Matrix` and `4.5.3 Recommended Option` are NOT extracted as standalone plan items — the trade-off matrix is evaluated implicitly through each option's `P-Opt-*` verification, and the recommended option is one of those `P-Opt-*` rows.
648
-
649
- Each plan item inherits the `[TICKETID: ...]` tag of its source section (per the standard ticket-tagging contract).
650
-
651
- When extracting each item, lead also captures a **`subject`** — a plain one-line label (≤12 words) describing *what that item is* in the reader's terms, e.g. `P-Opt-1` → "Option A: upload v2 를 신규 모듈로 분리", `P-Step-1.1` → "Stage 1 Step 2: `npm run test:v2` 로 회귀 검증". This is a label-capture, not new analysis. The `subject` is what §5.5.9 renders as the per-item heading so the reader knows *what* each AGREE/DISAGREE is about without cross-referencing §4.5; a bare `P-*` ID with no subject is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_item_subject_substance` fails a subject that is a placeholder — under 3 chars, equal to the item id, or shaped like a bare `P-*` id.
652
-
653
- ### Plan-body verdict semantics
654
-
655
- The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their meaning is plan-specific:
656
-
657
- - **AGREE**: the item is executable as written *and* internally consistent with other items in the plan.
658
- - **DISAGREE(<kind>)**: the item is broken. `<kind>` MUST be one of:
659
- - `a` — a concrete referenced file path / symbol **contradicts** a different concrete path / symbol for the same artifact in another step or option's File Structure list (a genuine mismatch between two spelled-out references). An abbreviated / ellipsis (`…`) / under-specified path is NOT kind `a` — it contradicts nothing, it is merely imprecise notation; classify it as `b`.
660
- - `b` — a command **or a referenced path** is not executable or is ambiguous — including an abbreviated, ellipsis, or under-specified path that does not resolve as written
661
- - `c` — validation signal is not observable
662
- - `d` — rollback violates commit / dependency order
663
- - `e` — item contradicts the trade-off matrix
664
- - `f` — requirement coverage row cites no concrete option / stage / step, cites a non-existent option / stage / step, or marks a requirement `covered` while the cited plan item does not satisfy the row's stated requirement. A row that cites an existing option / stage / step is concrete for this purpose even if that option's File Structure paths are abbreviated — path imprecision inside the cited option is kind `b` on that option's own item, not `f` on the coverage row.
665
- - **SUPPLEMENT**: the item is sound but is missing a dependency / edge case / precondition.
666
-
667
- Worker non-result handling (`timeout`, `error`, no result file, wrapper `cli-failure`) is identical to finding convergence: do NOT aggregate as DISAGREE, record `contract-violation`, and apply the round-level abort rule below.
668
-
669
- ### Mode constraint
670
-
671
- Plan-body verification only supports **lightweight mode** (defined in §"Verification Mode" above). `full-reanalysis` is not meaningful here because the "original source materials" for a plan item are the worker's own analysis plus the lead-mediated synthesis — there is no independent ground truth to re-read. The manifest's top-level `verificationMode` is ignored for this round; lightweight is always used.
672
-
673
- Exception for `P-Req-*`: verifiers still MUST NOT re-open the original task brief for this round, but they MUST compare the requirement text embedded in the `Requirement Coverage` row with the cited Option / Stage / Step in the draft plan. A row is not sound merely because it says `covered`; the cited plan item must actually satisfy the stated requirement.
674
-
675
- ### Adversarial plan-body posture
676
-
677
- When `config.adversarial == true` (the default for `implementation-planning`; see the top-level §"Configuration" table), the plan-body round runs with an **adversarial posture**. The classification rules and gate arithmetic in §"Round protocol" are UNCHANGED — `majority-disagree` (a *majority* of analysers DISAGREE) remains the only classification that blocks the Approval marker, and `dissent-isolated` still passes the gate. Adversarial mode changes only *how each verifier evaluates an item*:
678
-
679
- - The burden of proof sits on the plan: an item earns `AGREE` only if the verifier actively tried to break it and could not.
680
- - The verifier MUST open the file paths / symbols / commands the item cites and confirm they exist and are executable as written. This is the one allowed widening of the lightweight "judge from internal consistency and stated commands / paths" rule — confirming the existence of cited paths is not "re-analyzing the original requirements".
681
- - If a cited path / command / validation signal cannot be confirmed, the verifier responds `DISAGREE(<kind>)` with the applicable breakage kind (a–e); uncertainty resolves toward DISAGREE, not AGREE.
682
- - **Single-vote-blocking kinds.** A single `DISAGREE` is approval-blocking on its own — no majority needed — when the breakage kind is `a` (cited path/symbol mismatch) or `d` (rollback violates commit/dependency order) on *any* plan item, or `f` (requirement-coverage mismatch) on a `P-Req-*` item. These defects are concrete, safety-critical, and adversarially verifiable (the verifier confirmed the cited path / order / requirement), so one correct dissent must not be outvoted. Each creates a `majority-disagree` classification and MUST become a `Blocks=approval` clarification row. Kinds `b` / `c` / `e` still need a majority — `b` especially is prone to planning-vs-implementation environment false positives. Because `a` is reserved for a concrete contradiction between two spelled-out references (see §"Plan-body verdict semantics"), an abbreviated / ellipsis / under-specified path is raised as `b` (majority-gated), never `a` — a lone "this path is abbreviated" dissent must not single-vote-block on notation alone, and it is especially not blocking on a *rejected* option that will never be implemented. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` (`_SINGLE_VOTE_BLOCKING_KINDS = {a, d}` + the P-Req `f` rule).
683
-
684
- Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in §"Adversarial Verification Mode" applies to finding convergence only (see §"Mode constraint"); the adversarial posture here only changes verifier behaviour, not the mode. This raises verification *quality* (active refutation, plan-side burden). The gate *threshold* stays majority-based for the majority-gated kinds (`b`/`c`/`e`), with the single-vote-blocking exception above for the concrete, safety-critical kinds (`a`/`d`, and `f` on P-Req). A majority requires at least two participating (non-error) votes, so a lone surviving `DISAGREE` whose peer returned a non-result does NOT block on a majority-gated kind — a worker failure must not make the gate stricter than a healthy roster would.
685
-
686
- ### Round protocol (single round at default `maxRounds=1`)
687
-
688
- 1. Lead parses the report-writer draft and extracts the `P-*` plan items.
689
- 2. For each analyser worker in the roster (`claude`, `codex`, and `antigravity` if opted in), lead constructs a reverify prompt using the template in §"Plan-body reverify prompt" below.
690
- 3. Dispatch uses the same wrapper infrastructure as finding convergence. The `--role-slug` is `<role>-plan-verify-r<N>`. Result file path: `runs/<task-type>/worker-results/<role-slug>-plan-verify-r<N>-implementation-planning-<seq>.md`.
691
- 4. After all dispatches return, lead aggregates verdicts per `P-*` item across workers and classifies each:
692
- - `full-consensus` — all participating analysers `AGREE` (SUPPLEMENT counts as agree on the item itself).
693
- - `partial-consensus` — majority `AGREE`, dissenting `DISAGREE` recorded.
694
- - `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE` — treat as `partial-consensus` for gate purposes; record dissent. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs. Plan-body classifications use this dedicated label to avoid the collision.)
695
- - `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes), OR any single-vote-blocking kind fires: one `DISAGREE(a)` / `DISAGREE(d)` on any item, or one `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks the Approval marker**.
696
- - `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
697
- 5. Gate result resolution:
698
- - any `majority-disagree` item present AND `gating=true` → `blocked-by-disagreement`
699
- - all dispatches non-result → `aborted-non-result`
700
- - any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
701
- - all items `full-consensus` → `passed`
702
- 6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `### 5.5.9 Plan Body Verification` in the final report's data.json (`implementationPlanning.planBodyVerification`, schema `schemas/final-report-v1.0.schema.json`; template at `templates/reports/final-report.template.md`). The §5.5.9 body is **grouped by plan item**: `planItems[]`, each carrying its `id`, its plain-language `subject` (rendered as the item heading), an optional `sourceSection`, an optional `clarificationId` (the `C-<N>` this item blocks on when `majority-disagree`), and a `verdicts[]` list (`worker / verdict / breakageKind / note`) — one verdict row per worker under that item. The renderer prints three fixed legends (gate values, verdict tokens, breakage kinds a–f) so the reader can decode every cell without opening this spec. The older flat `#### Verdict details` table (`Plan item / Worker / …`, one row per plan-item × worker pair) is superseded by the grouped layout — it hid *what* each vote was about behind a bare `P-*` ID; the subject heading is the fix. The validator's `Plan Body Verification` + `Gate result:` substring checks still gate this section.
703
- 7. For every `majority-disagree` item, lead adds a row to `## 1. Clarification Items` with:
704
- - new `C-<N>` ID (numbering continues from any existing rows)
705
- - `Statement` summarising the disagreement and the worker breakage `<kind>`
706
- - `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
707
- - `Blocks=approval`
708
- - the item's `planItems[].clarificationId` set to that `C-<N>` (1:1 link). `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item's `clarificationId` is missing, dangling, or points at a non-`approval` row.
709
- 8. The top-of-report `- [ ] Approved` marker line is rendered if and only if the Gate result is `passed` or `passed-with-dissent`. `validators/validate-run.py` `validate_phase_boundary` enforces this correspondence; manually adding the marker line when the gate did not pass is a contract violation.
710
-
711
- ### `plan-body-verification-<task-type>-<seq>.json` schema
712
-
713
- ```json
714
- {
715
- "schemaVersion": "1.0",
716
- "phase": "implementation-planning",
717
- "round": 1,
718
- "effectiveMaxRounds": 1,
719
- "gating": true,
720
- "verificationMode": "lightweight",
721
- "gateResult": "passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result",
722
- "planItems": [
723
- {
724
- "id": "P-Opt-1",
725
- "subject": "Option A: upload v2 를 신규 모듈로 분리",
726
- "sourceSection": "4.5.1",
727
- "ticketId": "<id-or-unknown>",
728
- "votes": {"claude-worker": "AGREE", "codex-worker": "AGREE"},
729
- "classification": "full-consensus",
730
- "clarificationId": null
731
- },
732
- {
733
- "id": "P-Step-3",
734
- "subject": "Stage 2 Step 3: 마이그레이션 실행 후 회귀 테스트",
735
- "sourceSection": "4.5.4",
736
- "ticketId": "TICKET-123",
737
- "votes": {"claude-worker": "DISAGREE(a)", "codex-worker": "DISAGREE(a)"},
738
- "classification": "majority-disagree",
739
- "clarificationId": "C-7"
740
- }
741
- ],
742
- "dispatches": [
743
- {"role": "claude-worker", "resultPath": "...", "terminalStatus": "completed"}
744
- ]
745
- }
746
- ```
747
-
748
- `dispatches[].terminalStatus` mirrors finding convergence (`completed | timeout | error | not-run | cli-failure`).
749
-
750
- `planItems[].classification` enum: `full-consensus | partial-consensus | dissent-isolated | majority-disagree | contested`. `contested` only appears when `maxRounds > 1`; at default `maxRounds=1` any otherwise-unresolved item folds into `partial-consensus` per the round protocol above.
751
-
752
- `planItems[].votes.<worker>` is the verbatim verdict token emitted by the worker — `AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT` — or `verification-error` for terminal non-result dispatches. The `DISAGREE` token retains its `<kind>` suffix so the breakage class is recoverable from the state file alone.
753
-
754
- ### Plan-body reverify prompt
755
-
756
- Required prompt anchor headers are identical to finding convergence (see §"Required reverify-prompt anchor headers"). The prompt body changes from F-* listing to P-* listing:
757
-
758
- ```
759
- You are <worker-role> performing plan-body verification for <task-key> (round 1).
760
-
761
- ## Instructions
762
-
763
- Review the following items extracted from the consolidated implementation plan
764
- authored after your initial analysis. For EACH item, respond with exactly one
765
- verdict:
766
-
767
- - **AGREE**: The item is executable as written and internally consistent with
768
- other items in the plan.
769
- - **DISAGREE(<kind>)**: The item is broken. Cite which kind:
770
- (a) a concrete referenced file path / symbol contradicts a different concrete path / symbol for the same artifact elsewhere — a genuine mismatch; an abbreviated / ellipsis path is NOT (a), use (b),
771
- (b) command or referenced path is not executable or is ambiguous — including an abbreviated / ellipsis / under-specified path that does not resolve as written,
772
- (c) validation signal is not observable,
773
- (d) rollback violates commit / dependency order,
774
- (e) item contradicts the trade-off matrix,
775
- (f) requirement coverage row does not map the stated requirement to a concrete satisfying option / stage / step — citing an existing option counts as concrete even if that option's paths are abbreviated (that is (b) on the option's item, not (f)).
776
- - **SUPPLEMENT**: The item is sound but a dependency / edge case / precondition
777
- is missing.
778
-
779
- Do NOT re-analyze the original requirements. Judge solely from plan internal
780
- consistency and stated commands / paths. Do NOT inspect the original task brief
781
- or worker analyses for this round.
782
-
783
- For `P-Req-*` items, compare only the requirement text embedded in the row
784
- against the cited plan item(s). Do not open the original brief, but do reject
785
- coverage rows that cite no concrete option/stage/step or cite a plan item that
786
- does not satisfy the row's own requirement.
787
-
788
- ## Plan items to verify
789
-
790
- ### P-Step-3 [TICKETID: <id>]: <one-line summary>
791
- **From section**: 4.5.4 Stepwise Execution Order
792
- **Original text**:
793
- > <verbatim quote of the step>
794
-
795
- **Check**:
796
- - Are referenced file paths consistent with the option's File Structure list?
797
- - Is the named command executable as written?
798
- - Does the success criterion produce an observable signal?
799
-
800
- ### P-Opt-2 [TICKETID: <id>]: <one-line summary>
801
- ...
802
-
803
- ## Response format
804
-
805
- ### P-Step-3
806
- **Verdict**: AGREE | DISAGREE(<a|b|c|d|e>) | SUPPLEMENT
807
- **Explanation**: <2-3 sentences>
808
-
809
- ### P-Opt-2
810
- ...
811
- ```
812
-
813
- When `config.adversarial == true`, the lead prepends the adversarial framing from §"Adversarial plan-body posture" to the `## Instructions` block: the burden of proof is on the plan, the verifier opens and confirms every cited path / command, and an item whose cited references cannot be confirmed is answered `DISAGREE(<kind>)` rather than `AGREE`. The verdict tokens, breakage kinds (a–e), classification, and the majority gate threshold are unchanged. This prepended framing supersedes the template's "Judge solely from plan internal consistency" instruction for the adversarial round.
814
-
815
- The "Reverify prompt: required-reading suppression (BLOCKING)" rule (lightweight mode does NOT inject a `[Required reading]` clause) applies here as well.
816
-
817
- ### Worker non-result handling in plan-body round (BLOCKING)
818
-
819
- Mirrors finding convergence (§"Worker failure handling in reverify"). Concretely:
820
-
821
- - A dispatch that returns terminal non-result MUST NOT be aggregated as `DISAGREE`.
822
- - If at least one dispatch was issued AND **all** plan-body dispatches return non-result, the Gate result is `aborted-non-result`. Record one `contract-violation` event per non-result dispatch.
823
- - When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `## 1. Clarification Items` with `Statement="plan-body verification could not run — all workers returned non-result"`, `Kind=decision`, `Blocks=approval`, allowing the user to either retry the phase or override by manually flipping the frontmatter to `approved: true` (or running `--approve` on the resume command).
594
+ Moved to its own contract: [plan-body-verification](./plan-body-verification.md). It fires only for `task-type = implementation-planning`, as a Phase 6 sub-step after the report-writer draft read that file at that sub-step, NOT during the Phase 5.5 finding convergence this contract governs. The finding queue (`F-*`, this contract) and the plan-item queue (`P-*`, that contract) are disjoint — see its "MUTUAL EXCLUSION" section.