okstra 0.159.0 → 0.161.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/docs/architecture/storage-model.md +2 -0
  3. package/docs/architecture.md +2 -1
  4. package/docs/cli.md +8 -3
  5. package/docs/for-ai/README.md +2 -2
  6. package/docs/for-ai/skills/okstra-inspect.md +3 -0
  7. package/docs/for-ai/skills/okstra-run.md +2 -1
  8. package/docs/for-ai/skills/okstra-user-response.md +5 -5
  9. package/docs/project-structure-overview.md +5 -1
  10. package/docs/task-process/implementation.md +28 -0
  11. package/package.json +1 -1
  12. package/runtime/BUILD.json +2 -2
  13. package/runtime/bin/okstra-claude-exec.sh +4 -1
  14. package/runtime/prompts/host-orchestration/README.md +18 -0
  15. package/runtime/prompts/host-orchestration/implementation.md +57 -0
  16. package/runtime/prompts/launch.template.md +10 -1
  17. package/runtime/prompts/lead/adapters/claude-code.md +1 -1
  18. package/runtime/prompts/lead/adapters/cmux.md +67 -0
  19. package/runtime/prompts/lead/context-loader.md +5 -2
  20. package/runtime/prompts/lead/convergence.md +3 -1
  21. package/runtime/prompts/lead/plan-body-verification.md +21 -2
  22. package/runtime/prompts/lead/team-contract.md +2 -1
  23. package/runtime/prompts/profiles/_clarification-recommendation.md +11 -1
  24. package/runtime/prompts/profiles/_common-contract.md +3 -1
  25. package/runtime/prompts/profiles/implementation-planning.md +2 -0
  26. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  27. package/runtime/prompts/wizard/prompts.ko.json +3 -0
  28. package/runtime/python/okstra_ctl/clarification_items.py +9 -0
  29. package/runtime/python/okstra_ctl/cmux.py +531 -0
  30. package/runtime/python/okstra_ctl/codex_dispatch.py +6 -6
  31. package/runtime/python/okstra_ctl/convergence.py +168 -11
  32. package/runtime/python/okstra_ctl/dispatch_core.py +76 -7
  33. package/runtime/python/okstra_ctl/dispatch_state.py +16 -0
  34. package/runtime/python/okstra_ctl/error_issue.py +640 -0
  35. package/runtime/python/okstra_ctl/error_report.py +56 -0
  36. package/runtime/python/okstra_ctl/error_zip.py +23 -10
  37. package/runtime/python/okstra_ctl/incremental_scope.py +159 -19
  38. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +18 -5
  39. package/runtime/python/okstra_ctl/issue_signals.py +186 -0
  40. package/runtime/python/okstra_ctl/lead_runtime.py +30 -2
  41. package/runtime/python/okstra_ctl/paths.py +38 -0
  42. package/runtime/python/okstra_ctl/plan_items_cli.py +167 -3
  43. package/runtime/python/okstra_ctl/profile_show.py +134 -0
  44. package/runtime/python/okstra_ctl/recap.py +63 -0
  45. package/runtime/python/okstra_ctl/render.py +7 -2
  46. package/runtime/python/okstra_ctl/render_final_report.py +7 -22
  47. package/runtime/python/okstra_ctl/report_translation.py +4 -0
  48. package/runtime/python/okstra_ctl/report_views.py +7 -3
  49. package/runtime/python/okstra_ctl/run.py +54 -3
  50. package/runtime/python/okstra_ctl/run_audit.py +477 -0
  51. package/runtime/python/okstra_ctl/team.py +50 -11
  52. package/runtime/python/okstra_ctl/user_response.py +25 -10
  53. package/runtime/python/okstra_ctl/verdict_blocks.py +183 -0
  54. package/runtime/python/okstra_ctl/wizard.py +64 -10
  55. package/runtime/python/okstra_ctl/worker_audit_check.py +44 -0
  56. package/runtime/python/okstra_ctl/worker_audit_ledger.py +207 -0
  57. package/runtime/python/okstra_ctl/worker_heartbeat.py +9 -3
  58. package/runtime/python/okstra_ctl/worker_liveness.py +81 -9
  59. package/runtime/schemas/final-report-v1.0.schema.json +14 -0
  60. package/runtime/schemas/final-report-v2.0.schema.json +51 -1
  61. package/runtime/skills/okstra-inspect/SKILL.md +3 -1
  62. package/runtime/skills/okstra-inspect/facets/error-issue.md +77 -0
  63. package/runtime/skills/okstra-inspect/facets/run-audit.md +34 -0
  64. package/runtime/skills/okstra-run/SKILL.md +28 -10
  65. package/runtime/skills/okstra-user-response/SKILL.md +18 -18
  66. package/runtime/templates/reports/final-report.template.md +4 -0
  67. package/runtime/templates/reports/html/i18n/en.json +5 -1
  68. package/runtime/templates/reports/html/i18n/ko.json +5 -1
  69. package/runtime/templates/reports/html/macros/forms.html +15 -0
  70. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +1 -0
  71. package/runtime/templates/reports/i18n/en.json +2 -0
  72. package/runtime/validators/validate-run.py +267 -208
  73. package/runtime/validators/validate-workflow.sh +6 -0
  74. package/runtime/validators/validate_session_conformance.py +135 -31
  75. package/src/cli-registry.mjs +34 -0
  76. package/src/commands/execute/incremental-scope.mjs +10 -0
  77. package/src/commands/execute/worker-audit-check.mjs +35 -0
  78. package/src/commands/inspect/error-issue.mjs +27 -0
  79. package/src/commands/inspect/profile-show.mjs +29 -0
  80. package/src/commands/inspect/run-audit.mjs +26 -0
