okstra 0.151.0 → 0.151.1

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 (30) hide show
  1. package/docs/cli.md +1 -1
  2. package/docs/project-structure-overview.md +1 -1
  3. package/package.json +1 -1
  4. package/runtime/BUILD.json +2 -2
  5. package/runtime/agents/workers/antigravity-worker.md +2 -1
  6. package/runtime/agents/workers/claude-worker.md +2 -1
  7. package/runtime/agents/workers/codex-worker.md +2 -1
  8. package/runtime/agents/workers/grok-worker.md +2 -1
  9. package/runtime/agents/workers/kimi-worker.md +2 -1
  10. package/runtime/agents/workers/report-writer-worker.md +1 -1
  11. package/runtime/bin/okstra-report-translate.py +32 -11
  12. package/runtime/prompts/launch.template.md +1 -1
  13. package/runtime/prompts/lead/convergence.md +16 -4
  14. package/runtime/prompts/lead/report-writer.md +26 -14
  15. package/runtime/prompts/lead/team-contract.md +2 -1
  16. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  17. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  18. package/runtime/prompts/profiles/final-verification.md +1 -1
  19. package/runtime/prompts/profiles/implementation-planning.md +8 -7
  20. package/runtime/python/okstra_ctl/analysis_packet.py +1 -0
  21. package/runtime/python/okstra_ctl/dispatch_state.py +5 -1
  22. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +7 -0
  23. package/runtime/python/okstra_ctl/report_finalize.py +9 -4
  24. package/runtime/python/okstra_ctl/worker_prompt_body.py +4 -2
  25. package/runtime/python/okstra_ctl/worker_prompt_contract.py +41 -1
  26. package/runtime/python/okstra_ctl/worker_prompt_headers.py +3 -0
  27. package/runtime/templates/implementation-worker-preamble.md +12 -3
  28. package/runtime/templates/report-writer-prompt-preamble.md +5 -1
  29. package/runtime/templates/worker-prompt-preamble.md +12 -3
  30. package/runtime/validators/validate-run.py +111 -0
package/docs/cli.md CHANGED
@@ -401,7 +401,7 @@ worker roster contains Claude, Codex, or Antigravity.
401
401
 
402
402
  For a Codex lead dry run, use `okstra codex-run <args...>`. It adds `--render-only --lead-runtime codex` itself and prints the prepared task bundle and lead prompt without dispatching workers.
403
403
  The generated team-state, run manifest, and task manifest point `leadEventsPath` to `runs/<task-type>/state/lead-events-<task-type>-<seq>.jsonl`; rendering records a `bundle-prepared` event.
404
- Then `okstra codex-dispatch --project-root <dir> --run-manifest <run-manifest> [--workers <csv>]` reads each persisted assignment. `runner=native-session` rows stay with the current Codex host; `runner=cli-wrapper` rows run through their registered Claude, Antigravity, Grok, Kimi, or report-writer wrapper. The report-writer provider and model come from the manifest without a Codex-only opt-in flag; on success, postprocessing runs token-usage substitution → render-views → spawn-followups → validate-run in order.
404
+ Then `okstra codex-dispatch --project-root <dir> --run-manifest <run-manifest> [--workers <csv>]` reads each persisted assignment. `runner=native-session` rows stay with the current Codex host; `runner=cli-wrapper` rows run through their registered Claude, Antigravity, Grok, Kimi, or report-writer wrapper. The report-writer provider and model come from the manifest without a Codex-only opt-in flag; on success, postprocessing runs check-source → token-usage substitution → render-views → spawn-followups → validate-run in order.
405
405
 
406
406
  The Codex worker (`--workers codex`, `--codex-model`) and Codex lead runtime are separate. The former creates a worker assignment whose runner depends on the host; the latter selects Codex as the native lead boundary. On Claude Code the Codex worker uses a CLI wrapper, while on Codex it uses the host-native worker/session primitive.
407
407
 
@@ -312,7 +312,7 @@ Important modules:
312
312
  | `plan_items.py`, `plan_items_cli.py` | deterministic extraction of the report-writer data.json `P-*` plan-item queue plus the `okstra plan-items extract` / `validate` adapter |
313
313
  | `scope_provenance.py` | single source of truth for the scope-provenance grammar every phase-emitted requirement must declare, shared by `validators/validate-run.py` and `validators/validate_fanout.py` so the planning report and fan-out packets cannot drift |
314
314
  | `worker_artifact_paths.py` | canonical worker artifact path derivation (e.g. `audit_sidecar_rel` inserts `-audit-` after the first `-worker-` token), so dispatch and validation agree on non-canonical-path rejection |
315
- | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `token-usage` → `render-views` → `spawn-followups` → `validate-run` in that load-bearing order, stops at the first non-zero exit and names the failing step. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
315
+ | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `check-source` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` in that load-bearing order, stops at the first non-zero exit and names the failing step. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
316
316
  | `wrapper_status.py` | worker wrapper status sidecar reader — the host-side reader of the sidecar written by `okstra-wrapper-status.py` (the heartbeat writer) |
317
317
  | `task_target.py` | shared helper resolving `task-key → (task_root, project_root)` (`resolve_task_root`) |
318
318
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.151.0",
3
+ "version": "0.151.1",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.151.0",
3
- "builtAt": "2026-08-05T06:06:04.770Z",
2
+ "package": "0.151.1",
3
+ "builtAt": "2026-08-05T07:39:18.187Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -128,7 +128,7 @@ The Antigravity CLI's own exit terminates the underlying analysis; this wrapper
128
128
 
129
129
  ## MCP Scope
130
130
 
131
- This wrapper does NOT invoke MCP tools directly. MCP availability inside the Antigravity CLI is governed by the underlying CLI's own configuration. The `## Available MCP Servers` block from the lead prompt is forwarded verbatim into the dispatched prompt for record-keeping and so the Antigravity CLI's own logic can decide what to call this wrapper does not gate or filter it.
131
+ This wrapper does NOT invoke MCP tools directly. The analysis packet's `Available MCP Servers` section is the canonical server list. If the section is absent or says none, MCP is unavailable for this run; never infer tools from host configuration. The Antigravity CLI can use a packet-listed server only when its own configuration exposes that server; otherwise it records `MCP not available in this CLI`. This wrapper does not gate, filter, or invoke those tools.
132
132
 
133
133
  ## Prompt Composition
134
134
 
@@ -151,6 +151,7 @@ Before invoking the Antigravity CLI, you MUST:
151
151
 
152
152
  1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` and verify the CLI run will Read both selected files end-to-end. The first owns audience procedure; the second owns sidecar schema and write rules. Persist and forward both anchors unchanged; never substitute the analysis preamble for an implementation audience. **Exception — `-reverify-r<N>-` dispatches**: a Phase 5.5 re-verification prompt deliberately omits both reading contracts and uses its lightweight prompt contract; do NOT return a sentinel for those two omitted reading anchors. The `**Errors log path:**` / `**Errors sidecar path:**` gate still applies.
153
153
  2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
154
+ 3. When the prompt carries `**Evidence ledger:** required-v1`, verify the CLI follows the selected preamble's `Evidence read ledger` procedure for every claim-evidence file it opens. Do not define or infer another audit-row format in this wrapper.
154
155
 
155
156
  Extract `**Audit sidecar path:** <abs-path>` verbatim from the lead's dispatch prompt and verify that the value is absolute. If the header is absent or the value is not absolute, return `ANTIGRAVITY_AUDIT_PATH_MISSING: lead prompt did not include a valid absolute **Audit sidecar path:** header` without invoking the CLI. Do NOT synthesize the audit sidecar path from the task type, worker name, or sequence.
156
157
 
@@ -50,7 +50,7 @@ Unlike the Codex / Antigravity workers, you are an in-process Claude subagent
50
50
  - **Verifier QA-gate exception:** verifier roles MAY use the same `cd <WORKTREE> && <cmd>` shape when executing project-declared `qaCommands` (lint / format / typecheck / test) from `project.json`, since those commands are cwd-sensitive by nature. Outside the QA gate, verifiers still read with absolute paths only — do NOT use `cd` for file inspection.
51
51
  - **No extra chaining beyond `cd && cmd`:** the permission matcher only allows the exact two-segment shape `cd <PATH> && <single-command>`. Do NOT append additional pipes, semicolons, redirects, or `&&` chains — e.g. `cd ... && cargo test ... 2>&1 | tail -20; echo "exit:$?"` will trigger a permission prompt every dispatch because the trailing `| tail`, `; echo`, and `2>&1` tokens disqualify the prefix match against `Bash(cargo:*)`. Let Claude Code capture the full stdout/stderr and exit code natively — do not post-process with `tail`, `head`, or `echo "exit:$?"`. If output truncation is genuinely needed, run the command first and read the result in a separate tool call.
52
52
 
53
- 5. **MCP usage**: The canonical list of MCP servers and tools available for this run lives in the lead prompt's `## Available MCP Servers` section (sourced from `.okstra/project.json`'s `mcpServers` array). When the task requires inspection of an external system covered by one of those servers, call the listed tool directly by name (e.g. `mcp__<server>__<tool>`). Do NOT shell out via `claude --mcp-cli call ...` or run the tool name as a Bash command — those are not valid invocation paths. If a server you need is not listed, record `MCP not available for this run` in your worker output rather than guessing a tool name.
53
+ 5. **MCP usage**: The canonical list of MCP servers and tools available for this run lives in the analysis packet's `Available MCP Servers` section. If the section is absent or says none, treat MCP as unavailable for this run; never infer tools from host configuration. When the task requires inspection of an external system covered by a listed server, call the tool directly by name (e.g. `mcp__<server>__<tool>`). Do NOT shell out via `claude --mcp-cli call ...` or run the tool name as a Bash command — those are not valid invocation paths. If a server you need is not listed, record `MCP not available for this run` in your worker output rather than guessing a tool name.
54
54
 
55
55
  6. If your dispatch prompt carries a `**Phase 1.5 Grilling Log:** <abs-path>` anchor header (the lead injects it only on `improvement-discovery` runs), the file it points to is the authoritative scope and lens definition. Read it at the absolute path from the anchor — do NOT synthesize the path from `<RUN_DIR>`. Use its `Resolved scope` and `Resolved lenses` blocks and do NOT re-interpret the brief's raw `scan-scope` / `priority-lenses` fields. Findings that violate the resolved lens whitelist or scope are rejected by `validators/validate_improvement_report.py`.
56
56
 
@@ -60,6 +60,7 @@ Before producing any output, you MUST:
60
60
 
61
61
  1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` from the lead prompt and Read both selected files end-to-end with one full-file `Read` each. The preamble owns audience procedure; the error contract owns sidecar schema and write rules. Never replace the selected path with a hard-coded analysis preamble.
62
62
  2. Read every primary input file the lead enumerated under `## Inputs` (or equivalent heading) end-to-end, following the selected preamble. Analysis workers normally receive `analysis-packet.md`; implementation workers receive their role sidecar and approved deliverable inputs.
63
+ 3. When the prompt carries `**Evidence ledger:** required-v1`, follow the selected preamble's `Evidence read ledger` procedure for every claim-evidence file you open. Do not invent a separate audit-row format here.
63
64
 
