okstra 0.163.2 → 0.165.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 (167) hide show
  1. package/README.md +8 -6
  2. package/docs/architecture.md +24 -15
  3. package/docs/cli.md +15 -8
  4. package/docs/for-ai/README.md +2 -2
  5. package/docs/for-ai/skills/okstra-inspect.md +2 -2
  6. package/docs/for-ai/skills/okstra-user-response.md +2 -2
  7. package/docs/project-structure-overview.md +22 -14
  8. package/package.json +1 -1
  9. package/runtime/BUILD.json +2 -2
  10. package/runtime/agents/workers/antigravity-worker.md +9 -7
  11. package/runtime/agents/workers/claude-worker.md +1 -0
  12. package/runtime/agents/workers/codex-worker.md +9 -7
  13. package/runtime/agents/workers/grok-worker.md +6 -4
  14. package/runtime/agents/workers/kimi-worker.md +6 -4
  15. package/runtime/bin/lib/okstra/cli.sh +5 -0
  16. package/runtime/bin/lib/okstra/globals.sh +2 -0
  17. package/runtime/bin/lib/okstra/usage.sh +5 -5
  18. package/runtime/bin/okstra-antigravity-exec.sh +1 -340
  19. package/runtime/bin/okstra-claude-exec.sh +1 -178
  20. package/runtime/bin/okstra-codex-exec.sh +1 -467
  21. package/runtime/bin/okstra-provider-exec.py +165 -190
  22. package/runtime/bin/okstra-trace-cleanup.sh +14 -7
  23. package/runtime/bin/okstra-wrapper-status.py +26 -19
  24. package/runtime/bin/okstra.sh +87 -91
  25. package/runtime/prompts/lead/adapters/cmux.md +2 -2
  26. package/runtime/prompts/lead/convergence.md +36 -8
  27. package/runtime/prompts/lead/okstra-lead-contract.md +24 -1
  28. package/runtime/prompts/lead/plan-body-verification.md +9 -1
  29. package/runtime/prompts/lead/report-writer.md +1 -0
  30. package/runtime/prompts/lead/team-contract.md +3 -3
  31. package/runtime/prompts/profiles/_common-contract.md +9 -1
  32. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  33. package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
  34. package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
  35. package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
  36. package/runtime/prompts/profiles/implementation-planning.md +6 -4
  37. package/runtime/python/okstra_ctl/adapters/accounting/__init__.py +11 -0
  38. package/runtime/python/okstra_ctl/adapters/accounting/claude_jsonl.py +17 -0
  39. package/runtime/python/okstra_ctl/adapters/accounting/cli_artifact.py +17 -0
  40. package/runtime/python/okstra_ctl/adapters/accounting/unavailable.py +19 -0
  41. package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +92 -0
  42. package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +54 -0
  43. package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +68 -0
  44. package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
  45. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +60 -0
  46. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/manifest.json +1 -0
  47. package/runtime/{prompts/lead/adapters/antigravity.md → python/okstra_ctl/adapters/hosts/antigravity/relay.md} +52 -0
  48. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +292 -0
  49. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +120 -0
  50. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -0
  51. package/runtime/{prompts/lead/adapters/claude-code.md → python/okstra_ctl/adapters/hosts/claude-code/relay.md} +112 -1
  52. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +60 -0
  53. package/runtime/python/okstra_ctl/adapters/hosts/codex/manifest.json +1 -0
  54. package/runtime/{prompts/lead/adapters/codex.md → python/okstra_ctl/adapters/hosts/codex/relay.md} +52 -0
  55. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +72 -0
  56. package/runtime/python/okstra_ctl/adapters/hosts/external/manifest.json +1 -0
  57. package/runtime/{prompts/lead/adapters/external.md → python/okstra_ctl/adapters/hosts/external/relay.md} +53 -1
  58. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +63 -0
  59. package/runtime/python/okstra_ctl/adapters/hosts/grok/manifest.json +1 -0
  60. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +90 -0
  61. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +63 -0
  62. package/runtime/python/okstra_ctl/adapters/hosts/kimi/manifest.json +1 -0
  63. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +90 -0
  64. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +183 -0
  65. package/runtime/python/okstra_ctl/adapters/providers/antigravity/manifest.json +1 -0
  66. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +110 -0
  67. package/runtime/python/okstra_ctl/adapters/providers/claude/manifest.json +1 -0
  68. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +84 -0
  69. package/runtime/python/okstra_ctl/adapters/providers/codex/manifest.json +1 -0
  70. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +76 -0
  71. package/runtime/python/okstra_ctl/adapters/providers/grok/manifest.json +1 -0
  72. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +80 -0
  73. package/runtime/python/okstra_ctl/adapters/providers/kimi/manifest.json +1 -0
  74. package/runtime/python/okstra_ctl/application/__init__.py +1 -0
  75. package/runtime/python/okstra_ctl/application/advance_wizard.py +25 -0
  76. package/runtime/python/okstra_ctl/application/collect_usage.py +15 -0
  77. package/runtime/python/okstra_ctl/application/dispatch_assignments.py +15 -0
  78. package/runtime/python/okstra_ctl/application/resolve_assignment.py +93 -0
  79. package/runtime/python/okstra_ctl/application/resume_run.py +21 -0
  80. package/runtime/python/okstra_ctl/application/start_run.py +21 -0
  81. package/runtime/python/okstra_ctl/codex_dispatch.py +49 -826
  82. package/runtime/python/okstra_ctl/dispatch_core.py +244 -29
  83. package/runtime/python/okstra_ctl/dispatch_state.py +27 -0
  84. package/runtime/python/okstra_ctl/domain/__init__.py +34 -0
  85. package/runtime/python/okstra_ctl/domain/host.py +100 -0
  86. package/runtime/python/okstra_ctl/domain/provider.py +70 -0
  87. package/runtime/python/okstra_ctl/domain/wizard/__init__.py +19 -0
  88. package/runtime/python/okstra_ctl/domain/wizard/interaction.py +140 -0
  89. package/runtime/python/okstra_ctl/domain/worker_exec.py +102 -0
  90. package/runtime/python/okstra_ctl/domain/worker_role.py +34 -0
  91. package/runtime/python/okstra_ctl/domain/worker_stream.py +261 -0
  92. package/runtime/python/okstra_ctl/entrypoints/__init__.py +1 -0
  93. package/runtime/python/okstra_ctl/entrypoints/hosts.py +334 -0
  94. package/runtime/python/okstra_ctl/incremental_scope.py +16 -4
  95. package/runtime/python/okstra_ctl/models.py +54 -269
  96. package/runtime/python/okstra_ctl/ports/__init__.py +15 -0
  97. package/runtime/python/okstra_ctl/ports/host.py +32 -0
  98. package/runtime/python/okstra_ctl/ports/interaction.py +15 -0
  99. package/runtime/python/okstra_ctl/ports/lead_session.py +25 -0
  100. package/runtime/python/okstra_ctl/ports/usage_accounting.py +23 -0
  101. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +32 -0
  102. package/runtime/python/okstra_ctl/registry/__init__.py +13 -0
  103. package/runtime/python/okstra_ctl/registry/factory_loader.py +32 -0
  104. package/runtime/python/okstra_ctl/registry/host_discovery.py +124 -0
  105. package/runtime/python/okstra_ctl/registry/host_registry.py +365 -0
  106. package/runtime/python/okstra_ctl/registry/provider_registry.py +149 -0
  107. package/runtime/python/okstra_ctl/render.py +145 -47
  108. package/runtime/python/okstra_ctl/report_html/common.py +71 -25
  109. package/runtime/python/okstra_ctl/report_html/models.py +5 -0
  110. package/runtime/python/okstra_ctl/report_html/render.py +1 -1
  111. package/runtime/python/okstra_ctl/report_html/run_usage.py +19 -0
  112. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +14 -0
  113. package/runtime/python/okstra_ctl/report_views.py +44 -16
  114. package/runtime/python/okstra_ctl/run.py +80 -58
  115. package/runtime/python/okstra_ctl/session.py +1 -1
  116. package/runtime/python/okstra_ctl/stage_citations.py +52 -15
  117. package/runtime/python/okstra_ctl/team.py +44 -32
  118. package/runtime/python/okstra_ctl/user_response.py +45 -29
  119. package/runtime/python/okstra_ctl/wizard.py +175 -73
  120. package/runtime/python/okstra_ctl/worker_audit_ledger.py +29 -4
  121. package/runtime/python/okstra_ctl/worker_prompt_policy.py +10 -3
  122. package/runtime/python/okstra_ctl/worker_request.py +140 -0
  123. package/runtime/python/okstra_ctl/worker_runner.py +622 -0
  124. package/runtime/python/okstra_token_usage/collect.py +42 -7
  125. package/runtime/python/okstra_token_usage/report.py +42 -0
  126. package/runtime/python/okstra_token_usage/task_totals.py +88 -0
  127. package/runtime/schemas/final-report-v1.0.schema.json +4040 -1066
  128. package/runtime/schemas/final-report-v2.0.schema.json +5673 -1412
  129. package/runtime/skills/okstra-inspect/SKILL.md +1 -2
  130. package/runtime/skills/okstra-inspect/facets/logs.md +5 -5
  131. package/runtime/skills/okstra-inspect/facets/run-audit.md +3 -3
  132. package/runtime/skills/okstra-run/SKILL.md +74 -29
  133. package/runtime/skills/okstra-user-response/SKILL.md +15 -5
  134. package/runtime/templates/implementation-worker-preamble.md +1 -1
  135. package/runtime/templates/report-writer-prompt-preamble.md +1 -0
  136. package/runtime/templates/reports/final-report.template.md +3 -3
  137. package/runtime/templates/reports/html/assets/base.css +8 -4
  138. package/runtime/templates/reports/html/base.template.html +12 -6
  139. package/runtime/templates/reports/html/i18n/en.json +32 -7
  140. package/runtime/templates/reports/html/i18n/ko.json +32 -7
  141. package/runtime/templates/reports/html/macros/forms.html +9 -3
  142. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +14 -19
  143. package/runtime/templates/reports/report.js +59 -26
  144. package/runtime/templates/reports/user-response.template.md +12 -8
  145. package/runtime/templates/worker-prompt-preamble.md +1 -1
  146. package/runtime/validators/validate-implementation-plan-stages.py +17 -22
  147. package/runtime/validators/validate-report-views.py +0 -39
  148. package/runtime/validators/validate-run.py +96 -14
  149. package/runtime/validators/validate_session_conformance.py +69 -3
  150. package/src/cli-registry.mjs +0 -7
  151. package/src/commands/execute/render-bundle.mjs +4 -4
  152. package/src/commands/execute/run.mjs +8 -25
  153. package/src/commands/execute/wizard.mjs +33 -13
  154. package/src/commands/lifecycle/doctor.mjs +10 -10
  155. package/src/commands/lifecycle/install.mjs +53 -30
  156. package/src/commands/lifecycle/preflight.mjs +14 -4
  157. package/src/lib/host-registry-client.mjs +176 -0
  158. package/src/lib/runtime-manifest.mjs +6 -8
  159. package/runtime/bin/okstra-wrapper-agy-stream.py +0 -61
  160. package/runtime/python/okstra_ctl/error_issue.py +0 -640
  161. package/runtime/python/okstra_ctl/issue_signals.py +0 -186
  162. package/runtime/python/okstra_ctl/lead_runtime.py +0 -115
  163. package/runtime/python/okstra_ctl/runner_resolution.py +0 -103
  164. package/runtime/skills/okstra-inspect/facets/error-issue.md +0 -77
  165. package/src/commands/inspect/error-issue.mjs +0 -27
  166. package/src/lib/runtime-readiness.mjs +0 -90
  167. package/src/lib/runtime-resolver.mjs +0 -123
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: okstra-inspect
3
3
  description: >-