@@ -174,7 +174,9 @@ When `next.kind == "done"`, fetch the public wizard outcome:
174
174
  okstra wizard outcome --state-file /var/folders/.../okstra-wizard.AbCd.json
175
175
  ```
176
176
 
177
- Output: `{ok: true, outcome: {renderArgs: {...}, persistActions: [...], confirmationText: "..."}}`.
177
+ Output: `{ok: true, outcome: {renderArgs: {...}, orchestration: {chainStages: "..."}, persistActions: [...], confirmationText: "..."}}`.
178
+
179
+ `renderArgs` holds only flags `okstra render-bundle` accepts, which is what makes the pass-everything rule below safe. `orchestration` holds signals this skill acts on itself — never pass its entries to `render-bundle`.
178
180
 
179
181
  Run every `outcome.persistActions[]` entry BEFORE `render-bundle`. The only supported action is:
180
182
 
@@ -216,6 +218,19 @@ The python function underneath is mutex-protected (`~/.okstra/.locks/<task-key>.
216
218
 
217
219
  You can delete the literal state-file path after this point — its job is done. Invoke `rm` with the literal path (e.g. `rm /var/folders/.../okstra-wizard.AbCd.json`), not a shell variable.
218
220
 
221
+ <!-- BEGIN FRAGMENT: host-orchestration-implementation -->
222
+ ## Host orchestration rules — implementation
223
+
224
+ These are the rules the **host orchestrator** follows around an `implementation`
225
+ run: when to offer a conformance waiver, what a concurrent-run marker means, how
226
+ to recover a stale stage SHA, and what the chaining queue does when the next
227
+ stage is not ready. They are not lead phase rules — the lead's rules live in
228
+ `prompts/profiles/`.
229
+
230
+ This file is the single source. Two surfaces are generated from it: the
231
+ `okstra-run` skill body (marker block, synced by `tools/sync-skill-fragments.mjs`)
232
+ and each run's `instruction-set/host-orchestration-rules.md`. Edit here.
233
+
219
234
  ### Step 5.1 (implementation only): blocking local conformance waiver offer
220
235
 
221
236
  `render-bundle` accepts an optional `--qa-waiver "<stageKey>:<reason>"` flag (implementation only). It records a **user-acknowledged** waiver into the task-level conformance manifest entry (`entry.waiver`), letting the run proceed when an `io`-only Tier 3 conformance script genuinely cannot run. The waiver records the user's reason **verbatim**.