64
65
  **Heartbeat — write the audit sidecar EARLY and APPEND per stage (BLOCKING).** This worker runs as an in-process Agent or a fresh-session tmux pane, so the lead has no `BashOutput`-style liveness signal while it waits for your return — the audit sidecar is the only signal that survives a silent hang.
65
66
 
@@ -128,7 +128,7 @@ The Codex CLI's own exit terminates the underlying analysis; this wrapper termin
128
128
 
129
129
  ## MCP Scope
130
130
 
131
- This wrapper does NOT invoke MCP tools directly. MCP availability inside the Codex CLI is governed by the underlying CLI's own configuration. The `## Available MCP Servers` block from the lead prompt is forwarded verbatim into the dispatched prompt for record-keeping and so the Codex CLI's own logic can decide what to call this wrapper does not gate or filter it.
131
+ This wrapper does NOT invoke MCP tools directly. The analysis packet's `Available MCP Servers` section is the canonical server list. If the section is absent or says none, MCP is unavailable for this run; never infer tools from host configuration. The Codex CLI can use a packet-listed server only when its own configuration exposes that server; otherwise it records `MCP not available in this CLI`. This wrapper does not gate, filter, or invoke those tools.
132
132
 
133
133
  ## Prompt Composition
134
134
 
@@ -151,6 +151,7 @@ Before invoking the Codex CLI, you MUST:
151
151
 
152
152
  1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` and verify the CLI run will Read both selected files end-to-end. The first owns audience procedure; the second owns sidecar schema and write rules. Persist and forward both anchors unchanged; never substitute the analysis preamble for an implementation audience. **Exception — `-reverify-r<N>-` dispatches**: a Phase 5.5 re-verification prompt deliberately omits both reading contracts and uses its lightweight prompt contract; do NOT return a sentinel for those two omitted reading anchors. The `**Errors log path:**` / `**Errors sidecar path:**` gate still applies.
153
153
  2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
154
+ 3. When the prompt carries `**Evidence ledger:** required-v1`, verify the CLI follows the selected preamble's `Evidence read ledger` procedure for every claim-evidence file it opens. Do not define or infer another audit-row format in this wrapper.
154
155
 
155
156
  Extract `**Audit sidecar path:** <abs-path>` verbatim from the lead's dispatch prompt and verify that the value is absolute. If the header is absent or the value is not absolute, return `CODEX_AUDIT_PATH_MISSING: lead prompt did not include a valid absolute **Audit sidecar path:** header` without invoking the CLI. Do NOT synthesize the audit sidecar path from the task type, worker name, or sequence.
156
157
 
@@ -128,7 +128,7 @@ The Grok CLI's own exit terminates the underlying analysis; this wrapper termina
128
128
 
129
129
  ## MCP Scope
130
130
 
131
- This wrapper does NOT invoke MCP tools directly. MCP availability inside the Grok CLI is governed by the underlying CLI's own configuration. The `## Available MCP Servers` block from the lead prompt is forwarded verbatim into the dispatched prompt for record-keeping and so the Grok CLI's own logic can decide what to call this wrapper does not gate or filter it.
131
+ This wrapper does NOT invoke MCP tools directly. The analysis packet's `Available MCP Servers` section is the canonical server list. If the section is absent or says none, MCP is unavailable for this run; never infer tools from host configuration. The Grok CLI can use a packet-listed server only when its own configuration exposes that server; otherwise it records `MCP not available in this CLI`. This wrapper does not gate, filter, or invoke those tools.
132
132
 
133
133
  ## Prompt Composition
134
134
 
@@ -151,6 +151,7 @@ Before invoking the Grok CLI, you MUST:
151
151
 
152
152
  1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` and verify the CLI run will Read both selected files end-to-end. The first owns audience procedure; the second owns sidecar schema and write rules. Persist and forward both anchors unchanged; never substitute the analysis preamble for an implementation audience. **Exception — `-reverify-r<N>-` dispatches**: a Phase 5.5 re-verification prompt deliberately omits both reading contracts and uses its lightweight prompt contract; do NOT return a sentinel for those two omitted reading anchors. The `**Errors log path:**` / `**Errors sidecar path:**` gate still applies.
153
153
  2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
154
+ 3. When the prompt carries `**Evidence ledger:** required-v1`, verify the CLI follows the selected preamble's `Evidence read ledger` procedure for every claim-evidence file it opens. Do not define or infer another audit-row format in this wrapper.
154
155
 
155
156
  Extract `**Audit sidecar path:** <abs-path>` verbatim from the lead's dispatch prompt and verify that the value is absolute. If the header is absent or the value is not absolute, return `GROK_AUDIT_PATH_MISSING: lead prompt did not include a valid absolute **Audit sidecar path:** header` without invoking the CLI. Do NOT synthesize the audit sidecar path from the task type, worker name, or sequence.
156
157
 
@@ -128,7 +128,7 @@ The Kimi CLI's own exit terminates the underlying analysis; this wrapper termina
128
128
 
129
129
  ## MCP Scope
130
130
 
131
- This wrapper does NOT invoke MCP tools directly. MCP availability inside the Kimi CLI is governed by the underlying CLI's own configuration. The `## Available MCP Servers` block from the lead prompt is forwarded verbatim into the dispatched prompt for record-keeping and so the Kimi CLI's own logic can decide what to call this wrapper does not gate or filter it.
131
+ This wrapper does NOT invoke MCP tools directly. The analysis packet's `Available MCP Servers` section is the canonical server list. If the section is absent or says none, MCP is unavailable for this run; never infer tools from host configuration. The Kimi CLI can use a packet-listed server only when its own configuration exposes that server; otherwise it records `MCP not available in this CLI`. This wrapper does not gate, filter, or invoke those tools.
132
132
 
133
133
  ## Prompt Composition
134
134
 
@@ -151,6 +151,7 @@ Before invoking the Kimi CLI, you MUST:
151
151
 
152
152
  1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` and verify the CLI run will Read both selected files end-to-end. The first owns audience procedure; the second owns sidecar schema and write rules. Persist and forward both anchors unchanged; never substitute the analysis preamble for an implementation audience. **Exception — `-reverify-r<N>-` dispatches**: a Phase 5.5 re-verification prompt deliberately omits both reading contracts and uses its lightweight prompt contract; do NOT return a sentinel for those two omitted reading anchors. The `**Errors log path:**` / `**Errors sidecar path:**` gate still applies.
153
153
  2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
154
+ 3. When the prompt carries `**Evidence ledger:** required-v1`, verify the CLI follows the selected preamble's `Evidence read ledger` procedure for every claim-evidence file it opens. Do not define or infer another audit-row format in this wrapper.
154
155
 
155
156
  Extract `**Audit sidecar path:** <abs-path>` verbatim from the lead's dispatch prompt and verify that the value is absolute. If the header is absent or the value is not absolute, return `KIMI_AUDIT_PATH_MISSING: lead prompt did not include a valid absolute **Audit sidecar path:** header` without invoking the CLI. Do NOT synthesize the audit sidecar path from the task type, worker name, or sequence.
156
157
 
@@ -69,7 +69,7 @@ Write the audit sidecar at `**Audit sidecar path:**` before required reading, th
69
69
 
70
70
  5. Anchor all file operations to the absolute `Project Root`. Use absolute paths everywhere — do not rely on inherited cwd, do not `cd`.
71
71
 
72
- 6. **MCP usage**: If the lead prompt's `## Available MCP Servers` block lists tools, you may invoke them by name (e.g. `mcp__<server>__<tool>`) to verify evidence cited by analysis workers. Do not invent MCP tools that are not listed.
72
+ 6. **MCP usage**: The analysis packet's `Available MCP Servers` section is canonical. If the section is absent or says none, treat MCP as unavailable for this run; never infer tools from host configuration. You may invoke packet-listed tools by name (e.g. `mcp__<server>__<tool>`) to verify evidence cited by analysis workers. Do not invent MCP tools that are not listed.
73
73
 
74
74
  ## Required Reading Before Authoring
75
75
 
@@ -8,7 +8,10 @@ Usage:
8
8
  ``extract`` writes the translator's work list — every pointer the report holds
9
9
  a translatable string at, paired with the English text. The translator fills
10
10
  in the values rather than authoring pointers, so a sidecar cannot cite a path
11
- the document does not have.
11
+ the document does not have. It refuses a data.json that is itself over the
12
+ Korean-prose limit: the work list would pair every pointer with target-language
13
+ text, and the translator would spend a full pass translating a document into
14
+ the language it is already in.
12
15
 
13
16
  ``check`` is the translator's own gate before it returns: it resolves every
14
17
  pointer in the sidecar against the report and reports what is still English.
@@ -68,9 +71,31 @@ def _load(path: Path) -> dict:
68
71
  return payload
69
72
 
70
73
 
74
+ def _english_source_failure(data_path: Path, data: dict) -> str | None:
75
+ """The Korean-prose message for *data*, or None when it reads as English."""
76
+ share, _ = hangul_share(data)
77
+ if share < HANGUL_PROSE_LIMIT:
78
+ return None
79
+ return (
80
+ f"error: {data_path.name} was authored in Korean "
81
+ f"({share:.0%} of its prose, limit {HANGUL_PROSE_LIMIT:.0%}). "
82
+ "The data.json is the English SSOT every later phase reads; the "
83
+ "report language selects the human HTML's language and is served "
84
+ "by the Phase 7 translator, not by authoring the SSOT in it.\n"
85
+ )
86
+
87
+
71
88
  def cmd_extract(args: argparse.Namespace) -> int:
72
89
  data_path = Path(args.data).resolve()
73
90
  data = _load(data_path)
91
+ # Refuse before building the work list. Extracting from a Korean SSOT
92
+ # produces a translation from the target language into itself: a
93
+ # full-cost artifact whose English column is not English, discovered
94
+ # only later when `check-source` fails the finalize step.
95
+ failure = _english_source_failure(data_path, data)
96
+ if failure is not None:
97
+ sys.stderr.write(failure)
98
+ return 1
74
99
  strings = extract(data)
75
100
  out_path = translation_source_path(data_path)
76
101
  lang = str((data.get("meta") or {}).get("reportLanguage") or "")
@@ -139,22 +164,18 @@ def cmd_check(args: argparse.Namespace) -> int:
139
164
 
140
165
  def cmd_check_source(args: argparse.Namespace) -> int:
141
166
  data_path = Path(args.data).resolve()
142
- share, length = hangul_share(_load(data_path))
167
+ data = _load(data_path)
168
+ share, length = hangul_share(data)
169
+ failure = _english_source_failure(data_path, data)
143
170
  payload = {
144
- "ok": share < HANGUL_PROSE_LIMIT,
171
+ "ok": failure is None,
145
172
  "hangulShare": round(share, 4),
146
173
  "limit": HANGUL_PROSE_LIMIT,
147
174
  "proseChars": length,
148
175
  }
149
176
  print(json.dumps(payload, ensure_ascii=False))
150
- if not payload["ok"]:
151
- sys.stderr.write(
152
- f"error: {data_path.name} was authored in Korean "
153
- f"({share:.0%} of its prose, limit {HANGUL_PROSE_LIMIT:.0%}). "
154
- "The data.json is the English SSOT every later phase reads; the "
155
- "report language selects the human HTML's language and is served "
156
- "by the Phase 7 translator, not by authoring the SSOT in it.\n"
157
- )
177
+ if failure is not None:
178
+ sys.stderr.write(failure)
158
179
  return 1