4
- Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (verdict / pass); its elapsed time or context/read cost; its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (error report). Also turns cross-project okstra errors into an anonymized zip (error feedback) or GitHub issues (file okstra issues), and audits run health across tasks (run audit). NOT for starting a run (okstra-run), rollups/schedules (okstra-rollup / okstra-schedule-gen), a brief (okstra-brief-gen), setup (okstra-setup), or cross-project management (okstra-manager).
4
+ Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (verdict / pass); its elapsed time or context/read cost; its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (error report). Also turns cross-project okstra errors into an anonymized zip (error feedback), and audits run health across tasks (run audit). NOT for starting a run (okstra-run), rollups/schedules (okstra-rollup / okstra-schedule-gen), a brief (okstra-brief-gen), setup (okstra-setup), or cross-project management (okstra-manager).
5
5
  ---
6
6
 
7
7
  # OKSTRA Inspect
@@ -18,7 +18,6 @@ Single read-side entry point for okstra runtime inspection plus the one status m
18
18
  | `cost` | `facets/cost.md` | Estimate file/read context cost for a task bundle. |
19
19
  | `errors` | `facets/errors.md` | Aggregate okstra-run error logs for a task into a timestamped markdown report; print a summary. |
20
20
  | `error-zip` | `facets/error-zip.md` | Collect cross-project okstra error logs into an anonymized zip (report + raw) and summarize clusters. |
21
- | `error-issue` | `facets/error-issue.md` | Turn cross-project okstra anomalies into GitHub issue candidates; file the approved ones after explicit user approval. |
22
21
  | `run-audit` | `facets/run-audit.md` | Check every run's artifacts against progress invariants; report what went wrong without an error ever being logged. |
23
22
  | `recap` | `facets/recap.md` | Summarize a task's run-to-run phase transitions, then answer free-form questions over its `.okstra` artifacts. Appends each summary/Q&A to `recap/recap-log.jsonl`, and writes agent-authored notes to `notes/` for feeding into later runs. |
24
23
 
@@ -6,17 +6,17 @@ Loaded lazily by the dispatch table in `SKILL.md` (core). Shared rules — Step
6
6
 
7
7
  Trigger phrases: "okstra logs", "log status", "log files", "log size", "log cleanup".
8
8
 
9
- Read-only inventory of codex/antigravity wrapper log files written next to each prompt history file (`<prompt>.log`). Reports sizes, ages, totals, and suggests cleanup commands. **Does not delete** — the user runs whichever `find … -delete` line they like.
9
+ Read-only inventory of the worker log files written next to each prompt history file (`<prompt>.log`). Reports sizes, ages, totals, and suggests cleanup commands. **Does not delete** — the user runs whichever `find … -delete` line they like.
10
10
 