@@ -256,6 +271,13 @@ If `render-bundle` fails with a `PrepareError` containing `Recorded stage SHAs n
256
271
 
257
272
  If the anchor (`implementation_base_commit`) is reported unresolvable, run the same command's `--reset-anchor <ref>` after user confirmation. Correcting a confirm item without the picker is forbidden — the runtime also rejects a confirm correction without `--use-ref`.
258
273
 
274
+ ### Next stage not yet ready — normal termination (not an exception gate)
275
+ Because of the dependency closure, the chain queue **may include a stage that another implementation run has occupied as started/reserved.** That stage's `render-bundle` is rejected with `--stage N already in progress or reserved by another run` (StageTargetError). This is **not** an exception gate needing human judgment but a "next stage not yet ready" situation. On this rejection, **terminate the chain normally** and report the remaining queue to the user (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`). This is a different branch from the exception gate below (data corruption·concurrent-occupancy conflict confirmation).
276
+
277
+ ### Exception gate during chaining
278
+ If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent-run branch) or git stale-SHA reconciliation (git-reconcile branch), **stop the chain at that stage** and present the gate to the user exactly as Step 5 prescribes. Once the user resolves the gate, resume the chain in place (continue with the remaining queue). Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining. (Unlike the "not ready" rejection above, these two branches do not discard the queue; they wait for user resolution.)
279
+ <!-- END FRAGMENT: host-orchestration-implementation -->
280
+
259
281
  ## Step 6: Take over as Okstra lead
260
282
 
261
283
  Read `<INSTRUCTION_SET_PATH>/lead-execution-prompt.md` verbatim and take over as `Okstra lead` in the current host-native session. The prompt selects exactly one runtime adapter and points to compact intake artifacts first (`active-run-context`, `analysis-profile.md`, and `analysis-packet.md`); full source files such as `analysis-material.md`, `reference-expectations.md`, and `final-report-template.md` are lazy/fallback inputs. Follow the rendered prompt order, do not preempt it.
@@ -265,11 +287,11 @@ Then proceed through the phases exactly as the lead prompt directs (Phase 1 cont
265
287
  Inform the user with one short line:
266
288
  > Took over as Okstra lead (`<host-runtime>`) for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
267
289
 
268
- ## Step 7: implementation unattended chaining (chain-stages)
290
+ ## Step 7: implementation unattended chaining (orchestration.chainStages)
269
291
 
270
- When `task-type == implementation` and Step 5 render-args' `chain-stages` CSV has 2+ elements, the current session acts as the orchestrator and runs the stages in dependency order as an unattended chain (a single element behaves like the existing single run, so skip this section — the end of Step 6 is the end of the run).
292
+ When `task-type == implementation` and Step 5 outcome's `orchestration.chainStages` CSV has 2+ elements, the current session acts as the orchestrator and runs the stages in dependency order as an unattended chain (a single element behaves like the existing single run, so skip this section — the end of Step 6 is the end of the run).
271
293
 
272
- Queue = the topologically-sorted stage list from splitting `chain-stages` on `,` (the order Task 5 emitted by topologically sorting the dependency closure). For each stage `N` in the queue, in order:
294
+ Queue = the topologically-sorted stage list from splitting `orchestration.chainStages` on `,` (the order Task 5 emitted by topologically sorting the dependency closure). For each stage `N` in the queue, in order:
273
295
 
274
296
  1. Call Step 5's `render-bundle` with the same arguments but `--stage N` (the base commit is auto-computed by prepare from the predecessor's done `head_commit`, so do not pass it by hand). Step 5's blocking local conformance waiver offer·concurrent-run detection·git-reconcile gates apply identically to each stage's `render-bundle`.
275
297
  2. As in Step 6, become the host-native Okstra lead and run that stage's Phase 1–7 inline. Phase 6's lead post-stage persistence appends that stage's `status:"done"` row to `runs/<plan-task-key>/consumers.jsonl` (per the implementation profile directive).
@@ -278,11 +300,7 @@ Queue = the topologically-sorted stage list from splitting `chain-stages` on `,`
278
300
 
279
301
  Once the whole queue is consumed, end the chain and report completion to the user.