159
180
  return 0
160
181
 
@@ -75,7 +75,7 @@ Emit one `PROGRESS: <phase-id> <verb-phrase>` line as plain user-facing text at
75
75
  ## Available MCP Servers
76
76
 
77
77
  {{AVAILABLE_MCP_SERVERS}}
78
- - The full usage policy and per-phase rules live in the analysis packet's `Available MCP Servers` extract. Inject only the one-line pointer below into each analysis-worker prompt: `**MCP servers:** follow the analysis packet's "Available MCP Servers" section (already in your Required reading).`
78
+ - The full usage policy and per-phase rules live in the analysis packet's `Available MCP Servers` extract. Inject only the one-line pointer below into each analysis-worker prompt: `**MCP servers:** follow the analysis packet's "Available MCP Servers" section. If the section is absent or says none, treat MCP as unavailable for this run; never infer tools from host configuration.`
79
79
  - **Invocation rule (forward to every worker prompt)**: MCP tools are addressed through the executing worker provider's native tool interface — never by running the tool name as a shell command. In-process workers call the tool directly; CLI workers use that CLI's configured MCP transport. A worker without the configured MCP server records `MCP not available in this CLI` instead of guessing.
80
80
  - Codex worker and Antigravity worker run external CLIs; they can only use these MCP servers if their own CLI configs mirror them. If not, instruct the worker to record `MCP not available in this CLI` in its `Missing Information or Assumptions` block rather than guessing or shell-falling-back.
81
81
  - MCP queries are evidence-grade. Cite server, table, and the SELECT used in worker output. MCP must NOT be used as a write path in any phase, including `implementation`.
@@ -106,7 +106,7 @@ convergence-<task-type>-<seq>.json
106
106
 
107
107
  Follow this protocol exactly:
108
108
 
109
- 0. The machine-readable inputs are `schemas/convergence-groups-v1.0.schema.json`, `schemas/convergence-round-results-v1.0.schema.json`, and `schemas/convergence-critic-results-v1.0.schema.json`. Use `okstra convergence example --kind <groups|round-results|critic-results>` for deterministic valid examples; the command writes only JSON to stdout.
109
+ 0. Two schemas describe what the reducer reads: `schemas/convergence-groups-v1.0.schema.json` feeds step 1's `seed --groups`, and `schemas/convergence-round-results-v1.0.schema.json` feeds step 4's `apply-round --results`. `schemas/convergence-critic-results-v1.0.schema.json` is a third shape but **not** a reducer input — it describes the critic worker's own result document. Step 6's `apply-critic-gaps --results` takes the coverage batch you assemble from those candidates plus each analyser's vote (`{schemaVersion, taskKey, mode, provider, modelExecutionValue, dispatches[], gaps[]}`, spelled out in §"Coverage critic pass" §"State"); feeding the critic document straight in is rejected, by design. `okstra convergence example --kind <groups|round-results|critic-results>` prints a deterministic valid instance of each and writes only JSON to stdout.
110
110
  1. Run `okstra convergence seed --groups <groups> --work-state <work> --final-state <final> --migration-dir <state/migrations>`. A `reuse-final` action means validate the existing final and continue to Phase 6. `create-work`, `resume-work`, and `restart-round0` continue with planning.
111
111
  2. Run `okstra convergence plan-round --work-state <work> --plan <round-plan>`. This is read-only with respect to the working state.
112
112
  3. When the plan action is `dispatch`, create exactly one reverify prompt for each `dispatches[]` row and dispatch it through the selected runtime adapter. Its findings are exactly that row's `findingIds`.