11
- **Background:** codex/antigravity wrappers (`okstra-codex-exec.sh`, `okstra-antigravity-exec.sh`) write a sidecar log next to each prompt history file:
11
+ **Background:** every CLI-worker dispatch writes a sidecar log next to its prompt history file. All five providers go through the same shared runner (`~/.okstra/lib/python/okstra_ctl/worker_runner.py`), so the file is there whichever provider ran:
12
12
 
13
13
  ```
14
14
  .okstra/tasks/<task-group>/<task-id>/runs/<phase>/prompts/
15
15
  <worker>-worker-prompt-<phase>-<seq>.md <-- prompt (git-tracked)
16
- <worker>-worker-prompt-<phase>-<seq>.log <-- live stdout+stderr mirror
16
+ <worker>-worker-prompt-<phase>-<seq>.log <-- the run's archive of what the worker did
17
17
  ```
18
18
 
19
- The log is truncated at each dispatch (`: > "$log_path"`) — only the latest run for a given seq is preserved. Different seqs keep separate files. Long-running implementation dispatches can produce multi-MB logs; analysis-phase dispatches are typically smaller.
19
+ The log is rewritten from scratch at each dispatch — only the latest run for a given seq is preserved. Different seqs keep separate files. It holds one row per tool call and per tool result plus the worker's closing text, and drops thinking; a provider that emits plain text instead of an event stream has its progress copy capped at 5000 lines per run, with `[okstra log-cap] N progress line(s) elided` marking what was dropped. Long-running implementation dispatches can still produce multi-MB logs; analysis-phase dispatches are typically smaller.
20
20
 
21
21
  ### logs.1 — Inventory
22
22
 
@@ -29,7 +29,7 @@ Scans `<projectRoot>/.okstra/tasks/**/runs/*/prompts/*.log` and parses task/phas
29
29
  - `perTask[]` — `{taskKey, fileCount, totalBytes, oldestEpoch, newestEpoch}`, total-size desc
30
30
  - `totals` — `{fileCount, totalBytes, taskCount}`
31
31
 
32
- If `totals.fileCount` is 0, report `No wrapper log files found under <projectRoot>` and stop.
32
+ If `totals.fileCount` is 0, report `No worker log files found under <projectRoot>` and stop.
33
33
 
34
34
  ### logs.2 — Summary tables
35
35
 
@@ -28,7 +28,7 @@ Under each, list the affected `taskKey` values with their `detail` and `source`.
28
28
  State plainly which violations are this task's own state versus a repeated okstra defect:
29
29
 
30
30
  - an invariant broken in **one** task → that task's own state; tell the user what to do about that task
31
- - an invariant broken across **two or more** tasks → an okstra defect; point at `/okstra-inspect error-issue` to file it
32
- - `approval-not-forgotten` is the exception and never routes to `error-issue` no matter how far it spreads. It means a human has not approved yet, not that okstra did something wrong — `error_issue.NEVER_AN_ISSUE` drops it, so pointing the user at `error-issue` for it promises a filing that will never happen. Report it as a to-do list of tasks awaiting the user's approval instead.
31
+ - an invariant broken across **two or more** tasks → a repeated okstra defect; say so and cite every affected `taskKey` so the user can decide what to do with it
32
+ - `approval-not-forgotten` is the exception no matter how far it spreads. It means a human has not approved yet, not that okstra did something wrong. Report it as a to-do list of tasks awaiting the user's approval instead.
33
33
 
34
- Never file an issue from this facet. Filing is `error-issue`'s job and it has its own approval gate.
34
+ This facet reports; it never files anything anywhere.
@@ -41,28 +41,29 @@ Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit i
41
41
 
42
42
  On `ok: false`, re-prompt with the same `current.step` using the error message. The wizard never advances on validation failure; the user retries the same step. **`current` may be `null`** when the current step itself cannot render (e.g. the approved plan's Stage Map is corrupt) — that case is terminal: show `error` and stop, the user must fix the plan file before retrying. Never re-prompt off a `null` `current`.
43
43
 
44
- The wizard tells you *which UI to use* via `kind` (and the optional `multi` flag on `pick`):
45
-
46
- - `kind: "pick"` + `presentation: "numbered-text"` → do not call `AskUserQuestion`. Render every option as a numbered Markdown list in its original order, then consume the user's next message as the answer. Submit that message unchanged with `--answer`; the wizard accepts a 1-based number, an exact option value, or an exact option label and resolves it to the canonical value. An invalid, out-of-range, or ambiguous answer returns `ok: false` and must re-prompt the same complete list.
47
- - `kind: "pick"` + `multi: false` (default) → render `AskUserQuestion` with `label`, `options[].label`, and `multiSelect: false`. Use the chosen `options[].value` (single string) as the answer.
48
- - `kind: "pick"` + `multi: true` → render `AskUserQuestion` with `label`, `options[].label`, and `multiSelect: true`. Join the chosen `options[].value` entries with `,` into a single CSV string and submit that as `--answer "csv,values"`. If the user selects nothing, still submit `--answer ""` — the wizard will reply `ok: false` and re-prompt the same step (do not skip the call).
49
- - `kind: "pick_group"` → render a SINGLE `AskUserQuestion` whose questions array maps 1:1 to the wizard's `questions[]`. For each entry use `questions[].label`, `questions[].options[].label`, and `multiSelect: questions[].multi`. Collect the user's chosen `options[].value` per tab, build a JSON object keyed by each `questions[].step`, and submit it as a single literal `--answer '{"lead_model":"opus","claude_model":"default",...}'`. A tab the user leaves at its default still gets its `"default"`/`""` value in the JSON. Never split a `pick_group` into multiple `AskUserQuestion` calls — the wizard already capped it at 4 tabs and emits any remainder as the next prompt.
50
- - `kind: "text"` → write `label` as a plain text message and consume the user's NEXT message as the answer.
44
+ The wizard tells you which relay operation to use via `next.interaction.kind`. Step 1 loads the current registered host's relay contract. Use the matching entry under its `interactions` object exactly; if the entry is absent, stop instead of inventing a host function or falling back silently. The only exception is the explicit `Legacy text relay compatibility` mapping selected in Step 1 when the preflight response has no `relayContract`.
45
+
46
+ - `native-single` → use the current registered host's relay contract for its native single-select function. Pass every option in its original order and submit the selected `options[].value`.
47
+ - `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the selected values as one CSV answer; submit an empty selection as `--answer ""`.
48
+ - `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`; when a grouped question is multi-select, its step value is the selected option values joined as one CSV string, not a JSON array.
49
+ - `numbered-single` → render every option as a 1-based numbered Markdown list and submit the user's next message unchanged.
50
+ - `numbered-multi` → render every option as a 1-based numbered Markdown list and submit the user's comma-separated reply unchanged.
51
+ - `sequential-group` → render every question in order, preserve each complete option list, collect every answer, and submit one JSON object keyed by `questions[].step`.
52
+ - `plain-text` → write `label` as plain text and submit the user's next message unchanged.
51
53
  - `kind: "done"` → input collection finished; move to Step 5.
52
54
  - `kind: "aborted"` → the user picked abort; the wizard is terminally cancelled. Tell the user on one short line that the run setup was aborted, delete the state file (`rm` with the literal path), and stop this skill — do NOT call `render-args` or `render-bundle` (the wizard rejects `render-args` on an aborted state).
53
55
 
56
+ Submit the answer shape required by `interaction.answerProtocol`; do not add normalization that the protocol does not request. Invalid, out-of-range, or ambiguous answers return `ok: false` and must re-render the same complete interaction.
57
+
54
58
  The final `confirm` step is a normal `pick` step with three options — `Proceed` / `Edit` / `Abort`(abort) — and is rendered the same way (no special handling). `Edit` rewinds to any earlier step (including `base-ref`); `Abort` terminally cancels the wizard. The branch/worktree decision the run will actually use (for `implementation`, the **stage worktree** — not the task-key directory) is folded into the Step 4 confirmation summary block as a `worktree` line, so there is no separate branch-confirm prompt.
55
59
 
56
- Never invent additional questions. Never reorder. **Never drop, hide, or merge a `pick` / `pick_group` option** — render every `options[]` entry, including entries that carry a `(default)` / `(recommended)` suffix. Use an `AskUserQuestion` choice unless the wizard explicitly sets `presentation: "numbered-text"`; that presentation preserves overflow choices as a numbered Markdown list. Do NOT collapse a multi-option pick into a "recommended + Enter directly / Other" shortlist: the wizard's `options[]` array IS the complete, authoritative choice set. Example: if a pick's `options[]` carries N entries, render all N — never abbreviate a multi-option step down to one recommended value. The run-prompt recommendation rule (1–2 recommendations + Enter directly) applies ONLY to prompts this skill authors itself (e.g. the conformance-waiver picker), never to wizard-provided `options[]`. Never use `AskUserQuestion` for `text` prompts — the wizard explicitly chose `text` to avoid the picker-Other re-render lag.
60
+ Never invent additional questions. **Never drop, hide, merge, reorder, or truncate** a `pick` / `pick_group` option — relay every `options[]` entry, including entries that carry a `(default)` / `(recommended)` suffix. Do not collapse a multi-option pick into a "recommended + Enter directly / Other" shortlist. The wizard's arrays are the complete authoritative choice sets, regardless of the current host UI's usual option limit. The run-prompt recommendation rule (1–2 recommendations + Enter directly) applies only to prompts this skill authors itself, never to wizard-provided options.
57
61
 
58
62
  ## Step 1: Preflight
59
63
 
60
- Resolve `<host-runtime>` from the host that is executing this skill. This is a harness capability declaration, never a `which`/`PATH` inference and never a worker-provider choice:
64
+ Use the launcher's `OKSTRA_RUNTIME_HOST` value when it is present. Otherwise use the registered host ID that the current harness declares for this session. This is a harness capability declaration, never a worker-provider choice. Do not infer it from an executable or `PATH`, and do not silently substitute another host.
61
65
 
62
- - Claude Code host → `claude-code`
63
- - Codex host → `codex`
64
- - Antigravity CLI host → `antigravity`
65
- - other host → `external`
66
+ Record the semantic functions that the live harness can actually perform. Always include `plain_text_input`. Record `native_single_select`, `native_multi_select`, or `native_question_group` only when the current harness exposes that function. Do not infer functions from the host ID; the effective declaration is computed from the relay contract after preflight.
66
67
 
67
68
  The host-native provider runs as the current lead session when its role is supported. Other selected providers run through their registered CLI wrappers. Claude remains only the model-policy default; it is not the process owner.
68
69
 
@@ -72,15 +73,61 @@ Run one Bash tool call (Bash invocation rule from the top of this file applies):
72
73
  okstra preflight --runtime <host-runtime> --json
73
74
  ```
74
75
 
75
- Parse the stdout JSON. `ok: false` (or `okstra` not on `PATH` at all) → tell the user: "okstra not set up — run `/okstra-setup` first." Then stop. If the call fails with `unknown command: preflight`, the `okstra` binary on PATH predates this skill — tell the user to update it (`npm i -g okstra@latest`), then stop (`/okstra-setup` does not update the binary). Do **not** try to invoke `npx -y okstra@latest ...` as a fallback — `npx` is not on the literal-token allow-list and will force a confirmation prompt on every wizard call afterward. Every subsequent `okstra <subcmd>` call self-bootstraps its Python path, so never `export PYTHONPATH=...`.
76
+ Parse the stdout JSON. If `ok: false` with `stage: runtime_readiness`, show `runtimeReadiness.status` and every readiness check, including each adapter-provided `action`, then stop before Step 2. Do not relabel a host login, license, daemon, trust, or other readiness failure as missing Okstra setup. Any other `ok: false` response (or `okstra` not on `PATH` at all) → tell the user: "okstra not set up — run `/okstra-setup` first." Then stop. If the call fails with `unknown command: preflight`, the `okstra` binary on PATH predates this skill — tell the user to update it (`npm i -g okstra@latest`), then stop (`/okstra-setup` does not update the binary). Do **not** try to invoke `npx -y okstra@latest ...` as a fallback — `npx` is not on the literal-token allow-list and will force a confirmation prompt on every wizard call afterward. Every subsequent `okstra <subcmd>` call self-bootstraps its Python path, so never `export PYTHONPATH=...`.
77
+
78
+ When `runtimeReadiness` is present on an `ok: true` response, require `runtimeReadiness.status: ready` before Step 2. These checks come from the selected host adapter and are independent of worker-provider choices. A `blocked` or `unknown` status is a host readiness failure regardless of check IDs: show the status and checks, then stop before Step 2. Any other readiness status is unrecognized runtime output: show it and stop before Step 2 instead of guessing that the host is ready.
79
+
80
+ For a ready response, read the absolute file path in `runtimeReadiness.relayContract` with the current host's file-read primitive. Do not derive the path from the host ID or search `PATH`. In that file, find the `Wizard interaction relay` JSON block, require `schemaVersion: 1` and `runtime` equal to `runtimeReadiness.runtime`, then take its `semanticFunctions` allowlist and intersect it with the functions the live harness exposes. Pass only that intersection to Step 2; `plain_text_input` must be present. Keep the parsed `interactions` object for Step 3's function/input/response conversion. An unreadable file, malformed block, runtime mismatch, absent `plain_text_input`, or later interaction kind missing from the object is a host relay contract failure: show the problem and stop rather than guessing.
81
+
82
+ If `runtimeReadiness` is absent, preserve compatibility with an older preflight response: carry `projectRoot` and `projectId` into Step 2 and treat it as a response with no `relayContract`. If readiness exists but `relayContract` is absent, also enter the compatibility branch below. In that branch only, declare `plain_text_input` and keep its built-in `interactions` mapping for Step 3; do not assume a native tool from the host ID. The existing `unknown command: preflight` branch remains the authoritative stale-CLI failure.
76
83
 
77
- On `ok: true`, inspect `runtimeReadiness.checks` for the check whose `id` is `workspace-trust`. This check is host-specific and independent of the selected worker providers.
84
+ ### Legacy text relay compatibility
85
+
86
+ Use this built-in mapping only when the successful preflight response omitted `relayContract`. It is not a fallback for an unreadable, malformed, mismatched, or incomplete relay contract. If a present relay contract omits the wizard's interaction kind, keep the fail-closed rule above and stop.
87
+
88
+ ```json
89
+ {
90
+ "scope": "preflight-response-without-relayContract-only",
91
+ "semanticFunctions": ["plain_text_input"],
92
+ "interactions": {
93
+ "numbered-single": {
94
+ "function": "host-text",
95
+ "input": {
96
+ "question": "label-with-progress",
97
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
98
+ },
99
+ "response": { "source": "next-message", "submit": "raw" }
100
+ },
101
+ "numbered-multi": {
102
+ "function": "host-text",
103
+ "input": {
104
+ "question": "label-with-progress",
105
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
106
+ },
107
+ "response": { "source": "next-message", "submit": "raw" }
108
+ },
109
+ "sequential-group": {
110
+ "function": "host-text",
111
+ "input": {
112
+ "questions": "all-one-at-a-time-in-original-order",
113
+ "question": "label-with-progress",
114
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
115
+ },
116
+ "response": {
117
+ "source": "next-message-by-question-position",
118
+ "submit": "compact-step-json-raw"
119
+ }
120
+ },
121
+ "plain-text": {
122
+ "function": "host-text",
123
+ "input": { "question": "label-with-progress" },
124
+ "response": { "source": "next-message", "submit": "raw" }
125
+ }
126
+ }
127
+ }
128
+ ```
78
129
 
79
- - On `claude-code`, `status: required` with `action: restart-and-trust` → tell the user: "Claude Code must trust `<projectRoot>` before Okstra can dispatch workers. Close this session, reopen that project in Claude Code, choose `Yes, I trust this folder`, then run `/okstra-run` again." Then stop before Step 2. Never start the wizard or dispatch a worker from this session.
80
- - On `claude-code`, `status: unavailable` → tell the user that Claude Code workspace trust could not be verified, ask them to open `<projectRoot>` directly in Claude Code and complete any trust prompt, then run `/okstra-run` again. Then stop before Step 2.
81
- - `status: accepted` or `status: not-applicable` → carry `projectRoot` and `projectId` as literal strings into Step 2.
82
- - If `runtimeReadiness` is absent, preserve compatibility with an older preflight response: carry `projectRoot` and `projectId` into Step 2. The existing `unknown command: preflight` branch remains the authoritative stale-CLI failure.
83
- - Any other `workspace-trust` status is unrecognized runtime output: show the status and stop before Step 2 instead of guessing that trust exists.
130
+ For numbered interactions, render every option as a 1-based Markdown item containing its label followed by its description verbatim, in original order. For `sequential-group`, ask every question in order and build one compact JSON object keyed by `questions[].step`. Submit each user message unchanged; the wizard owns numbered and grouped normalization.
84
131
 
85
132
  ## Step 2: Initialize the wizard
86
133
 
@@ -99,7 +146,10 @@ okstra wizard init \
99
146
  --state-file /var/folders/.../okstra-wizard.AbCd.json \
100
147
  --project-root /abs/path/to/project \
101
148
  --project-id my-project-id \
102
- --host-runtime <host-runtime>
149
+ --host-runtime <host-runtime> \
150
+ --entry-mode current-session \
151
+ --available-function plain_text_input \
152
+ --available-function <each-additional-function-in-the-effective-intersection>
103
153
  ```
104
154
 
105
155
  Output: the same `{ok, next}` JSON described above. The first `next` is always `step: "task_pick"`.
@@ -108,12 +158,7 @@ Output: the same `{ok, next}` JSON described above. The first `next` is always `
108
158
 
109
159
  Repeat until `next.kind == "done"` (or `"aborted"` — terminal cancel, see "How the wizard talks to you"):
110
160
 
111
- 1. **Render** the prompt according to `kind` (and `multi` for pick). **Always append the progress marker to the rendered question label** (the `AskUserQuestion` question text, or the `text`-prompt message): suffix it with ` (<next.progress.label>)` — render `progress.label` exactly as the wizard sent it, never recompute it. Example: label `Step 8/11 · 3 steps remaining` → `Select a model (Step 8/11 · 3 steps remaining)`. Re-prompts after `ok: false` reuse `current.progress.label` the same way. The progress marker is presentation-only — never send it back to the wizard as part of an answer.
112
- - `pick` + `presentation: "numbered-text"` → plain text containing `label`, followed by every option as a numbered Markdown list. Consume the user's next message verbatim as the answer string.
113
- - `pick` + `multi: false` → `AskUserQuestion` with `multiSelect: false`, `label`, and `options`. The user's chosen option's `value` is the answer string.
114
- - `pick` + `multi: true` → `AskUserQuestion` with `multiSelect: true`, `label`, and `options`. Join the selected `value`s with `,` into a single literal CSV string (e.g. `"claude,codex,grok,kimi"`) and submit it as one `--answer` value. Empty selection submits `--answer ""` and the wizard re-prompts.
115
- - `pick_group` → one `AskUserQuestion` with one question per `questions[]` entry (tab). Map each tab's selected `value` back by `questions[].step`, assemble a JSON object, and submit it as a single literal `--answer '<json>'`.
116
- - `text` → plain text message containing `label`. Consume the user's next reply verbatim as the answer string (empty reply = empty string).
161
+ 1. **Render** the prompt according to `next.interaction.kind` using the relay rules above. **Always append the progress marker to the rendered question label**: suffix it with ` (<next.progress.label>)` — render `progress.label` exactly as the wizard sent it, never recompute it. Example: label `Step 8/11 · 3 steps remaining` → `Select a model (Step 8/11 · 3 steps remaining)`. Re-prompts after `ok: false` reuse `current.progress.label` the same way. The progress marker is presentation-only — never send it back to the wizard as part of an answer.
117
162
  2. **Submit** the answer — call `okstra wizard step` with the literal state-file path from Step 2 and the literal user answer (no shell variables, no `$(...)`):
118
163
  ```bash
119
164
  okstra wizard step --state-file /var/folders/.../okstra-wizard.AbCd.json --answer preprod
@@ -138,7 +183,7 @@ That is the entire interactive flow. The wizard handles:
138
183
  - brief path — **asked only for entry task-types (requirements-discovery / error-analysis / improvement-discovery / project-analysis / feature-analysis / change-impact-analysis)** (same-group `.okstra/briefs/<task-group>/**/*.md` candidates first, sorted by the newer of file-created/modified time and latest task-catalog use; direct input last; `Keep / Change` for existing entry tasks). `project-analysis`, `feature-analysis`, and `change-impact-analysis` are brief entry task types. A downstream lifecycle task-type auto carries in the manifest's brief, and when no registered brief exists a `brief_carry` 3-option prompt appears (recommend switching to entry / Enter directly / Abort). `release-handoff` has no brief step at all — prepare generates the input document that cites the verification report,
139
184
  - analysis-input sub-flow — `project-analysis` has no target or evidence step. `feature-analysis` runs `project_evidence_pick` / `project_evidence`, then `analysis_target_pick` / `analysis_target`; the target is required even when the user skips project evidence. `change-impact-analysis` runs `feature_evidence_pick` / `feature_evidence`, then `project_evidence_pick` / `project_evidence`. The evidence steps show accepted compatible reports first and retain direct-path entry; do not invent a different relation or reorder these steps,
140
185
  - base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
141
- - `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = the earliest incomplete stage whose dependencies are satisfied, or a specific stage number) + executor pick. When an approved plan is selected and a `## APPROVAL` sidecar exported from the report — matching the plan on source-report·seq — is detected in that run's sibling `user-responses/`, the approve-confirm step expands to 3 options (`yes_apply` recommended: approve + apply the option as exported / `yes` approve only / `no` abort) — `yes_apply` validates the option against the plan's `optionCandidates` before applying it via the existing approval·option path,
186
+ - `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = the earliest incomplete stage whose dependencies are satisfied, or a specific stage number) + executor pick. When an approved plan is selected and a `## PLAN DECISION` sidecar carrying `Status: approved`, exported from the report — matching the plan on source-report·seq — is detected in that run's sibling `user-responses/`, the approve-confirm step expands to 3 options (`yes_apply` recommended: approve + apply the option as exported / `yes` approve only / `no` abort) — `yes_apply` validates the option against the plan's `optionCandidates` before applying it via the existing approval·option path,
142
187
  - `release-handoff`-only sub-flow: after the approved plan auto-resolves, a `handoff_stage_pick` multi-select — choose an eligible stage bundle (stage-group) or the whole task (when an accepted whole-task verification report exists); the result goes out as render-args' `stages` key (csv, empty when whole-task),
143
188
  - `Use defaults / Customize` branch with profile-aware worker/model questions,
144
189
  - **resume-clarification (in-session equivalent)** — there is no separate mode or flag matching the shell's `okstra.sh --resume-clarification`; two steps of the standard flow carry out its substance. (1) `reuse_previous` (yes/no to reuse the previous run's settings — in `requirements-discovery` / `error-analysis` / `implementation-planning`, only when prior run-inputs exist): YES prefills workers·model·directive·related-tasks at once. (2) `clarification_pick`: if the **task-type's own** previous `final-report` exists it is auto-recommended as the carry-in input (falling back to the newest by mtime across all phases when absent), and the same run's `user-responses/` sidecar (answers the user filled in) is attached alongside. The chosen path is passed to prepare as `--clarification-response` — the user makes the sidecar via the report's `Export user response`, places it in `runs/<task-type>/user-responses/`, and re-runs the same phase,
@@ -217,7 +262,7 @@ okstra render-bundle \
217
262
 
218
263
  The python function underneath is mutex-protected (`~/.okstra/.locks/<task-key>.lock`), writes `run-context-*.json` + `run-inputs-*.json` + all manifests + discovery files, and registers the run in `~/.okstra/recent.jsonl` with status `prepared`.
219
264
 
220
- 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.
265
+ You can delete the literal state-file path after this point — its job is done. Invoke `command rm` with the literal path (e.g. `command rm /var/folders/.../okstra-wizard.AbCd.json`), not a shell variable. `command` is what keeps a `rm='rm -i'` alias from turning this into a confirmation prompt nobody is there to answer.
221
266
 
222
267
  <!-- BEGIN FRAGMENT: host-orchestration-implementation -->
223
268
  ## Host orchestration rules — implementation
@@ -147,26 +147,36 @@ Once the loop has covered every open item, **before** calling `write`, **echo ba
147
147
  - If the user changes any item, show the echo-back again with the changed value and re-confirm.
148
148
  - Every `value` must be what the user picked or typed, never a wording you settled on for them — this gate guarantees that.
149
149
 
150
- ## Step 5: (Optional) Record approval
150
+ ## Step 5: (Optional) Record the plan decision
151
151
 
152
- Record approval alongside only when the report's approval-blocking clarification items are **all filled with an answer** and the user explicitly said "approve with this option":
152
+ Record a decision alongside the answers only when the user stated one outright. Never infer it from the tone of the conversation.
153
+
154
+ Approval also requires that the report's approval-blocking clarification items are **all filled with an answer**:
153
155
 
154
156
  ```
155
- --approval '{"approved":true,"implementationOption":"<the option the user chose>"}'
157
+ --plan-decision '{"status":"approved","implementationOption":"<the option the user chose>"}'
156
158
  ```
157
159
 
158
160
  If any approval-target item is not filled with an answer, or is a reframe/hold, do not approve — tell the user the gate is still open.
159
161
 
162
+ A user who turns the plan down gets the same treatment, and the reason is mandatory — the next planning run reads it to know what to change:
163
+
164
+ ```
165
+ --plan-decision '{"status":"rejected","reason":"<the user's own words>"}'
166
+ ```
167
+
168
+ Use `revision-requested` instead of `rejected` when the user wants the same plan reworked rather than dropped. Either way, quote the user's reason; do not summarise it into your own.
169
+
160
170
  ## Step 6: Write the sidecar
161
171
 
162
172
  Run only after the user has confirmed the echo-back with "confirmed":
163
173
 
164
174
  ```bash
165
- okstra user-response write --report <reportPath> --answers '<answers-json>' --approval '<approval-json>' --task-key <taskKey>
175
+ okstra user-response write --report <reportPath> --answers '<answers-json>' --plan-decision '<plan-decision-json>' --task-key <taskKey>
166
176
  ```
167
177
 
168
178
  - `--answers` = the JSON array of `{id, kind, value, rationale?, disposition}` objects collected in Step 3.
169
- - `--approval` is attached only when recording approval in Step 5 (otherwise omit it).
179
+ - `--plan-decision` is attached only when recording a decision in Step 5 (otherwise omit it).
170
180
  - `--task-key` is the value carried forward from Step 1/2.
171
181
  - Report the path from the returned JSON `{sidecar: <path>}` to the user. (`write` merges items when a sidecar already exists — the same `id` is overwritten with the new value.)
172
182
 
@@ -31,7 +31,7 @@ When `**Evidence ledger:** required-v1` is present, append one canonical row to
31
31
 
32
32
  - Evidence read: `<project-relative path without a line suffix>`
33
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.
34
+ Every file citation in the result MUST use backticks, a line suffix, and the same project-relative path its ledger row carries — for example `src/config/env.ts:1-22`. A bare filename (`env.ts:1-22`) does not match its row and fails exactly like a file you never opened, however many times you cited the full path earlier. 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
35
 
36
36
  ## Anchor headers
37
37
 
@@ -25,6 +25,7 @@ Write `- PROGRESS: <stage> <ISO-8601-UTC>` there before reading and at least eve
25
25
  - Follow the task-type schema excerpt and Phase 6 report-writer contract. Do not perform independent analysis, edit source code, or load implementation coding-preflight resources.
26
26
  - Invoke `okstra render-final-report <Result Path>` after writing data.json and verify the markdown sibling exists before returning.
27
27
  - Preserve source item IDs, convergence classifications, round history, and unresolved dissent; do not recompute them from intuition.
28
+ - Every `clarificationItems[]` row you leave `status: open` with `blocks: approval` carries `origin` and `userConfirmation`. `origin` is `worker-finding` only when an analyser or verifier reached it on its own evidence — when the lead's dispatch prompt told you to raise it, it is `lead-directed`, whatever the workers then agreed. `userConfirmation` is what the lead reports having done about it (`asked-and-answered` / `asked-awaiting` / `deferred-no-interactive-session`); if the dispatch does not say, write `asked-awaiting` and name the gap in your return message rather than guessing. An open approval blocker stops the whole task, so a row that misreports where it came from costs a re-run. **Enforced:** `validators/validate-run.py` `_validate_open_approval_blocker_provenance`.
28
29
 
29
30
  ## Anchor headers
30
31
 
@@ -380,10 +380,10 @@ Carried-forward plan items retain their prior verdicts verbatim; each such item
380
380
 
381
381
  ### Stepwise Execution Order
382
382
 
383
- | step | action | files | command | expected |
384
- |------|--------|-------|---------|----------|
383
+ | step | action | files | command | outcome | expected |
384
+ |------|--------|-------|---------|---------|----------|
385
385
  {% for row in stage.stepwiseExecution -%}
386
- | {{ row.step | mdcell }} | {{ row.action | mdcell }} | `{{ row.files | mdcell }}` | `{{ row.command | mdcell }}` | {{ row.expected | mdcell }} |
386
+ | {{ row.step | mdcell }} | {{ row.action | mdcell }} | `{{ row.files | mdcell }}` | `{{ row.command | mdcell }}` | {{ row.outcome | mdcell }} | {{ row.expectedDetail | mdcell }} |
387
387
  {% endfor %}
388
388
 
389
389
  ### Stage Exit Contract
@@ -77,8 +77,9 @@ th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-m
77
77
  .ledger-key { display: flex; flex-wrap: wrap; gap: .6rem; font-weight: 600; }
78
78
  .ledger-kind { font-weight: 400; color: GrayText; }
79
79
  .ledger-text { max-width: 78ch; }
80
- .ledger-source { font-size: .9rem; color: GrayText; }
81
- .ledger-source > span:first-child::after { content: ":"; }
80
+ .ledger-source, .ledger-confidence { font-size: .9rem; color: GrayText; }
81
+ .ledger-source > span:first-child::after,
82
+ .ledger-confidence > span:first-child::after { content: ":"; }
82
83
  .status { display: inline-block; white-space: nowrap; border-radius: 999px; padding: .1rem .5rem; background: color-mix(in srgb, GrayText 15%, Canvas); }
83
84
  .status-gap, .status-risk, .status-error, .status-timeout { background: color-mix(in srgb, #d94b4b 18%, Canvas); }
84
85
  /* Figures are read by comparing them down the column, which only works when
@@ -86,13 +87,16 @@ th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-m
86
87
  the right edge is the one they share. */
87
88
  .run-usage-lede { max-width: 78ch; }
88
89
  .figure { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
89
- /* Four nowrap number columns need almost none of the width, and the default
90
+ /* Five nowrap number columns need almost none of the width, and the default
90
91
  even split spent it on them while the agent names wrapped mid-word. */
91
- [data-report-section="run-usage"] .row-key { width: 45%; }
92
+ [data-report-section="run-usage"] .row-key { width: 40%; }
92
93
  .cli-extra { display: block; color: GrayText; font-size: .85em; }
93
94
  tfoot th { font-weight: 600; }
94
95
  tfoot tr:first-child > * { border-top: 2px solid color-mix(in srgb, CanvasText 30%, transparent); }
95
96
  tfoot .grand-total > * { font-weight: 700; }
97
+ /* The task total counts runs this report is not about, so it is ruled off from
98
+ the run's own figures rather than reading as one more line of them. */
99
+ tfoot .task-cumulative > * { border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent); color: GrayText; }
96
100
  .human-report-footer { width: min(1120px, calc(100% - 2rem)); margin: 0 auto 2rem; display: flex; flex-wrap: wrap; gap: .6rem; }
97
101
  .human-report-footer pre { flex-basis: 100%; white-space: pre-wrap; max-height: 14em; overflow: auto; }
98
102
  fieldset, label { display: block; margin: .7rem 0; }
@@ -31,9 +31,11 @@
31
31
  <ol class="ledger">
32
32
  {% for row_id, row in evidenceIndex.items() %}
33
33
  <li class="ledger-item" id="id-{{ row_id }}">
34
- <p class="ledger-key"><span class="ledger-id">{{ row_id }}</span><span class="ledger-kind">{{ row.kind }}</span></p>
34
+ <p class="ledger-key"><span class="ledger-id">{{ row_id }}</span><span class="ledger-kind">{{ row.kind | enum_label('ledgerKind') }}</span></p>
35
35
  <p class="ledger-text">{{ row.text | inline_code }}</p>
36
- <p class="ledger-source"><span>{{ t('base.source-confidence') }}</span> {% if row.codeEvidence %}{{ row.codeEvidence | code_evidence }}{% else %}{{ row.source | inline_code }}{% endif %}</p>
36
+ {% if row.codeEvidence %}<p class="ledger-source"><span>{{ t('base.source') }}</span> {{ row.codeEvidence | code_evidence }}</p>
37
+ {% elif row.source %}<p class="ledger-source"><span>{{ t('base.source') }}</span> {{ row.source | inline_code }}</p>{% endif %}
38
+ {% if row.confidence %}<p class="ledger-confidence"><span>{{ t('base.confidence') }}</span> {{ row.confidence | inline_code }}</p>{% endif %}
37
39
  </li>
38
40
  {% endfor %}
39
41
  </ol>
@@ -47,6 +49,7 @@
47
49
  <thead><tr>
48
50
  <th scope="col">{{ t('runUsage.agent') }}</th>
49
51
  <th scope="col" class="figure">{{ t('runUsage.raw-tokens') }}</th>
52
+ <th scope="col" class="figure">{{ t('runUsage.cache-read-tokens') }}</th>
50
53
  <th scope="col" class="figure">{{ t('runUsage.billable-tokens') }}</th>
51
54
  <th scope="col" class="figure">{{ t('runUsage.cost') }}</th>
52
55
  <th scope="col" class="figure">{{ t('runUsage.duration') }}</th>
@@ -56,6 +59,7 @@
56
59
  <tr>
57
60
  {{ row_key(pairs=[(t('runUsage.agent'), row.agent), (t('runUsage.role'), row.role), (t('runUsage.model'), row.model)], status_name=t('runUsage.status'), status_raw=row.status, status_text=row.status | enum_label('workerStatus')) }}
58
61
  <td class="figure">{{ row.rawTokens }}{% if row.cliTokens %}<span class="cli-extra">{{ t('runUsage.cli') }} {{ row.cliTokens }}</span>{% endif %}</td>
62
+ <td class="figure">{{ row.cacheReadTokens }}</td>
59
63
  <td class="figure">{{ row.billableTokens }}</td>
60
64
  <td class="figure">{{ row.cost }}{% if row.cliCost %}<span class="cli-extra">+ {{ t('runUsage.cli') }} {{ row.cliCost }}</span>{% endif %}</td>
61
65
  <td class="figure">{{ row.duration }}</td>
@@ -65,6 +69,7 @@
65
69
  <tr>
66
70
  <td class="row-key">{{ t('runUsage.unaccounted') }}</td>
67
71
  <td class="figure">{{ runUsage.unaccounted.rawTokens }}</td>
72
+ <td class="figure">{{ runUsage.unaccounted.cacheReadTokens }}</td>
68
73
  <td class="figure">{{ runUsage.unaccounted.billableTokens }}</td>
69
74
  <td class="figure">{{ runUsage.unaccounted.cost }}</td>
70
75
  <td class="figure"></td>
@@ -72,10 +77,11 @@
72
77
  {% endif %}
73
78
  </tbody>
74
79
  <tfoot>
75
- <tr><th scope="row">{{ t('runUsage.row-lead') }}</th><td class="figure">{{ runUsage.lead.rawTokens }}</td><td class="figure">{{ runUsage.lead.billableTokens }}</td><td class="figure">{{ runUsage.lead.cost }}</td><td class="figure"></td></tr>
76
- <tr><th scope="row">{{ t('runUsage.row-workers') }}</th><td class="figure">{{ runUsage.worker.rawTokens }}</td><td class="figure">{{ runUsage.worker.billableTokens }}</td><td class="figure">{{ runUsage.worker.cost }}</td><td class="figure"></td></tr>
77
- <tr class="grand-total"><th scope="row">{{ t('runUsage.row-total') }}</th><td class="figure">{{ runUsage.grand.rawTokens }}</td><td class="figure">{{ runUsage.grand.billableTokens }}</td><td class="figure">{{ runUsage.grand.cost }}</td><td class="figure"></td></tr>
78
- {% if runUsage.cliCost %}<tr><th scope="row">{{ t('runUsage.row-cli') }}</th><td class="figure"></td><td class="figure"></td><td class="figure">{{ runUsage.cliCost }}</td><td class="figure"></td></tr>{% endif %}
80
+ <tr><th scope="row">{{ t('runUsage.row-lead') }}</th><td class="figure">{{ runUsage.lead.rawTokens }}</td><td class="figure">{{ runUsage.lead.cacheReadTokens }}</td><td class="figure">{{ runUsage.lead.billableTokens }}</td><td class="figure">{{ runUsage.lead.cost }}</td><td class="figure"></td></tr>
81
+ <tr><th scope="row">{{ t('runUsage.row-workers') }}</th><td class="figure">{{ runUsage.worker.rawTokens }}</td><td class="figure">{{ runUsage.worker.cacheReadTokens }}</td><td class="figure">{{ runUsage.worker.billableTokens }}</td><td class="figure">{{ runUsage.worker.cost }}</td><td class="figure"></td></tr>
82
+ <tr class="grand-total"><th scope="row">{{ t('runUsage.row-total') }}</th><td class="figure">{{ runUsage.grand.rawTokens }}</td><td class="figure">{{ runUsage.grand.cacheReadTokens }}</td><td class="figure">{{ runUsage.grand.billableTokens }}</td><td class="figure">{{ runUsage.grand.cost }}</td><td class="figure"></td></tr>
83
+ {% if runUsage.cliCost %}<tr><th scope="row">{{ t('runUsage.row-cli') }}</th><td class="figure"></td><td class="figure"></td><td class="figure"></td><td class="figure">{{ runUsage.cliCost }}</td><td class="figure"></td></tr>{% endif %}
84
+ {% if runUsage.taskCumulative %}<tr class="task-cumulative"><th scope="row">{{ t('runUsage.row-task-cumulative') | replace('{runs}', runUsage.taskCumulative.runCount) }}</th><td class="figure">{{ runUsage.taskCumulative.rawTokens }}</td><td class="figure">{{ runUsage.taskCumulative.cacheReadTokens }}</td><td class="figure">{{ runUsage.taskCumulative.billableTokens }}</td><td class="figure">{{ runUsage.taskCumulative.cost }}</td><td class="figure"></td></tr>{% endif %}
79
85
  </tfoot>
80
86
  </table>
81
87
  </section>
@@ -67,6 +67,21 @@
67
67
  "timeout": "Timed out",
68
68
  "not-run": "Not run",
69
69
  "synthesis-only": "Synthesis only"
70
+ },
71
+ "replanMode": {
72
+ "full": "Replanned in full",
73
+ "incremental": "Partly replanned"
74
+ },
75
+ "ledgerKind": {
76
+ "code-evidence": "Code evidence",
77
+ "hypothesis": "Hypothesis",
78
+ "confirmed-fact": "Confirmed fact",
79
+ "inference": "Inference",
80
+ "unknown": "Unknown",
81
+ "cross-check-consensus": "Agreed across workers",
82
+ "cross-check-dissent": "Workers disagreed",
83
+ "missing-information": "Missing information",
84
+ "follow-up": "Follow-up"
70
85
  }
71
86
  },
72
87
  "enumHint": {
@@ -85,7 +100,8 @@
85
100
  "written": "Written",
86
101
  "elapsed": "Elapsed",
87
102
  "evidence-ledger": "Evidence ledger",
88
- "source-confidence": "Source · confidence",
103
+ "source": "Source",
104
+ "confidence": "Confidence",
89
105
  "export-my-answers": "Export my answers",
90
106
  "copy": "Copy",
91
107
  "dismiss": "Dismiss",
@@ -95,7 +111,7 @@
95
111
  },
96
112
  "runUsage": {
97
113
  "heading": "What this run cost",
98
- "intro": "Every agent this run dispatched, with what it spent and how long it was working. Agents run alongside each other, so their durations do not add up to the elapsed time in the header. Raw tokens are the volume processed; billable tokens are that same work restated in input-price units, which is what the cost is computed from.",
114
+ "intro": "What each agent this run put in, and how long it worked. Agents overlap, so their working times do not add up to the elapsed time in the header. Raw tokens are what the run sent through the model once. Cache re-reads are the context read back on every turn — they are billed at a tenth of the input rate, which is why they sit outside the raw figure but inside the money. Billable tokens restate the whole run in base-input units, and the cost comes from that.",
99
115
  "agent": "Agent",
100
116
  "role": "Role",
101
117
  "model": "Model",
@@ -109,7 +125,9 @@
109
125
  "row-lead": "Lead",
110
126
  "row-workers": "Workers",
111
127
  "row-total": "Total",
112
- "row-cli": "CLI calls (billed separately)"
128
+ "row-cli": "CLI calls (billed separately)",
129
+ "cache-read-tokens": "Cache re-reads",
130
+ "row-task-cumulative": "This task so far ({runs} runs)"
113
131
  },
114
132
  "macros": {
115
133
  "forms": {
@@ -122,7 +140,6 @@
122
140
  "why": "Why",
123
141
  "further-evidence": "Further evidence",
124
142
  "scope-change-requested": "Scope change requested",
125
- "approve-this-plan": "Approve this plan",
126
143
  "implementation-option": "Implementation option",
127
144
  "i-approve-this-plan": "I approve this plan.",
128
145
  "approval-is-held": "Approval is held:",
@@ -136,7 +153,12 @@
136
153
  "recommended": "Recommended",
137
154
  "scope-impact": "Scope",
138
155
  "added-work": "Added work",
139
- "direction-change": "Direction change"
156
+ "direction-change": "Direction change",
157
+ "decide-on-this-plan": "Decide on this plan",
158
+ "request-changes-to-this-plan": "Send it back for changes",
159
+ "reject-this-plan": "Reject this plan",
160
+ "why-required-for-changes-or-rejection": "Why — required when you send it back or reject it",
161
+ "answer-the-blockers-then-regenerate": "Answer {ids}, export your answers, then regenerate the report with the clarification resume command."
140
162
  },
141
163
  "visualizations": {
142
164
  "component": "Component",
@@ -285,7 +307,8 @@
285
307
  "statement-overturned": "Statement overturned",
286
308
  "replaced-with": "Replaced with",
287
309
  "no-answer-overturned-anything": "No answer overturned anything.",
288
- "scope-restated": "Scope, restated",
310
+ "scope-restated": "How much of the plan was rewritten",
311
+ "scope-restated-lede": "This planning run started from an earlier plan. This is how much of it survived your answers.",
289
312
  "stages-verified-again": "Stages verified again",
290
313
  "stages-carried-over-as-they-were": "Stages carried over as they were",
291
314
  "decision-record-draft": "Decision record draft",
@@ -297,7 +320,9 @@
297
320
  "topic": "Topic",
298
321
  "why": "Why",
299
322
  "open-decisions-before-approval": "Open decisions before approval",
300
- "no-open-decision-is-holding-approval": "No open decision is holding approval."
323
+ "no-open-decision-is-holding-approval": "No open decision is holding approval.",
324
+ "uniform-verifier-label": "Uniform verifier",
325
+ "uniform-verifier-legend": "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."
301
326
  },
302
327
  "implementation": {
303
328
  "what-was-delivered": "What was delivered",