280
302
 
281
- ### Next stage not yet ready — normal termination (not an exception gate)
282
- Because of the dependency closure, the chain queue **may include a stage that another implementation run has occupied as started/reserved.** That stage's `render-bundle` is rejected with `--stage N already in progress or reserved by another run` (StageTargetError). This is **not** an exception gate needing human judgment but a "next stage not yet ready" situation. On this rejection, **terminate the chain normally** and report the remaining queue to the user (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`). This is a different branch from the exception gate below (data corruption·concurrent-occupancy conflict confirmation).
283
-
284
- ### Exception gate during chaining
285
- If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent-run branch) or git stale-SHA reconciliation (git-reconcile branch), **stop the chain at that stage** and present the gate to the user exactly as Step 5 prescribes. Once the user resolves the gate, resume the chain in place (continue with the remaining queue). Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining. (Unlike the "not ready" rejection above, these two branches do not discard the queue; they wait for user resolution.)
303
+ The two branches that end or pause the queue — "Next stage not yet ready" and "Exception gate during chaining" — are in the host orchestration rules block above.
286
304
 
287
305
  ## Persisting the PR template scope (release-handoff)
288
306
 
@@ -308,4 +326,4 @@ Do not read the wizard state file directly. `okstra wizard outcome` exposes any
308
326
 
309
327
  - Echo each captured answer (`result.echo`) on one short line so the user sees what was registered.
310
328
  - Never invent identity; if a `text` prompt returns an empty answer where the wizard rejects it, the user must retry.
311
- - After Step 6, begin the lead workflow without re-summarizing the skill itself. For a single run, the end of Step 6 is the end of the run — but in an unattended chain where `chain-stages` has 2+ elements, repeat Step 6 per stage until Step 7's queue is empty (or it stops at a "not ready" / exception gate), then finish.
329
+ - After Step 6, begin the lead workflow without re-summarizing the skill itself. For a single run, the end of Step 6 is the end of the run — but in an unattended chain where `orchestration.chainStages` has 2+ elements, repeat Step 6 per stage until Step 7's queue is empty (or it stops at a "not ready" / exception gate), then finish.
@@ -1,21 +1,21 @@
1
1
  ---
2
2
  name: okstra-user-response
3
3
  description: >-
4
- Use this to answer an okstra task's open clarification questions in-session, without hand-editing any file. The tell is a request to respond to an okstra run's clarification items or its approval gate — "answer okstra", "I'll answer the questions", "clarification response", "user response", "approve and move on". This skill lists tasks whose latest report still has open clarification blockers, then walks the open C-ids one at a time — each rewritten as a self-contained question with background, three concrete answers (recommendation first) plus "Enter directly" — echoes the collected answers back for an explicit "confirmed" acknowledgement, optionally records approval, and writes the `user-responses/` sidecar via `okstra user-response write`. NOT for starting a run (okstra-run), inspecting a finished task (okstra-inspect), or generating a brief (okstra-brief-gen). The skill never picks an answer — it builds the option board and transcribes what the user decides.
4
+ Use this to answer an okstra task's open clarification questions in-session, without hand-editing any file. The tell is a request to respond to an okstra run's clarification items or its approval gate — "answer okstra", "I'll answer the questions", "clarification response", "user response", "approve and move on". This skill lists tasks whose latest report still has open clarification blockers, then walks the open C-ids one at a time — each rewritten as a self-contained question with background, its options with their impact plus "Enter directly" — echoes the collected answers back for an explicit "confirmed" acknowledgement, optionally records approval, and writes the `user-responses/` sidecar via `okstra user-response write`. NOT for starting a run (okstra-run), inspecting a finished task (okstra-inspect), or generating a brief (okstra-brief-gen). The skill never picks an answer — it builds the option board and transcribes what the user decides.
5
5
  ---
6
6
 
7
7
  # OKSTRA User Response
8
8
 
9
9
  Single entry point for answering the clarification questions an okstra run left behind (the open `C-*` rows under the final report's `## 1. Clarification Items`) **in-session**, and recording those answers as a `runs/<type>/user-responses/` sidecar. The next `/okstra-run` auto-attaches this sidecar via `--clarification-response`.
10
10
 
11
- **Core principle — the skill never picks an answer for the user.** It builds the option board — background, a self-contained question, three concrete answers, `Enter directly` — and the user alone picks from it; every `value` is what the user chose or typed. It never calls `write` until the user has explicitly confirmed.
11
+ **Core principle — the skill never picks an answer for the user.** It builds the option board — background, a self-contained question, the report's options with their impact, `Enter directly` — and the user alone picks from it; every `value` is what the user chose or typed. It never calls `write` until the user has explicitly confirmed.
12
12
 
13
13
  **Second principle — one question at a time.** Never batch two clarification items into one question, and never dump the whole open list at the user. Ask item 1, transcribe the answer, then ask item 2.
14
14
 
15
15
  | Sub-command | What it does |
16
16
  |---|---|
17
17
  | `list` | List tasks that still have approval-open clarification, newest report first. |
18
- | `show` | Expand one report's open `C-*` rows (statement + recommended + alternatives + contextRefs). |
18
+ | `show` | Expand one report's open `C-*` rows (statement + options + contextRefs). |
19
19
  | `write` | Record the collected answers (+ optional approval) as a `user-responses/` sidecar. |
20
20
 
21
21
  ## Step 0: Preflight (shared)
@@ -71,7 +71,11 @@ Carry the chosen entry's `reportPath` and `taskKey` forward.
71
71
  okstra user-response show --report <reportPath>
72
72
  ```
73
73
 
74
- Returns `{reportPath, rows: [{id, kind, blocks, status, statement, recommended, alternatives, contextRefs, resolvedRefs}]}`. `resolvedRefs` is `[{ref, definition}]` — the CLI has already looked up what each internal token (`RB-002`, `FU-001`, `§4.7`, …) means in the report body; `definition` is `null` only when the report text alone could not resolve it (e.g. a `path:line` pointer).
74
+ Returns `{reportPath, rows: [{id, kind, blocks, status, statement, expectedForm, options, contextRefs, resolvedRefs}]}`.
75
+
76
+ Each entry of `options[]` is `{role, answer, rationale, scopeImpact, addedWork, directionChange}`. `role` is `recommended` for exactly one entry and `alternative` for the rest; `scopeImpact` is a token list drawn from `in-repo` / `cross-repo` / `new-schema` / `deferrable`; `addedWork` and `directionChange` are one sentence each. A schema-v1 report has nowhere to record impact, so those three arrive empty — the CLI reconstructs only the answers from the report's `Expected form` cell.
77
+
78
+ `resolvedRefs` is `[{ref, definition}]` — the CLI has already looked up what each internal token (`RB-002`, `FU-001`, `§4.7`, …) means in the report body; `definition` is `null` only when the report text alone could not resolve it (e.g. a `path:line` pointer).
75
79
 
76
80
  This call is a **data fetch, not a presentation step**. Do not print `rows` at the user, and do not paste a raw `statement` as a question — a `statement` like `Rewrite RB-002 rollback (see §4.7)` is meaningless on its own, which is the whole reason this skill exists. Announce only the count and the plan:
77
81
 
@@ -103,23 +107,19 @@ Close the background with the raw source on one line, so the mapping back to the
103
107
 
104
108
  > Source: `C-014` — "<raw statement>"
105
109
 
106
- ### 3b. The picker — three answers plus `Enter directly`
110
+ ### 3b. The picker — the report's options plus `Enter directly`
107
111
 
108
- One `AskUserQuestion` (single-select), with exactly four options in this order:
112
+ One `AskUserQuestion` (single-select). Fill the slots from `options[]` in array order — the `role: recommended` entry first with its label suffixed `(Recommended)`, then the `alternative` entries — and always close with `Enter directly` as the last option. Never mark anything but the `recommended` entry as recommended.
109
113
 
110
- 1. **The recommendation** — the answer from `recommended` restated in plain language, label suffixed `(Recommended)`; its description carries the rationale (the part of the `recommended` cell after `—`).
111
- 2. **Alternative** — `alternatives[0]`.
112
- 3. **Alternative** — `alternatives[1]`.
113
- 4. **`Enter directly`** — always last, always present.
114
+ Each `label` is that option's `answer`, kept to the answer itself (1–5 words). Each `description` carries the rationale followed by the three impact axes, in this fixed order:
114
115
 
115
- Slots 2–3 fill from `alternatives[]` first. When `alternatives[]` is short, fill each remaining slot with the first of these that applies:
116
+ > `<rationale>` — Scope: `<scopeImpact, comma-joined>` · Added work: `<addedWork>` · Direction: `<directionChange>`
116
117
 
117
- - **a concrete candidate the report itself names** — another option row, an approach §2 rejected, a value already in use elsewhere. Its description must open with `Not an okstra proposal — from <where>` and cite that source.
118
- - **`Reframe this question`** — the item is held rather than answered, and the next run re-asks it.
118
+ The three axes answer three different questions: how far the choice reaches, what new work it creates, and what it overturns. Never fold them into one phrase — whichever is easiest to write ends up standing in for the other two, and the user weighs a scope change as though it were free. That is the failure this board exists to prevent.
119
119
 
120
- That last entry is what guarantees the picker always reaches three answers plus `Enter directly`. Never pad a slot with an option no source supports, and never mark anything but `recommended` as recommended.
120
+ When an axis is empty — a schema-v1 report has nowhere to record impact — write `not stated in the report` for that axis. Do not infer it, and do not read the code to reconstruct it. A guessed side effect is worse than a stated gap, because the user cannot tell the two apart.
121
121
 
122
- Keep each `label` to the answer itself (1–5 words); the description carries the consequence.
122
+ When `options[]` carries more than three entries, keep the recommended one plus the two alternatives whose `scopeImpact` differs most from it, and say in the background text how many you left out. When it carries exactly two, the picker has three options in total — do not pad it with an invented third.
123
123
 
124
124
  ### 3c. Transcribe the decision, then move on
125
125
 
@@ -127,13 +127,13 @@ Record one entry — `{id, kind, value, rationale?, disposition}`, `kind` copied
127
127
 
128
128
  | The user picks | `value` | `disposition` |
129
129
  |---|---|---|
130
- | Option 1–3 | that option's full answer text, not its short label | `answer` |
130
+ | One of the `options[]` entries | that option's `answer` text, not its short label | `answer` |
131
131
  | `Enter directly` → their own answer | the user's utterance verbatim (rationale into `rationale`) | `answer` |
132
- | `Reframe this question`, or free text asking for it to be re-asked | what the user wants re-asked, verbatim (empty → the raw statement) | `reframe` |
132
+ | Free text asking for the item to be re-asked | what the user wants re-asked, verbatim (empty → the raw statement) | `reframe` |
133
133
 
134
134
  A reframe is not an answer, so it does not satisfy the approval gate.
135
135
 
136
- If the user replies with a question instead of an answer ("what does this mean?"), record nothing: **Read** the `§`/`path:line` from `contextRefs[]`, explain it in plain language, and re-ask the same item with the same four options. Explain only — **do not resolve it for them**; the decision goes back to the user.
136
+ If the user replies with a question instead of an answer ("what does this mean?"), record nothing: **Read** the `§`/`path:line` from `contextRefs[]`, explain it in plain language, and re-ask the same item with the same options. Explain only — **do not resolve it for them**; the decision goes back to the user.
137
137
 
138
138
  Echo one line per finished item (`[2/5] C-014 → answer: 60s`), then ask the next one. Do not summarize the whole set until Step 4.
139
139
 
@@ -406,6 +406,10 @@ Carried-forward plan items retain their prior verdicts verbatim; each such item
406
406
  {% endif %}
407
407
  {% if implementationPlanning.planBodyVerification.gateBlockedBy %}- **Blocked by**: {% for cause in implementationPlanning.planBodyVerification.gateBlockedBy %}`{{ cause }}`{% if not loop.last %}, {% endif %}{% endfor %}
408
408
  {% endif %}
409
+ {% if implementationPlanning.planBodyVerification.uniformVerifiers %}- **{{ t("implementationPlanning.planBodyUniformVerifierLabel") }}**: {% for u in implementationPlanning.planBodyVerification.uniformVerifiers %}`{{ u.worker }}` → `{{ u.verdict }}` ({{ u.itemCount }}){% if not loop.last %}, {% endif %}{% endfor %}
410
+
411
+ > {{ t("implementationPlanning.planBodyUniformVerifierLegend") }}
412
+ {% endif %}
409
413
  > {{ t("implementationPlanning.planBodyGateLegend") }}
410
414
  >
411
415
  > {{ t("implementationPlanning.planBodyBlockedByLegend") }}
@@ -132,7 +132,11 @@
132
132
  "answer-as": "Answer as",
133
133
  "questions-waiting-on-you": "Questions waiting on you",
134
134
  "count-questions-waiting-on-you": "{count} questions waiting on you",
135
- "your-answer-to-id": "Your answer to {id}"
135
+ "your-answer-to-id": "Your answer to {id}",
136
+ "recommended": "Recommended",
137
+ "scope-impact": "Scope",
138
+ "added-work": "Added work",
139
+ "direction-change": "Direction change"
136
140
  },
137
141
  "visualizations": {
138
142
  "component": "Component",
@@ -132,7 +132,11 @@
132
132
  "answer-as": "답변 형식",
133
133
  "questions-waiting-on-you": "답변을 기다리는 질문",
134
134
  "count-questions-waiting-on-you": "답변을 기다리는 질문 {count}건",
135
- "your-answer-to-id": "{id}에 대한 답변"
135
+ "your-answer-to-id": "{id}에 대한 답변",
136
+ "recommended": "권장",
137
+ "scope-impact": "범위",
138
+ "added-work": "추가 작업",
139
+ "direction-change": "방향 전환"
136
140
  },
137
141
  "visualizations": {
138
142
  "component": "구성 요소",
@@ -40,6 +40,21 @@
40
40
  <p class="eyebrow">{{ row.id }} · {{ row.kind }}</p>
41
41
  {{ row.statement | paragraphs }}
42
42
  <dl class="clarification-expected"><dt>{{ t('macros.forms.answer-as') }}</dt><dd>{{ row.expectedForm | inline_code }}</dd></dl>
43
+ {% if row.options %}
44
+ <ol class="clarification-options">
45
+ {% for option in row.options %}
46
+ <li class="clarification-option{% if option.role == 'recommended' %} is-recommended{% endif %}">
47
+ <p class="clarification-option-answer">{{ option.answer }}{% if option.role == 'recommended' %} <span class="badge">{{ t('macros.forms.recommended') }}</span>{% endif %}</p>
48
+ <p class="clarification-option-rationale">{{ option.rationale }}</p>
49
+ <dl class="clarification-option-impact">
50
+ <dt>{{ t('macros.forms.scope-impact') }}</dt><dd>{{ option.scopeImpact | join(', ') }}</dd>
51
+ <dt>{{ t('macros.forms.added-work') }}</dt><dd>{{ option.addedWork }}</dd>
52
+ <dt>{{ t('macros.forms.direction-change') }}</dt><dd>{{ option.directionChange }}</dd>
53
+ </dl>
54
+ </li>
55
+ {% endfor %}
56
+ </ol>
57
+ {% endif %}
43
58
  <label for="response-{{ row.id }}">{{ t('macros.forms.your-answer-to-id') | replace('{id}', row.id) }}</label>
44
59
  <textarea id="response-{{ row.id }}" data-response-id="{{ row.id }}" rows="4">{{ row.userInput | default('') }}</textarea>
45
60
  </article>
@@ -69,6 +69,7 @@
69
69
  <h2>{{ t('tasks.implementation-planning.plan-body-verification') }}</h2>
70
70
  <p><strong>{{ t('tasks.implementation-planning.verdict') }}</strong> — {{ planning.planBodyVerification.gateResult | inline_code }} ({{ planning.planBodyVerification.roundCount }} rounds{% if planning.planBodyVerification.get("selfFixRoundsApplied") is not none %}, {{ planning.planBodyVerification.selfFixRoundsApplied }} self-fix rounds{% if planning.planBodyVerification.get("selfFixStopReason") %} · {{ planning.planBodyVerification.selfFixStopReason | inline_code }}{% endif %}{% endif %})</p>
71
71
  {% if planning.planBodyVerification.get("gateBlockedBy") %}<p><strong>{{ t('tasks.implementation-planning.what-it-blocked') }}</strong> — {{ planning.planBodyVerification.gateBlockedBy | join(", ") | inline_code }}</p>{% endif %}
72
+ {% if planning.planBodyVerification.get("uniformVerifiers") %}<p><strong>{{ t('implementationPlanning.planBodyUniformVerifierLabel') }}</strong> — {% for u in planning.planBodyVerification.uniformVerifiers %}{{ u.worker | inline_code }} → {{ u.verdict | inline_code }} ({{ u.itemCount }}){% if not loop.last %}, {% endif %}{% endfor %}<br><small>{{ t('implementationPlanning.planBodyUniformVerifierLegend') }}</small></p>{% endif %}
72
73
  <table><thead><tr><th>{{ t('tasks.implementation-planning.plan-item') }}</th><th>{{ t('tasks.implementation-planning.subject') }}</th></tr></thead><tbody>{% for row in planning.planBodyVerification.planItems %}<tr id="id-{{ row.id }}">{{ row_key(pairs=[("ID", row.id), ("Source section", row.sourceSection)]) }}<td>{{ row.subject | inline_code }}</td></tr>{% endfor %}</tbody></table>
73
74
  {% if planning.planBodyVerification.get("dissentLog") %}<h3>{{ t('tasks.implementation-planning.dissent-left-standing') }}</h3>
74
75
  <table><thead><tr><th>{{ t('tasks.implementation-planning.subject') }}</th><th>{{ t('tasks.implementation-planning.body') }}</th></tr></thead><tbody>{% for row in planning.planBodyVerification.dissentLog %}<tr>{{ row_key(pairs=[("Subject", row.planItem), ("Worker", row.workerRole)]) }}<td>{{ row.body | inline_code }}</td></tr>{% endfor %}</tbody></table>{% endif %}
@@ -137,6 +137,8 @@
137
137
  "implementationPlanning": {
138
138
  "planBodyGateLegend": "Gate values — `passed`: agreed, no dissent · `passed-with-dissent`: a minority dissent remains but the gate passes (a majority dissent would block approval) · `blocked-by-disagreement`: majority dissent blocks approval · `aborted-non-result`: verification itself produced no result.",
139
139
  "planBodyBlockedByLegend": "Which input blocked the gate — `majority-disagree`: a worker majority dissent · `coverage-gap`: a Requirement Coverage gap / blocked row, independent of worker votes · `non-result`: verification produced no result. Absent means nothing blocked.",
140
+ "planBodyUniformVerifierLabel": "Uniform verifier",
141
+ "planBodyUniformVerifierLegend": "This verifier answered the same verdict to every item it voted on, so this round's refutation signal came from its peers alone — the gate above reads as a wider cross-check than it was. A unanimous round is a legitimate outcome; confirm from the worker's `-audit-` sidecar that it actually opened the cited evidence.",
140
142
  "planBodyVerdictLegend": "Verdict — **AGREE**: executable as written and internally consistent with other items · **SUPPLEMENT**: item is sound but a dependency / edge case / precondition is missing · **DISAGREE**: has a defect (see Breakage kind) · **verification-error**: the worker produced no result.",
141
143
  "planBodyBreakageLegend": "Breakage kind — a: cited file path/symbol mismatches another step or option · b: command is not executable or is ambiguous · c: validation signal is not observable · d: rollback violates commit/dependency order (advisory — a rollback is human-run, so this never blocks the gate) · e: contradicts the trade-off matrix · f: requirement-coverage row does not map to an option/stage/step that actually satisfies the requirement. (`--` = not applicable) Fixability: planner-fixable = correctable from code + plan + brief; needs-user-input = requires an external decision.",
142
144
  "planBodySourceLabel": "source §",