@@ -541,11 +541,23 @@ Runs only when `convergence.critic.enabled == true` (set by `--critic <provider>
541
541
 
542
542
  The critic input is the Round 0 consolidated finding list. Reverify rounds only classify findings — they never add or remove them (in-round queue insertions are forbidden, see §"Convergence State Artifact" `carriedForwardCount`) — so the critic dispatch MUST NOT wait for classification to finish:
543
543
 
544
- - **Dispatch**: immediately after Round 0 grouping, CONCURRENTLY with the first reverify round's dispatches. When the verification queue is empty after Round 0 (no reverify round runs), dispatch right after grouping. Concurrent dispatch to the same provider is safe — the critic result path (`<provider>-critic-...`) never collides with a reverify result path.
544
+ - **Dispatch**: immediately after Round 0 grouping, CONCURRENTLY with the first reverify round's dispatches. When the verification queue is empty after Round 0 (no reverify round runs), dispatch right after grouping. Concurrent dispatch to the same provider is safe — the critic result path (`<provider>-worker-critic-...`) never collides with a reverify result path.
545
545
  - **Gap verification + merge**: only after BOTH the finding-convergence loop has exited AND the critic result is collected, and BEFORE the Phase 6 report-writer dispatch. If the loop exited `aborted-non-result`, do NOT dispatch a gap-verification round — record every gap in `unverifiedGaps[]` per §"Gap verification".
546
546
 
547
547
  ### Dispatch (fresh one-shot)
548
- Dispatch one fresh pass to `config.critic.provider` through `redispatch_worker`, with `model = config.critic.modelExecutionValue` and `dispatchKind = "critic"`. If the model value is empty, record `critic-skipped: model-unresolved`; never dispatch without a model. Result path: `runs/<task-type>/worker-results/<provider>-critic-<task-type>-<seq>.md`. The critic prompt seeds the consolidated findings and asks ONLY for coverage gaps:
548
+ Dispatch one fresh pass to `config.critic.provider` through `redispatch_worker`, with `model = config.critic.modelExecutionValue` and `dispatchKind = "critic"`. If the model value is empty, record `critic-skipped: model-unresolved`; never dispatch without a model. Result path: `runs/<task-type>/worker-results/<provider>-worker-critic-<task-type>-<seq>.md`.
549
+
550
+ The `-worker-` token is load-bearing, not decoration: the critic prompt carries the same generated anchor headers as every other worker ([team-contract](./team-contract.md) §"Worker prompts"), and its `**Audit sidecar path:**` comes from passing that result path through `okstra_ctl.worker_artifact_paths.audit_sidecar_rel()`, which inserts `-audit-` after the token and raises without it. A `<provider>-critic-...` name leaves the lead choosing between breaking the contract and hand-inventing the sidecar name. Note that `originWorker` stays `"<provider>-critic"` — that is a worker id in the convergence state, not a filename, and the two do not have to match.
551
+
552
+ The critic prompt seeds the consolidated findings and asks ONLY for coverage gaps:
553
+
554
+ Required reading before proposing a gap:
555
+ - the current run's `analysis-packet.md` for requirements and phase scope;
556
+ - `convergence-groups-<task-type>-<seq>.json` for the complete Round 0 ledger;
557
+ - every initial analysis-worker result named by team-state;
558
+ - each matching audit sidecar, to distinguish an uninspected path from a claim that was inspected but summarized during grouping.
559
+
560
+ Operational guardrails are not task requirements. A gap must trace to a brief requirement, an analysis-packet scope item, a source path the packet authorizes, or an evidence claim in a worker result. Do NOT infer missing verification from a one-line summary; open the named result and audit sidecar first.
549
561
 
550
562
  ```
551
563
  You are the coverage critic for <task-key>. Below are the consolidated findings
@@ -600,7 +612,7 @@ Promoted blockers enter `## 5.8 Acceptance Blockers`; since `accepted` requires
600
612
 
601
613
  ### State
602
614
 
603
- Critic output lives in the run's `worker-results/` directory (`runs/final-verification/worker-results/` for whole-task verification, `runs/final-verification/stage-<N>/worker-results/` for single-stage), filename `<provider>-critic-final-verification-<seq>.md`. The convergence state `config.critic` summary records `mode: "acceptance-devils-advocate"`, `candidatesProposed`, `confirmedBlockers`, `downgradedToResidual`; v1.3 enforces `candidatesProposed = confirmedBlockers + downgradedToResidual`, so no candidate can be silently dropped.
615
+ Critic output lives in the run's `worker-results/` directory (`runs/final-verification/worker-results/` for whole-task verification, `runs/final-verification/stage-<N>/worker-results/` for single-stage), filename `<provider>-worker-critic-final-verification-<seq>.md` (same `-worker-` token rule as §"Coverage critic pass" — the audit sidecar is derived from it). The convergence state `config.critic` summary records `mode: "acceptance-devils-advocate"`, `candidatesProposed`, `confirmedBlockers`, `downgradedToResidual`; v1.3 enforces `candidatesProposed = confirmedBlockers + downgradedToResidual`, so no candidate can be silently dropped.
604
616
 
605
617
  ## Output
606
618
 
@@ -48,15 +48,14 @@ The prompt MUST include, in this order at the top:
48
48
  10. The full `[Required reading]` clause (see [team-contract](./team-contract.md)) — for Phase 6 it adds two **per-task-type, instruction-set-local** read-only files, both scoped to this run's task-type by `okstra-ctl` at prep time:
49
49
  - `<instruction-set>/final-report-schema.json` — a task-type excerpt of schema v2. This is the binding authoring shape; the installed full schema is what the run is judged against. Do **NOT** pull the full repository schema because it is outside the task bundle.
50
50
  - `<instruction-set>/final-report-template.md` — the AI handoff Markdown template. It shows the agent-facing ledger shape, not the human presentation. The task-specific HTML renderer reads data.json separately.
51
- 11. A one-line MCP pointer instead of the verbatim block (redundant — the brief is already in the report-writer's Required reading, item 10): `**MCP servers:** follow the task brief's "## Available MCP Servers" section (already in your Required reading).`
51
+ 11. The analysis packet path plus a one-line MCP pointer instead of copying the server block verbatim: `**MCP servers:** follow the analysis packet's "Available MCP Servers" section. If the section is absent or says none, treat MCP as unavailable for this run; never infer tools from host configuration.`
52
52
  12. `Convergence state: runs/<task-type>/state/convergence-<task-type>-<seq>.json`, followed by pointers to all analysis-worker result files under `worker-results/`. The convergence path is deterministic and is listed even before Phase 5.5 creates the file. Read its classifications (Full/Partial/Contested/Worker-Unique), `roundHistory[]`, `round2SkippedReason`, and `finalClassificationCounts`; populate `crossVerification.roundHistory` in data.json so Section 6 can show which rounds executed, queue sizes, and why Round 2 was (or was not) skipped. The renderer prints the full per-round table only when more than one round ran; single-round or zero-round histories are auto-collapsed to a one-line summary.
53
53
  13. `**Report Language:** <en|ko>` — must be either `en` or `ko`; `auto`
54
54
  has been resolved by the lead from project.json / global config
55
55
  before the dispatch is constructed. The worker copies this verbatim
56
56
  into `data.json.meta.reportLanguage`.
57
- 14. For implementation-planning runs: a literal block listing the 12 required English section headings `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. This list is `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`; that tuple is the SSOT and this block must match it exactly. The writer uses these exact substrings as section headings (Korean translation in parentheses is allowed), and the `Plan Body Verification` section carries its required `Gate result:` line.
58
- 15. An explicit instruction: `You are the author of THREE files: (a) the final-report data.json at <Result Path>, (b) its rendered Markdown sibling produced through "okstra render-final-report <Result Path>", and (c) the worker-result pointer at <Worker Result Path>. Maintain the separate heartbeat audit sidecar at <Audit sidecar path>. Do not return the report inline. The dispatch fails when any of the three completion artifacts is missing, and session conformance fails when the audit sidecar is missing or invalid.`
59
- 16. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT — at most 3 sentences. `rationale.*` fields stay within 2 sentences each; `humanSummary` entries stay concise; task `userNarrative` explains each user-facing section once with evidence references. Do not copy these narratives into the AI Markdown. `summary` stays at 3-5 rows unless the run covers multiple tickets. Generation time scales with output volume, so exceeding the budget is a cost bug, not extra diligence.
57
+ 14. An explicit instruction: `You are the author of THREE files: (a) the final-report data.json at <Result Path>, (b) its rendered Markdown sibling produced through "okstra render-final-report <Result Path>", and (c) the worker-result pointer at <Worker Result Path>. Maintain the separate heartbeat audit sidecar at <Audit sidecar path>. Do not return the report inline. The dispatch fails when any of the three completion artifacts is missing, and session conformance fails when the audit sidecar is missing or invalid.`
58
+ 15. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT at most 3 sentences. `rationale.*` fields stay within 2 sentences each; `humanSummary` entries stay concise; task `userNarrative` explains each user-facing section once with evidence references. Do not copy these narratives into the AI Markdown. `summary` stays at 3-5 rows unless the run covers multiple tickets. Generation time scales with output volume, so exceeding the budget is a cost bug, not extra diligence.
60
59
 
61
60
  **Fix-run incremental authoring (applies when the run's profile carries a "Fix-Run Carry" block).** Do not author the data.json from scratch. Start by copying the previous run's data.json (the `Previous report` path in the Fix-Run Carry block) to this run's Result Path, then update ONLY the blocks the fix run changed: `meta`/`header` (run seq, dates), `executionStatus`, `implementation.verifierResults`, `implementation.validationEvidence`, `implementation.commitList` / `diffSummary`, `crossVerification`, `verdictCard`, `finalVerdict`, and any `evidence` rows the fix touched. Deliverable prose for unchanged sections is carried forward verbatim — do not re-generate it. Then invoke the renderer exactly as in a full run. The schema validation and renderer contract are unchanged, so an incrementally-authored data.json passes the same post-hoc gates. The lead's dispatch prompt MUST include the previous data.json path when the carry block is present.
62
61
 
@@ -83,7 +82,17 @@ Phase 6 first produces the final-report data.json at `runs/<task-type>/reports/f
83
82
 
84
83
  For an implementation-planning run, the Report writer worker owns the Phase 6 design assessment snapshot: it writes `designPreparation` and every stage's `designSurfaceCoverage` into data.json from the detector output and consolidated plan. It does not create user inputs, consume a user answer as if it were part of that snapshot, or materialize `design-prep-requests/`; `schemas/final-report-v2.0.schema.json` and `validators/validate-run.py` `_validate_design_prep_contract` enforce the snapshot shape, detector coverage, and references.
85
84
 
86
- Phase 7 post-processing is **one command**. `okstra report-finalize` owns the ordered sequence it is the same code path the Codex lead adapter runs automatically, so a Claude-led run and a Codex-led run finalize identically:
85
+ ### Before `report-finalize`: the translation sidecar (BLOCKING order)
86
+
87
+ `report-finalize` step `render-views` overlays the translation sidecar, so a non-English run must produce that sidecar **before** the command runs. That leaves exactly one correct order, and it is not the intuitive one:
88
+
89
+ 1. **Verify the data.json is English first.** Run `okstra report-translate check-source <data.json>`. Do this even when **Report Language** is `en` — it is the cheapest gate in the phase and it protects every step after it.
90
+ 2. **Only when it passes and Report Language is not `en`**, dispatch the translator worker, which writes `final-report-<task-type>-<seq>.i18n.<lang>.json`.
91
+ 3. Then run `report-finalize`.
92
+
93
+ **Never dispatch the translator before step 1.** The data.json is the English SSOT; a report-writer that authored it in the reader's language produces a translation *from that language into itself* — a full-cost, entirely useless artifact, and the run still fails at `check-source` afterwards. **Enforced:** `okstra report-translate extract` refuses to build a work list from a data.json over the Korean-prose limit, so a mis-ordered dispatch fails at the translator's first command instead of after it. When it does fail, the fix is a report-writer rewrite in English — discard the sidecar and `translation-source.json` produced from the Korean draft rather than editing them, because their English column is not English.
94
+
95
+ Phase 7 post-processing is then **one command**. `okstra report-finalize` owns the ordered sequence — it is the same code path the Codex lead adapter runs automatically, so a Claude-led run and a Codex-led run finalize identically:
87
96
 
88
97
  ```bash
89
98
  okstra report-finalize \
@@ -92,24 +101,25 @@ okstra report-finalize \
92
101
  --report <runDirectoryPath>/reports/final-report-<task-type>-<seq>.md
93
102
  ```
94
103
 
95
- Do NOT run the four steps below by hand. Hand-running them is the recurring root cause of reports shipping with `--` token cells, a missing html sibling, Section 3 missing follow-up entries, or Section 4 rows never spawning — the order is load-bearing and a skipped step surfaces only later, as a validator `contract-violated`. Every step is idempotent, so after fixing a reported failure just re-run the same command.
104
+ Do NOT run the five steps below by hand. Hand-running them is the recurring root cause of reports shipping with `--` token cells, a missing html sibling, Section 3 missing follow-up entries, or Section 4 rows never spawning — the order is load-bearing and a skipped step surfaces only later, as a validator `contract-violated`. Every step is idempotent, so after fixing a reported failure just re-run the same command.
96
105
 
97
106
  The steps it executes, in this contractual order, and the contract each one carries:
98
107
 
99
- 1. **`token-usage` — collect usage.** Aggregates `leadUsage` / `workers[].usage` / `usageSummary` into team-state, populates `tokenUsage` and the execution-status usage fields in data.json, and re-invokes the renderer so the markdown carries real numbers.
108
+ 1. **`check-source` — verify the data.json is English.** The same gate as the pre-translator check above, run again here because everything after it derives from the data.json: rendering a Korean SSOT into English chrome, spawning follow-ups from it, and validating it all succeed on a record the next phase cannot read. A failure here means the report-writer authored in the reader's language; re-dispatch it with the English rule rather than editing the data.json by hand.
109
+ 2. **`token-usage` — collect usage.** Aggregates `leadUsage` / `workers[].usage` / `usageSummary` into team-state, populates `tokenUsage` and the execution-status usage fields in data.json, and re-invokes the renderer so the markdown carries real numbers.
100
110
 
101
111
  The data.json paths populated: `tokenUsage.lead.{totalTokens,billableTokens,costUsd}`, the `worker` / `grand` rows, `tokenUsage.cli.costUsd`, and each `executionStatus[].{totalTokens,billableTokens,costUsd,durationMs,cliTotalTokens,cliCostUsd}` for rows whose role matches a team-state worker. The data.json MUST already exist (Phase 6 output).
102
112
 
103
113
  For implementation-planning, this Phase 7 canonical render calls `materialize_design_prep_requests()` after token substitution and creates deterministic request files only for `provisional` / `blocked` items. Later answers are append-only user-input sidecars; request generation and user input never rewrite the assessment fields, so the source report remains immutable as the design-input snapshot after this render. `validators/validate-run.py` `_validate_design_prep_requests` enforces request existence, canonical path, content, and assessment fingerprint.
104
- 2. **`render-views` — render the human report artifact.** Runs against the substituted v2 data.json and its Markdown sibling.
114
+ 3. **`render-views` — render the human report artifact.** Runs against the substituted v2 data.json and its Markdown sibling.
105
115
 
106
116
  Output (idempotent — re-running overwrites):
107
117
  - `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view, always generated for schema v2 from the dedicated template registered for that task type. Clarification rows with `Status` ∈ {`open`, `answered`} embed response controls and export a `user-response-<task-type>-<seq>.md` sidecar. The original data and Markdown artifacts are never mutated by user input.
108
118
  - the implementation-planning report renders a **Plan Approval** section at the end of the body (implementation-option `<select>` + an approval checkbox) — disabled while any §1 `Blocks: approval` row is unresolved. Checking approval and exporting embeds a `## APPROVAL` block in the sidecar body, and the implementation-start wizard's approve-confirm step detects it and, after user confirmation, applies it through the existing `--approve` / `--implementation-option` path.
109
119
  - Schema-v1 and quick compatibility reports retain the legacy conditional HTML path; this does not change the schema-v2 always-generated contract.
110
120
 
111
- It runs after usage collection so token placeholders are substituted in any rendered html, and before routing persistence so the html artifact, when generated, exists for the validator step that checks it.
112
- 3. **`spawn-followups` — routing and follow-up persistence.** Turns the report's `## 4. Follow-up Tasks` rows into `tasks/<task-group>/<new-task-id>/` stubs.
121
+ It runs after usage collection so token placeholders are substituted in any rendered html, and before routing persistence so the html artifact, when generated, exists for the validator step that checks it. It also overlays the translation sidecar, which is why a non-English run must dispatch the translator before this command — see the ordering rule above.
122
+ 4. **`spawn-followups` — routing and follow-up persistence.** Turns the report's `## 4. Follow-up Tasks` rows into `tasks/<task-group>/<new-task-id>/` stubs.
113
123
 
114
124
  Behaviour contract:
115
125
  - Idempotent: rows whose target dir exists are reported as `existing` and skipped. Reruns of the same parent task are safe.
@@ -124,7 +134,7 @@ The steps it executes, in this contractual order, and the contract each one carr
124
134
  ```
125
135
 
126
136
  The status file is written after routing and follow-up persistence completes.
127
- 4. **`validate-run` — validate the finished run.** Checks the completed artifact set, including the report-views contract that catches a missing or stale html sibling. A failure here names the specific contract; fix it and re-run `okstra report-finalize`.
137
+ 5. **`validate-run` — validate the finished run.** Checks the completed artifact set, including the report-views contract that catches a missing or stale html sibling. A failure here names the specific contract; fix it and re-run `okstra report-finalize`.
128
138
 
129
139
  After `okstra report-finalize` reports `"ok": true`, **execute the run-scoped cleanup gate.** Call `shutdown_workers` only after that success, all persistence work, and explicit user approval under [okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped worker-resource lifecycle". If the user keeps resources, leave the selected adapter's resources intact and surface its manual cleanup guidance.
130
140
 
@@ -208,11 +218,13 @@ Token Summary Generation Rules:
208
218
  - If `lead` or any `worker.usage` records unavailable evidence, show `--` for that row and append a one-line note (`reason: <note>`).
209
219
  - If pricing for a model is unknown, the script omits `estimatedCostUsd` for that block — show `N/A` in that column and add a note like `pricing missing for model <model>`.
210
220
 
211
- ### Implementation-planning section heading contract (BLOCKING)
221
+ ### Implementation-planning section heading contract (schema v1 only)
222
+
223
+ **This does not apply to any run you will author.** New runs are schema v2 (`report_contract.CURRENT_REPORT_SCHEMA_VERSION`), and `validate_phase_boundary` returns before the substring scan when `schemaVersion == "2.0"` — the v2 deliverable is gated by the schema instead, whose `implementationPlanning` block requires every one of these contents as a named key. The v2 AI-handoff template carries nine headings and serialises the plan as JSON beneath them, so it cannot produce these strings and is not expected to.
212
224
 
213
- When the run's `task-type` is `implementation-planning`, the final report MUST contain section headings whose **lines include each of the 12 literal English substrings below**. The validator (`validators/validate-run.py`) does plain substring matching on the report text and validates the design-preparation data contract missing headings was a real, repeatedly observed failure mode caused by translating the headings to Korean.
225
+ Reading this section as a live instruction is a known and expensive mistake: the writer is sent to author headings the v2 template has no place for, and the run reads as structurally unpassable when nothing is wrong with it. It is retained only for rendering or diagnosing historical schema-v1 reports.
214
226
 
215
- The rows below mirror `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`, which is the SSOT. **Enforced:** `tests/contract/test_planning_required_sections_ssot.py` fails when this table and that tuple diverge a heading listed here but not required (or required but not listed) sends the writer to author a section the validator rejects, or to omit one it demands.
227
+ For those v1 reports, the final report must contain section headings whose **lines include each of the 12 literal English substrings below**. The rows mirror `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`, which is the SSOT. **Enforced:** `tests/contract/test_planning_required_sections_ssot.py` fails when this table and that tuple diverge, and pins the v1 scoping above so the section cannot silently become unconditional again.
216
228
 
217
229
  | # | Required substring | Recommended heading form |
218
230
  |---|--------------------|--------------------------|
@@ -79,7 +79,7 @@ The Phase 7 run validator enforces the same cross-task rule against the persiste
79
79
 
80
80
  When a worker reads any project-relative path from the prompt, it MUST resolve it against `Project Root` (e.g. `<Project Root>/<Result Path>`) — never use bare relative paths that depend on cwd.
81
81
 
82
- If the task brief contains an `## Available MCP Servers` section, inject only the one-line pointer into every analysis worker's prompt (and into the report-writer prompt when it is dispatched in Phase 6) the brief is already in every worker's [Required reading], so verbatim copy is redundant: `**MCP servers:** follow the task brief's "## Available MCP Servers" section (already in your Required reading).` Codex/Antigravity workers run external CLIs whose MCP availability is governed by their own CLI configs; they can record `MCP not available in this CLI` cleanly after reading that section in the brief.
82
+ Inject only the packet-scoped one-line pointer into every analysis worker's prompt and into the report-writer prompt when it is dispatched in Phase 6: `**MCP servers:** follow the analysis packet's "Available MCP Servers" section. If the section is absent or says none, treat MCP as unavailable for this run; never infer tools from host configuration.` Codex/Antigravity workers run external CLIs whose MCP availability is governed by their own CLI configs; they record `MCP not available in this CLI` when their CLI does not expose a server the packet names.
83
83
 
84
84
  Persist the exact worker prompt before dispatch per Operating Rule 6; never use `/tmp/*prompt*.txt` as the canonical artifact path.
85
85
 
@@ -91,6 +91,7 @@ The lead does not inline reading or error blocks. It resolves `PromptPlan.audien
91
91
 
92
92
  What the lead MUST still do per dispatch:
93
93
  - Inject the input file enumeration into the dispatch prompt body via an `## Inputs` section (or any heading the recipient agent expects), listing the actual project-relative primary inputs derived from the run's `instruction-set/`. For `final-verification` analysis workers, list only `analysis-packet.md` as the primary input; source files are reached on demand through that packet. Other phases may list source/fallback paths when useful. The preamble describes the rules; the lead provides the specific paths for THIS run.
94
+ - Inject `**Evidence ledger:** required-v1` into every initial non-report-writer prompt. The selected audience preamble owns the audit-row syntax; `validators/validate-run.py` `validate_worker_results_audit()` enforces that each backticked `path:line` result citation has a matching evidence-read row. Report-writer and reverify prompts do not carry this marker.
94
95
  - Inject `**Worker Error Contract Path:**` plus the absolute `**Errors log path:**` and `**Errors sidecar path:**` headers — workers cannot synthesize these paths.
95
96
  - Omit the preamble pointer for reverify dispatches (Phase 5.5 lightweight mode) — see [convergence](./convergence.md) "Reverify prompt: required-reading suppression".
96
97
 
@@ -17,6 +17,6 @@ Load the applicable coding conventions for every language the diff will touch, t
17
17
 
18
18
  - **Resource selection — read the routed pack, never inline it here.** Use this worker prompt's `**Coding preflight pack:**` anchor header as the absolute path to the installed routed pack. Detect each touched file's language and framework from its extension or project manifest (`package.json`, `Cargo.toml`, `pyproject.toml`, `pom.xml`, `build.gradle*`, `prisma/schema.prisma`), then read that pack's resources via the Read tool by absolute path. Always read `overview.md` (the router) + `clean-code.md`, then select per the router's three ordered stages — Stage 1 language → `languages/<lang>.md`, Stage 2 framework → `frameworks/<fw>.md` (e.g. `frameworks/node-server.md` for server-side Node), Stage 3 architecture → `architectures/<arch>.md` (e.g. `architectures/hexagonal.md` for ports-and-adapters / NestJS-hex). Each stage is a list of rules; include EVERY matching resource (a change set can touch multiple languages/frameworks/architectures) — do not stop at the first match. These files are runtime resources, not Skill-tool skills, so always read them by path.
19
19
  - **Declared architecture style — an authoritative Stage 3 input, and it binds.** Before selecting resources, read `<PROJECT_ROOT>/.okstra/project.json` and take `architecture.style`. A declared `hexagonal` selects `architectures/hexagonal.md` even when none of Stage 3's layout signals matched, so the declaration — not the directory shape — decides. A declared `layered` has no pack resource; its invariant applies from this line: dependencies run one direction only — an upper layer may import a lower one, never the reverse — and a variation point is extracted onto a layer boundary. A declared style makes this overlay binding rather than advisory, and which rule binds follows the style: under `hexagonal` the overlay's otherwise-advisory concrete-adapter item is blocking, so a service dependency you add or modify goes through a port instead of a concrete implementation and that placement violation is fixed before the write rather than recorded as a note; under `layered` what binds is the direction invariant just stated — your own judgement over the import list of every file the diff touches, plus extracting a variation point onto a layer boundary — while the concrete-adapter item stays advisory, since `layered` has no ports to route it through. An absent field, a `none` style, or an unreadable `project.json` changes nothing — Stage 3 stays detection-driven and its overlay stays advisory, leaving the language-agnostic principles below as the only always-binding layer. The verifier re-grades the same diff under the same declaration (`_implementation-verifier.md` → Static design & test-quality review), so a placement violation missed here returns as a verdict `FAIL`.
20
- - **Project review rule packs:** also look for project-local review skills in `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`. Read the relevant `SKILL.md` plus referenced `references/*.md` files and apply their rules during implementation. This is a prevention pass, not a PR-comment generation workflow: do not dispatch reviewer subagents from the executor. For Fonts Ninja-style PR review packs, the executor must avoid newly introduced duplicate helper stacks, tautological tests that merely re-call the delegated helper, self-mocking, domain rules in adapters/ports, domain objects outside `domain/`, dead APIs, weak public names, and functions that fail the plain-English read.
20
+ - **Project review rule packs:** apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path. Read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Apply those rules during implementation as a prevention pass, not a PR-comment generation workflow: do not dispatch reviewer subagents from the executor. For Fonts Ninja-style PR review packs, the executor must avoid newly introduced duplicate helper stacks, tautological tests that merely re-call the delegated helper, self-mocking, domain rules in adapters/ports, domain objects outside `domain/`, dead APIs, weak public names, and functions that fail the plain-English read.
21
21
  - **Language-agnostic principles that ALWAYS bind (the TDD loop MUST satisfy them):** (1) no self-mocking of the SUT — stub/spy only injected collaborators, never the subject's own methods; (2) behavioral assertions on outcomes (return value, state, persisted rows, events, boundary calls) — never `toHaveBeenCalled*` on an internal helper as the only/primary assertion; (3) truthful names — a `get*` / `find*` that writes/inserts, or a name encoding the caller's use-case (`*ForInit`) or hiding a domain rule (`findValid*`), is a defect; (4) single-purpose functions ≤50 effective lines, plain-English readability. Self-mocking (1) — Enforced by `validators/detect_self_mock.py` (static); absent `qa/self-mock-*.json` sidecar BLOCKS at `validate-run.py`.
22
22
  - **Graceful degradation (codex / antigravity executor runtimes, or any runtime where the resolved coding-preflight pack files are absent or unreadable):** do NOT skip the gate — apply the agnostic principles above plus the project's own `CLAUDE.md` / `CONTRIBUTING` / formatter+lint config, and record `coding-conventions: resource-unavailable → applied <project rules + agnostic principles>` in the final report. Never claim a resource read that did not happen.
@@ -156,7 +156,7 @@ Re-running commands proves the diff *builds and passes*; it does NOT prove the d
156
156
 
157
157
  - **Scope (no silent sampling).** Enumerate every changed source/test file via `git diff --name-only <base>...HEAD` and review each one. Skipping a changed file silently is a `contract-violated` outcome. If a file's language has no reference and is not covered by the agnostic checks below, record `design-review skipped: <file> (language=<x> no reference)` — never pass it silently.
158
158
  - **Load the same conventions the executor used via the routed pack.** Use this worker prompt's `**Coding preflight pack:**` anchor header as the absolute path to the installed routed pack. Read `overview.md` first, then `clean-code.md`, then apply the router's three ordered stages: language, framework, architecture. In each stage, iterate every rule, treat a rule as matched when any listed condition is true, and accumulate every matching resource — including `frameworks/node-server.md` for server-side Node work and `architectures/hexagonal.md` for ports-and-adapters / NestJS-hex layouts. Degrade to the agnostic checks below when the resolved pack is unreadable, and record either `coding-conventions: resources=<...>` or `coding-conventions: resource-unavailable → applied <project rules + agnostic principles>`. The verifier does NOT inline language rules — it loads the same situation-specific resources as the executor preflight.
159
- - **Load project review rule packs when present.** Search the project root, `.claude/skills`, and up to two parent `skills/` directories for `*review*/SKILL.md` rule packs. Read their referenced `references/*.md` files and apply them as an overlay on this static review. If a premium review skill exists, use its coverage philosophy (recall-first enumeration followed by verify-only confirmation) as the verifier's mental model, but do NOT dispatch extra reviewer agents unless the task explicitly configured them. Record `project-review-rules: <paths read>` or `project-review-rules: none found` in the worker result.
159
+ - **Load brief-cited project review rule packs.** Apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path. Read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Apply the cited rules as an overlay on this static review, but do NOT dispatch extra reviewer agents unless the task explicitly configured them. Record `project-review-rules: <paths read>` or `project-review-rules: none cited` in the worker result.
160
160
  - **Declared architecture style promotes the placement overlay from advisory to binding.** Read `<PROJECT_ROOT>/.okstra/project.json` — the same file Tier 2's `qaCommands` comes from — take `architecture.style`, and record `architecture-style: <hexagonal|layered|none>` in the worker result next to the `coding-conventions:` line. A declared `hexagonal` counts the overlay as loaded even when none of the router's Stage 3 layout signals matched, so the **Hexagonal** blocking check below applies in full, and the concrete-adapter injection listed under Advisory findings is promoted to a blocking finding → verdict `FAIL`, not a `should-fix`. A declared `layered` has no pack resource; its binding invariant is direction — an upper layer may import a lower one, never the reverse — so a changed file whose import list reaches back up a layer, or around a layer boundary, is a blocking placement violation cited `path:line` from that import list. The `layered` half is worker judgement: no machine check reads layer names, so a missed reverse dependency is a missed finding, not a validator failure. A `none` style, an absent field, or an unreadable `project.json` leaves this section exactly as it is today — Stage 3 stays detection-driven and the placement items stay advisory. **Enforced:** `scripts/okstra_project/resolver.py` `resolve_architecture` reads this same field for the planning-side rule in `validators/validate-run.py` `_validate_variation_point_analysis`, and `_validate_verifier_fail_blocks_verdict` (cited under the DB gate below) keeps the resulting `FAIL` from being dropped during synthesis.
161
161
  - **Blocking checks (any hit → verdict `FAIL`, cited `path:line` + rule name, recommended fix recorded — the verifier does NOT apply it):**
162
162
  - **New duplication / DRY:** two or more newly added or meaningfully modified blocks implement the same helper stack, transform, or domain rule. Literal copy-paste is always blocking; semantically equivalent transforms across services are blocking unless the approved plan explicitly justified keeping them separate. Recommend the shared module location.
@@ -20,7 +20,7 @@
20
20
  - **External Tier 3 de-duplication exception.** A DB/IO/SQL surface covered by an in-scope Tier 3 entry whose `requires` include `db`, `http`, or `external` is governed by the External QA outcome policy. Its non-PASS or unavailable result MUST NOT generate a second legacy db-test-not-configured or mock-only blocker solely for that same Tier 3 non-PASS or unavailable result. Tier 1 or Tier 2 failures remain blocking, and DB surfaces without declared external Tier 3 coverage remain blocking.
21
21
  - no new defects introduced — the diff does not break previously-working behaviour and adds no new bug (logic/off-by-one, null/empty handling, resource leaks, broken error paths)
22
22
  - scope conformance — the delivered diff stays within the approved plan's scope; flag out-of-scope edits, unrelated file changes, leftover debug/commented-out code, and unintended deletions
23
- - project review-rule packs (when present) — search `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`; read the matching `SKILL.md` + referenced `references/*.md` and apply their rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none found`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
23
+ - project review-rule packs (when brief-cited) — apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path; read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Apply the cited rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none cited`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
24
24
  - Residual-tracked — note as Residual Risk unless severe enough to block:
25
25
  - unresolved edge cases
26
26
  - regression risk in adjacent code paths not directly changed
@@ -42,7 +42,7 @@
42
42
  - **Follow established patterns**: in existing codebases, conform to current conventions. Targeted cleanup of a file you are already modifying is acceptable; unrelated refactors are not.
43
43
  - **Variation-point extraction (OCP)**: when the same behavior is served by two or more resources / implementations — stated in the brief, or foreseeable from a sibling task or the code you inspected — the plan MUST record it in `variationPointAnalysis` and include an option that extracts the variation point behind an interface (a port, or a strategy the next implementation plugs into), scored against the non-extracted option in the trade-off matrix. Penalize an option that branches on resource identity inside a service (one `if` / `switch` arm per implementation): adding the next implementation then means editing that same call site again, which is the closed-for-extension shape this principle exists to catch. This does not contradict YAGNI below: YAGNI drops *speculative* variation (a second implementation nobody named), while a behavior with two implementations already on the table is a present fact, not a forecast. **Enforced:** the `variationPointAnalysis` bullet under `Required deliverable shape` names the schema / validator / `P-Var-*` enforcement points.
44
44
  - **YAGNI ruthlessly**: drop features, abstractions, and configuration knobs that do not serve the stated requirement.
45
- - **Project review-rule preflight**: before choosing the recommended option, look for project-local review rule packs such as `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`. If present, read the relevant `SKILL.md` plus referenced `references/*.md` files and treat their rules as planning constraints. Do not run the PR-review workflow here; extract only the rules. For Fonts Ninja-style TS/NestJS review packs, this means planning away known review findings before code exists: shared transforms instead of duplicate helper stacks, behavioral tests instead of collaborator-tautology assertions, domain rules in domain modules rather than repositories/adapters, domain objects under `domain/`, plain-English functions, truthful/specific names, and no dead APIs introduced by the plan.
45
+ - **Project review-rule preflight**: apply a project review rule pack only when the task brief's `Source Material` or `Reporter Confirmations` cites its exact `SKILL.md` path. Read only that cited file and the `references/*.md` files it directly names. Do not search parent directories or host skill catalogs. Do not run the PR-review workflow here; extract only the rules. For Fonts Ninja-style TS/NestJS review packs, this means planning away known review findings before code exists: shared transforms instead of duplicate helper stacks, behavioral tests instead of collaborator-tautology assertions, domain rules in domain modules rather than repositories/adapters, domain objects under `domain/`, plain-English functions, truthful/specific names, and no dead APIs introduced by the plan.
46
46
  - Expected output emphasis:
47
47
  - feasible plan options
48
48
  - dependency and risk visibility
@@ -80,11 +80,12 @@
80
80
  - admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
81
81
  - NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything inside an option the plan does not recommend, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
82
82
  - A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
83
- - Section heading contract (BLOCKING — validator scans for these literal English substrings):
84
- - The final report MUST include section headings containing each of the following exact strings — this list mirrors `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`, which is the SSOT: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. (Approval is no longer a body section it is the YAML frontmatter `approved` field.) Three further headings are enforced elsewhere, not by that scan: `Stage Exit Contract` and `Stage Validation` are per-stage subsections checked by `validators/validate-implementation-plan-stages.py`, and `Implementation Design Preparation` is enforced and rendered by `schemas/final-report-v1.0.schema.json` plus `templates/reports/final-report.template.md` from its required data block.
85
- - Korean translations are allowed in parentheses (e.g. `### Recommended Option (Korean gloss)`), but the English keyword must be present verbatim in the heading line.
86
- - The shape and ordering follow `final-report-template.md` sections 5.4 (`Implementation Plan Deliverables`) + 5.5 (`Stage Map`). `validators/validate-run.py` substring-matches the raw report text, so a Korean-only heading fails the gate — the cause of repeated observed failures.
87
- - Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary not deferred to the `implementation` entry gate. It enforces: the exact `## 5.5 Stage Map` heading, each `## 5.5.<i> Stage <i>:` section with its four required subsections, the per-stage effective step count (≤8), the `depends-on` DAG, and the per-stage vertical-slice contract (S10). S10 scans for the literal in-section strings `Slice value:`, `Acceptance:`, the three `Test case (success):` / `Test case (boundary):` / `Test case (failure):` lines (S10d), and the Stepwise `action`-cell prefixes `RED:` / `GREEN:` (or a `TDD exemption:` line, which waives both the test-case lines and the RED/GREEN check) — keep these tokens verbatim for the same reason as the heading keywords above.
83
+ - Deliverable completeness contract (BLOCKING — the schema checks data keys, not heading strings):
84
+ - The plan lives in `data.json` under `implementationPlanning`, and `schemas/final-report-v2.0.schema.json` requires every one of these keys: `optionCandidates`, `tradeoffMatrix`, `recommendedOption`, `stageMap`, `stages`, `dependencyMigrationRisk`, `validationChecklist`, `rollbackStrategy`, `requirementCoverage`, `planBodyVerification`, `crossProjectDependencies`, `decisionDrafts`, `skippedAdrCandidates`, `variationPointAnalysis`, `userNarrative`. A missing block fails schema validation; there is nothing to satisfy by naming a heading. (Approval is not a body section it is the YAML frontmatter `approved` field.)
85
+ - Each `stages[]` entry requires `stage`, `title`, `sliceValue`, `acceptance`, `carryIn`, `stepwiseExecution` (1–6 rows), `exitContract`, and `stageValidation`. Each `stageMap[]` row requires `stage`, `title`, `dependsOn`, `stepCount`, `exitContractSummary`.
86
+ - Beyond the schema, `validators/validate-run.py` reads the same data.json for `_validate_planning_conformance_declared`, `_validate_end_state_coverage`, `_validate_requirement_provenance`, `_validate_stage_has_requirement`, and `_validate_plan_body_state_file`. These run for every planning report regardless of schema version.
87
+ - **Do not chase English heading substrings.** `PLANNING_REQUIRED_SECTIONS` and the structural Stage Map scan (`validators/validate-implementation-plan-stages.py`) live inside `validate_phase_boundary`, which returns immediately when `schemaVersion == "2.0"` they gate historical v1 Markdown only. The v2 AI-handoff template renders nine headings and serialises the plan as JSON beneath them, so those substrings cannot appear, and a report is not defective for lacking them.
88
+ - *Guideline, not gated:* write each `stepwiseExecution` row's `action` with a `RED:` / `GREEN:` prefix (or state a TDD exemption in `stageValidation`), and name the success / boundary / failure cases in `acceptance`. The v1 scan checked those tokens literally; v2 requires only that the fields be non-empty, so this is now review-enforced rather than validator-enforced.
88
89
  - Required deliverable shape (final report, in addition to the standard sections):
89
90
  - at least two implementation options. **Each option must include**:
90
91
  - **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
@@ -188,7 +189,7 @@
188
189
  3. **Internal consistency** — option file lists, trade-off matrix, and recommended step list must agree on file paths, names, and signatures. A symbol called `clearLayers()` in the matrix and `clearFullLayers()` in the steps is a bug.
189
190
  4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
190
191
  5. **Scope check** — if the recommended plan now spans multiple independent subsystems, recommend splitting into separate planning runs rather than shipping an oversized plan. Then walk the plan in the expansion direction: for every stage, name the Requirement Coverage row that demanded it, and for every requirement row, read its `Source` cell as a skeptic — does the cited brief heading actually exist, and does a `derived:` rationale state a real technical consequence rather than a preference? Move anything that fails to a `Blocks=approval` clarification row.
191
- 6. **Review-rule preflight check** — if a project review rule pack exists, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
192
+ 6. **Review-rule preflight check** — if the task brief cites a project review rule pack under `Source Material` or `Reporter Confirmations`, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
192
193
  7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** For every §5.5.9 `planItems[]` entry whose verdicts make it `majority-disagree`, set that item's `clarificationId` to a `C-<N>` row that MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. **Enforced:** `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item has no `clarificationId`, or its `clarificationId` is dangling / points at a non-`approval` row. For `partial-consensus` and `dissent-isolated` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
193
194
  8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 8. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` — this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap). **Project-boundary:** confirm no stage mixes edits from two projects (different repo/`PROJECT_ROOT` or different top-level deployable module); if any stage does, split it per project. For multi-project plans, confirm each stage's `title` carries its `[<project>]` tag and the `Cross-project parallelism:` line under the table records the parallel-vs-sequenced determination (with the forcing dependency) for every project pair; for cross-repo work, confirm it is split into separate per-repo runs (required — one run structurally cannot touch another repo) rather than crammed into one task's stages.
194
195
  9. **Cross-project dependency check** — confirm you have not missed a dependency on another repo / another top-level deployable module / a published package. If `dependencyMigrationRisk` has a `kind: cross-project` row, confirm a matching `direction: upstream-precondition` `XP-NNN` row exists in `crossProjectDependencies`, and re-read as a reviewer whether its `requiredWork` is the concrete work the other side must actually build rather than an abstract phrase ("other side's work done") — validator S only checks existence, so concreteness is the self-review's responsibility. Confirm cross-repo work is split into a separate run + XP row instead of being crammed into one task's stages, and that the cross-project substance is not duplicated in `§3 Recommended Next Steps` but lives only in `§5.4 Cross-Project Dependencies`.
@@ -53,6 +53,7 @@ WORKER_PROFILE_SECTIONS_BY_TASK_TYPE = {
53
53
  ),
54
54
  "implementation-planning": (
55
55
  "Worker planning procedure",
56
+ "Pre-planning context exploration",
56
57
  "Design principles applied when scoring options",
57
58
  ),
58
59
  "final-verification": (
@@ -275,7 +275,11 @@ def validate_initial_prompts(
275
275
  PromptRecord(job.worker_id, job.dispatch_kind, job.prompt_path)
276
276
  for job in jobs
277
277
  ]
278
- errors = validate_initial_prompt_records(manifest=manifest, records=records)
278
+ errors = validate_initial_prompt_records(
279
+ manifest=manifest,
280
+ records=records,
281
+ require_evidence_ledger=True,
282
+ )
279
283
  if errors:
280
284
  task_type = require_string(manifest, "taskType")
281
285
  raise DispatchError(f"{task_type} prompt contract: " + "; ".join(errors))
@@ -521,6 +521,7 @@ def _validate_prepublication_set(
521
521
  errors = validate_initial_prompt_records(
522
522
  manifest=context.manifest,
523
523
  records=[record],
524
+ require_evidence_ledger=True,
524
525
  )
525
526
  if errors:
526
527
  raise InitialPromptMaterializationError(
@@ -530,6 +531,7 @@ def _validate_prepublication_set(
530
531
  errors = validate_initial_prompt_records(
531
532
  manifest=context.manifest,
532
533
  records=records,
534
+ require_evidence_ledger=True,
533
535
  )
534
536
  if errors:
535
537
  raise InitialPromptMaterializationError(
@@ -577,6 +579,7 @@ def _validate_existing_prompt(
577
579
  basic_errors = validate_initial_prompt_records(
578
580
  manifest=context.manifest,
579
581
  records=[basic_record],
582
+ require_evidence_ledger=True,
580
583
  )
581
584
  if basic_errors:
582
585
  raise InitialPromptMaterializationError(
@@ -593,6 +596,7 @@ def _validate_existing_prompt(
593
596
  expected_errors = validate_initial_prompt_records(
594
597
  manifest=context.manifest,
595
598
  records=[expected_record],
599
+ require_evidence_ledger=True,
596
600
  )
597
601
  if expected_errors:
598
602
  reason = _existing_metadata_reason(expected_errors)
@@ -765,6 +769,7 @@ def _validate_published_set(
765
769
  errors = validate_initial_prompt_records(
766
770
  manifest=context.manifest,
767
771
  records=[record],
772
+ require_evidence_ledger=True,
768
773
  )
769
774
  if errors:
770
775
  raise InitialPromptMaterializationError(
@@ -774,6 +779,7 @@ def _validate_published_set(
774
779
  errors = validate_initial_prompt_records(
775
780
  manifest=context.manifest,
776
781
  records=records,
782
+ require_evidence_ledger=True,
777
783
  )
778
784
  if errors:
779
785
  raise InitialPromptMaterializationError(
@@ -983,6 +989,7 @@ def _validate_existing_record_subset(
983
989
  errors = validate_initial_prompt_records(
984
990
  manifest=manifest,
985
991
  records=records,
992
+ require_evidence_ledger=True,
986
993
  )
987
994
  if errors:
988
995
  raise InitialPromptMaterializationError(
@@ -1,10 +1,15 @@
1
1
  """Phase 7 report post-processing — the single reference point.
2
2
 
3
3
  Phase 7 turns a Phase 6 final-report data.json into shippable artifacts through
4
- four ordered steps: usage substitution, html view rendering, follow-up task
5
- spawning, and run validation. The order is load-bearing — rendering before
6
- substitution ships `--` token cells, and validating before rendering trips the
7
- report-views contract.
4
+ five ordered steps: English-SSOT verification, usage substitution, html view
5
+ rendering, follow-up task spawning, and run validation. The order is
6
+ load-bearing — rendering before substitution ships `--` token cells, and
7
+ validating before rendering trips the report-views contract.
8
+
9
+ The translation sidecar is NOT one of these steps. `render-views` overlays it,
10
+ so a non-English run dispatches the translator before this sequence starts —
11
+ after verifying the data.json is English, which is why `check-source` is also
12
+ available as a standalone command.
8
13
 
9
14
  Every lead adapter drives Phase 7 through this module: the Codex adapter calls
10
15
  it in-process (``codex_dispatch``), and a Claude-led run reaches the same code
@@ -128,6 +128,7 @@ def report_writer_input_lines(
128
128
  team_state: Mapping[str, Any],
129
129
  ) -> list[str]:
130
130
  inputs = [
131
+ ("Analysis packet", instruction_path(manifest, active_context, "analysisPacketPath")),
131
132
  ("Task brief", instruction_path(manifest, active_context, "taskBriefPath")),
132
133
  ("Analysis profile", instruction_path(manifest, active_context, "analysisProfilePath")),
133
134
  ("Analysis material", instruction_path(manifest, active_context, "analysisMaterialPath")),
@@ -193,8 +194,9 @@ def run_path(
193
194
 
194
195
  def mcp_pointer_line() -> str:
195
196
  return (
196
- '**MCP servers:** follow the task brief\'s "## Available MCP Servers" '
197
- "section (already in your Required reading)."
197
+ '**MCP servers:** follow the analysis packet\'s "Available MCP Servers" '
198
+ "section. If the section is absent or says none, treat MCP as unavailable "
199
+ "for this run; never infer tools from host configuration."
198
200
  )
199
201
 
200
202
 
@@ -11,6 +11,7 @@ from .worker_prompt_policy import (
11
11
  PromptPlan,
12
12
  resolve_prompt_plan_for_manifest,
13
13
  )
14
+ from .worker_prompt_headers import EVIDENCE_LEDGER_HEADER
14
15
 
15
16
 
16
17
  MAX_FINAL_VERIFICATION_DIRECTIVE_LINES = 40
@@ -24,6 +25,7 @@ FORBIDDEN_ACTIONS_HEADER = "**Forbidden actions:**"
24
25
 
25
26
  _DIRECTIVE_HEADING = "## Run-specific directive"
26
27
  _WORKER_ERROR_CONTRACT_HEADER = "**Worker Error Contract Path:**"
28
+ _EVIDENCE_LEDGER_HEADER_PREFIX = "**Evidence ledger:**"
27
29
  _PRIMARY_PACKET_RE = re.compile(
28
30
  r"(?im)^-\s+Primary analysis packet:\s+`[^`\n]*analysis-packet\.md`\s*$"
29
31
  )
@@ -45,6 +47,7 @@ _NON_BODY_PREFIXES = (
45
47
  "**Audit sidecar path:**",
46
48
  "Assigned worker prompt history path:",
47
49
  "**Worker Preamble Path:**",
50
+ _EVIDENCE_LEDGER_HEADER_PREFIX,
48
51
  *ERRORS_PATH_HEADERS,
49
52
  "**Read scope:**",
50
53
  "**File write mode:**",
@@ -239,8 +242,13 @@ def validate_initial_prompt_records(
239
242
  *,
240
243
  manifest: Mapping[str, Any],
241
244
  records: Sequence[PromptRecord],
245
+ require_evidence_ledger: bool = False,
242
246
  ) -> list[str]:
243
- """Validate prompt audiences and compare their normalized equality groups."""
247
+ """Validate prompt audiences and compare their normalized equality groups.
248
+
249
+ Newly published prompts opt into the evidence-ledger requirement. Persisted
250
+ historical prompts still validate under the contract they were written with.
251
+ """
244
252
  errors: list[str] = []
245
253
  equality_groups: dict[str, dict[str, str]] = {}
246
254
  for record in records:
@@ -262,6 +270,15 @@ def validate_initial_prompt_records(
262
270
  f"{record.worker_id}: {error}"
263
271
  for error in _validate_record_metadata(text, record)
264
272
  )
273
+ errors.extend(
274
+ f"{record.worker_id}: {error}"
275
+ for error in _validate_evidence_ledger_header(
276
+ text,
277
+ plan,
278
+ record.dispatch_kind,
279
+ required=require_evidence_ledger,
280
+ )
281
+ )
265
282
  if plan.equality_group:
266
283
  group = equality_groups.setdefault(plan.equality_group, {})
267
284
  group[record.worker_id] = text
@@ -289,6 +306,29 @@ def _validate_record_metadata(text: str, record: PromptRecord) -> list[str]:
289
306
  return errors
290
307
 
291
308
 
309
+ def _validate_evidence_ledger_header(
310
+ text: str,
311
+ plan: PromptPlan,
312
+ dispatch_kind: str,
313
+ *,
314
+ required: bool,
315
+ ) -> list[str]:
316
+ values = _header_values(text, _EVIDENCE_LEDGER_HEADER_PREFIX)
317
+ if dispatch_kind != "initial":
318
+ return []
319
+ if plan.audience == "report-writer":
320
+ if values:
321
+ return ["Evidence ledger header is forbidden for report-writer"]
322
+ return []
323
+ if not values:
324
+ if required:
325
+ return [f"exactly one `{EVIDENCE_LEDGER_HEADER}` header is required"]
326
+ return []
327
+ if values != ["required-v1"]:
328
+ return [f"exactly one `{EVIDENCE_LEDGER_HEADER}` header is required"]
329
+ return []
330
+
331
+
292
332
  def _validate_delivery_mode(
293
333
  values: list[str],
294
334
  expected: str | None,
@@ -36,6 +36,7 @@ READ_SCOPE_HEADER = (
36
36
  "un-enumerated file seems essential, record it under *Missing Information "
37
37
  "or Assumptions* instead of reading it."
38
38
  )
39
+ EVIDENCE_LEDGER_HEADER = "**Evidence ledger:** required-v1"
39
40
 
40
41
  # `agy`'s write tool validates the target against the Gemini artifact store
41
42
  # whenever the model attaches ArtifactMetadata, and rejects every path outside
@@ -101,6 +102,8 @@ def worker_prompt_headers(
101
102
  headers.append(
102
103
  f"**Coding preflight pack:** {_coding_preflight_pack_path(active_context)}"
103
104
  )
105
+ if dispatch_kind == "initial" and plan.audience != "report-writer":
106
+ headers.append(EVIDENCE_LEDGER_HEADER)
104
107
  headers.extend([
105
108
  f"**Errors log path:** {errors_log_path}",
106
109
  f"**Errors sidecar path:** {errors_sidecar_path}",
@@ -25,6 +25,14 @@ Work like a senior engineer who owns this result, not a commentator on it.
25
25
  - Only the executor may mutate source files. Verifiers remain read-only except for okstra result/audit artifacts and project-declared QA commands.
26
26
  - Execute the role sidecar's pre-write, post-write, QA, and return gates without substituting analysis Sections 1–6 or report-authoring instructions.
27
27
 
28
+ ## Evidence read ledger
29
+
30
+ When `**Evidence ledger:** required-v1` is present, append one canonical row to the audit sidecar immediately after opening every file used as claim evidence:
31
+
32
+ - Evidence read: `<project-relative path without a line suffix>`
33
+
34
+ Every file citation in the result MUST use backticks and a line suffix, for example `src/config/env.ts:1-22`. A cited path without a matching ledger row fails Phase 7 in `validators/validate-run.py` `validate_worker_results_audit()`. Do not add a row for a file you did not open.
35
+
28
36
  ## Anchor headers
29
37
 
30
38
  Every initial implementation prompt begins with these generated common anchors in this exact order, before its implementation-specific body:
@@ -36,9 +44,10 @@ Every initial implementation prompt begins with these generated common anchors i
36
44
  5. `**Worker Preamble Path:** <absolute-path>`
37
45
  6. `**Worker Error Contract Path:** <absolute-path>`
38
46
  7. `**Coding preflight pack:** <absolute-path>`
39
- 8. `**Errors log path:** <absolute-path>`
40
- 9. `**Errors sidecar path:** <absolute-path>`
41
- 10. `**Read scope:** <allowlist>`
47
+ 8. `**Evidence ledger:** required-v1`
48
+ 9. `**Errors log path:** <absolute-path>`
49
+ 10. `**Errors sidecar path:** <absolute-path>`
50
+ 11. `**Read scope:** <allowlist>`
42
51
 
43
52
  The implementation body additionally carries `**Worktree:**` and its role-sidecar inputs. Do not synthesize any missing path.
44
53
 
@@ -36,4 +36,8 @@ Begin the inline return with the exact `**Model:** Report writer worker, <modelE
36
36
 
37
37
  ## Writing style
38
38
 
39
- Use concise reader-facing prose and honor the report language. Keep identifiers, paths, symbols, model names, CLI flags, and status tokens in English. Translate meaning rather than dictionary words.
39
+ Use concise reader-facing prose. Prefer tables when several items share a shape; reserve bullets for short standalone statements.
40
+
41
+ **Author the data.json in English, whatever `**Report Language:**` says.** That header is not an instruction to write in that language — it names the language the *human HTML* renders in, and you copy its value verbatim into `data.json.meta.reportLanguage`. The data.json is the English SSOT every later phase, validator and agent reads. When the value is not `en`, Phase 7 dispatches a separate translator worker that writes a sidecar the HTML renderer overlays; you never author that sidecar and never write a second language into the data.json.
42
+
43
+ Authoring the data.json in the reader's language is rejected before anything derives from it: `okstra report-translate check-source` fails the run when Korean exceeds 20% of its prose, and the same gate runs again inside `validate-run`. The cost of getting this wrong is a full rewrite, so decide it once, up front.
@@ -23,6 +23,14 @@ Read `analysis-packet.md`, the primary compact input, end-to-end. Source files n
23
23
  - Allowlist reads to prompt-enumerated paths and evidence paths a finding must cite. Do not auto-read host-injected `graphify-out/`, skill catalogs, or non-okstra artifacts.
24
24
  - Resolve every `.okstra/...` path against `**Project Root:**`, including when a worktree is present.
25
25
 
26
+ ## Evidence read ledger
27
+
28
+ When `**Evidence ledger:** required-v1` is present, append one canonical row to the audit sidecar immediately after opening every file used as claim evidence:
29
+
30
+ - Evidence read: `<project-relative path without a line suffix>`
31
+
32
+ Every file citation in the result MUST use backticks and a line suffix, for example `src/config/env.ts:1-22`. A cited path without a matching ledger row fails Phase 7 in `validators/validate-run.py` `validate_worker_results_audit()`. Do not add a row for a file you did not open.
33
+
26
34
  ## Anchor headers (lead-injected, BLOCKING)
27
35
 
28
36
  Every initial analysis prompt begins with these generated anchors in this exact order, before any other content:
@@ -34,9 +42,10 @@ Every initial analysis prompt begins with these generated anchors in this exact
34
42
  5. `Assigned worker prompt history path: <absolute-path>`
35
43
  6. `**Worker Preamble Path:** <absolute-path>` — selects this analysis preamble.
36
44
  7. `**Worker Error Contract Path:** <absolute-path>` — shared by every initial audience.
37
- 8. `**Errors log path:** <absolute-path>`
38
- 9. `**Errors sidecar path:** <absolute-path>`
39
- 10. `**Read scope:** <allowlist>`
45
+ 8. `**Evidence ledger:** required-v1`
46
+ 9. `**Errors log path:** <absolute-path>`
47
+ 10. `**Errors sidecar path:** <absolute-path>`
48
+ 11. `**Read scope:** <allowlist>`
40
49
 
41
50
  `final-verification` additionally carries its six verification-target anchors. `improvement-discovery` carries `**Phase 1.5 Grilling Log:**`. Reverify prompts are lightweight and do not use this preamble.
42
51
 
@@ -103,6 +103,7 @@ from okstra_ctl.worker_prompt_contract import ( # noqa: E402
103
103
  PromptRecord,
104
104
  validate_initial_prompt_records,
105
105
  )
106
+ from okstra_ctl.worker_prompt_headers import EVIDENCE_LEDGER_HEADER # noqa: E402
106
107
  from validate_analysis_report import validate_analysis_report # noqa: E402
107
108
  from okstra_ctl.convergence_engine import ( # noqa: E402
108
109
  grouped_input_digest,
@@ -2378,6 +2379,16 @@ def validate_report(
2378
2379
  _WORKER_RESULT_BASENAME_RE = re.compile(
2379
2380
  r"^(?P<worker>[a-z][a-z0-9-]*-worker)-(?P<task_type>[a-z][a-z-]*?)-(?P<seq>\d{3})\.md$"
2380
2381
  )
2382
+ _EVIDENCE_READ_RE = re.compile(
2383
+ r"^- Evidence read: `(?P<path>[^`\n]+)`\s*$",
2384
+ re.MULTILINE,
2385
+ )
2386
+ _FILE_LINE_CITATION_RE = re.compile(
2387
+ r"`(?P<path>(?!https?://)[^`\n]+?):(?P<line>\d+(?:-\d+)?)`"
2388
+ )
2389
+ _EXTENSIONLESS_SOURCE_FILENAMES = frozenset(
2390
+ {"Dockerfile", "Justfile", "Makefile", "Procfile", "Rakefile"}
2391
+ )
2381
2392
 
2382
2393
  _REPORT_BASENAME_SEQ_RE = re.compile(r"-(?P<seq>\d{3})(?:\.data)?\.(?:md|json)$")
2383
2394
 
@@ -2390,6 +2401,90 @@ def _report_run_seq(report_path: Path) -> str | None:
2390
2401
  return match.group("seq") if match else None
2391
2402
 
2392
2403
 
2404
+ def _cited_file_paths(content: str) -> set[str]:
2405
+ paths: set[str] = set()
2406
+ for match in _FILE_LINE_CITATION_RE.finditer(content):
2407
+ path = match.group("path")
2408
+ if _looks_like_file_path(path):
2409
+ paths.add(path)
2410
+ return paths
2411
+
2412
+
2413
+ def _looks_like_file_path(path: str) -> bool:
2414
+ if (
2415
+ not path
2416
+ or path.startswith(("-", "$"))
2417
+ or any(char.isspace() for char in path)
2418
+ ):
2419
+ return False
2420
+ if re.fullmatch(r"[0-9a-fA-F]{7,64}", path):
2421
+ return False
2422
+ return (
2423
+ "/" in path
2424
+ or "." in Path(path).name
2425
+ or Path(path).name in _EXTENSIONLESS_SOURCE_FILENAMES
2426
+ )
2427
+
2428
+
2429
+ def _audit_evidence_read_paths(content: str) -> set[str]:
2430
+ return {
2431
+ match.group("path")
2432
+ for match in _EVIDENCE_READ_RE.finditer(content)
2433
+ }
2434
+
2435
+
2436
+ def _worker_prompt_path(
2437
+ report_path: Path,
2438
+ worker_role: str,
2439
+ task_type: str,
2440
+ seq: str,
2441
+ ) -> Path:
2442
+ return (
2443
+ report_path.parent.parent
2444
+ / "prompts"
2445
+ / f"{worker_role}-prompt-{task_type}-{seq}.md"
2446
+ )
2447
+
2448
+
2449
+ def _validate_worker_evidence_read_ledger(
2450
+ *,
2451
+ report_path: Path,
2452
+ worker_role: str,
2453
+ task_type: str,
2454
+ seq: str,
2455
+ result_name: str,
2456
+ result_content: str,
2457
+ audit_path: Path,
2458
+ failures: list[str],
2459
+ ) -> None:
2460
+ if worker_role == "report-writer-worker":
2461
+ return
2462
+ prompt_path = _worker_prompt_path(report_path, worker_role, task_type, seq)
2463
+ try:
2464
+ prompt_content = prompt_path.read_text(encoding="utf-8")
2465
+ except OSError:
2466
+ return
2467
+ if EVIDENCE_LEDGER_HEADER not in prompt_content.splitlines():
2468
+ return
2469
+ try:
2470
+ audit_content = audit_path.read_text(encoding="utf-8")
2471
+ except OSError as exc:
2472
+ failures.append(
2473
+ f"worker audit sidecar unreadable: {audit_path.name} ({exc})"
2474
+ )
2475
+ return
2476
+
2477
+ missing_paths = sorted(
2478
+ _cited_file_paths(result_content) - _audit_evidence_read_paths(audit_content)
2479
+ )
2480
+ for missing_path in missing_paths:
2481
+ failures.append(
2482
+ f"worker `{worker_role}` result `{result_name}` cites "
2483
+ f"`{missing_path}:line` without an Evidence read row for "
2484
+ f"`{missing_path}` in `{audit_path.name}`"
2485
+ )
2486
+
2487
+
2393
2488
  def validate_worker_results_audit(
2394
2489
  report_path: Path, task_type: str, failures: list[str]
2395
2490
  ) -> None:
@@ -2404,6 +2499,10 @@ def validate_worker_results_audit(
2404
2499
  2. The matching audit sidecar exists at
2405
2500
  `<worker>-audit-<task-type>-<seq>.md`. Missing sidecar means the
2406
2501
  worker silently skipped the reading-confirmation step.
2502
+ 3. For new prompts carrying the required-v1 marker, every canonical
2503
+ backticked `path:line` citation has a matching Evidence read row in
2504
+ that audit sidecar. Historical prompts without the marker retain the
2505
+ existence-only contract.
2407
2506
 
2408
2507
  Scoped to this run's seq. `worker-results/` accumulates every run's
2409
2508
  artifacts, so scanning the whole directory judged a run by files it did
@@ -2465,6 +2564,18 @@ def validate_worker_results_audit(
2465
2564
  f"Confirmation block (one short line per input file). Workers "
2466
2565
  f"write this in the same step as the main worker-results file."
2467
2566
  )
2567
+ continue
2568
+
2569
+ _validate_worker_evidence_read_ledger(
2570
+ report_path=report_path,
2571
+ worker_role=worker_role,
2572
+ task_type=task_type,
2573
+ seq=seq,
2574
+ result_name=rel,
2575
+ result_content=content,
2576
+ audit_path=audit_path,
2577
+ failures=failures,
2578
+ )
2468
2579
 
2469
2580
 
2470
2581
  def validate_team_state_usage(team_state: dict, failures: list[str]) -> None: