okstra 0.147.0 → 0.148.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 (116) hide show
  1. package/README.md +21 -7
  2. package/docs/architecture/storage-model.md +34 -61
  3. package/docs/architecture.md +51 -49
  4. package/docs/cli.md +38 -21
  5. package/docs/for-ai/skills/okstra-run.md +13 -34
  6. package/docs/performance-improvement-plan-v2.md +2 -2
  7. package/docs/pr-template-usage.md +1 -1
  8. package/docs/project-structure-overview.md +10 -8
  9. package/docs/task-process/README.md +4 -4
  10. package/docs/task-process/common-flow.md +12 -12
  11. package/docs/task-process/final-verification.md +2 -2
  12. package/docs/task-process/implementation.md +1 -1
  13. package/docs/task-process/release-handoff.md +1 -1
  14. package/package.json +2 -2
  15. package/runtime/BUILD.json +2 -2
  16. package/runtime/agents/workers/antigravity-worker.md +2 -2
  17. package/runtime/agents/workers/claude-worker.md +1 -1
  18. package/runtime/agents/workers/codex-worker.md +2 -2
  19. package/runtime/agents/workers/grok-worker.md +256 -0
  20. package/runtime/agents/workers/kimi-worker.md +256 -0
  21. package/runtime/agents/workers/report-writer-worker.md +2 -2
  22. package/runtime/bin/lib/okstra/cli.sh +13 -1
  23. package/runtime/bin/lib/okstra/globals.sh +3 -0
  24. package/runtime/bin/lib/okstra/usage.sh +17 -12
  25. package/runtime/bin/okstra-grok-exec.sh +5 -0
  26. package/runtime/bin/okstra-kimi-exec.sh +5 -0
  27. package/runtime/bin/okstra-provider-exec.py +235 -0
  28. package/runtime/bin/okstra.sh +3 -0
  29. package/runtime/prompts/lead/adapters/antigravity.md +48 -0
  30. package/runtime/prompts/lead/adapters/claude-code.md +13 -11
  31. package/runtime/prompts/lead/adapters/codex.md +7 -7
  32. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  33. package/runtime/prompts/lead/report-writer.md +1 -1
  34. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  35. package/runtime/prompts/profiles/_common-contract.md +4 -4
  36. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  37. package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
  38. package/runtime/prompts/profiles/_implementation-executor.md +12 -12
  39. package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
  40. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  41. package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
  42. package/runtime/prompts/profiles/error-analysis.md +2 -0
  43. package/runtime/prompts/profiles/feature-analysis.md +2 -0
  44. package/runtime/prompts/profiles/final-verification.md +3 -1
  45. package/runtime/prompts/profiles/forbidden-actions.json +4 -4
  46. package/runtime/prompts/profiles/implementation-planning.md +3 -1
  47. package/runtime/prompts/profiles/implementation.md +2 -2
  48. package/runtime/prompts/profiles/improvement-discovery.md +3 -1
  49. package/runtime/prompts/profiles/project-analysis.md +2 -0
  50. package/runtime/prompts/profiles/release-handoff.md +7 -7
  51. package/runtime/prompts/profiles/requirements-discovery.md +2 -0
  52. package/runtime/prompts/wizard/prompts.ko.json +9 -1
  53. package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
  54. package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
  55. package/runtime/python/okstra_ctl/lead_events.py +1 -1
  56. package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
  57. package/runtime/python/okstra_ctl/models.py +156 -8
  58. package/runtime/python/okstra_ctl/path_hints.py +9 -25
  59. package/runtime/python/okstra_ctl/paths.py +1 -1
  60. package/runtime/python/okstra_ctl/render.py +172 -74
  61. package/runtime/python/okstra_ctl/report_html/common.py +38 -2
  62. package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
  63. package/runtime/python/okstra_ctl/report_html/render.py +7 -0
  64. package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +2 -1
  65. package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +2 -1
  66. package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +2 -1
  67. package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -1
  68. package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +2 -1
  69. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -1
  70. package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +2 -1
  71. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +2 -1
  72. package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +2 -1
  73. package/runtime/python/okstra_ctl/report_html/visualizations.py +32 -6
  74. package/runtime/python/okstra_ctl/run.py +264 -45
  75. package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
  76. package/runtime/python/okstra_ctl/team.py +2 -7
  77. package/runtime/python/okstra_ctl/wizard.py +194 -21
  78. package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
  79. package/runtime/python/okstra_ctl/workers.py +3 -1
  80. package/runtime/python/okstra_ctl/workflow.py +4 -2
  81. package/runtime/python/okstra_token_usage/__init__.py +1 -0
  82. package/runtime/python/okstra_token_usage/collect.py +32 -23
  83. package/runtime/python/okstra_token_usage/pricing.py +35 -3
  84. package/runtime/schemas/final-report-v2.0.schema.json +2 -2
  85. package/runtime/skills/okstra-run/SKILL.md +31 -42
  86. package/runtime/templates/prd/pr-body.template.md +1 -1
  87. package/runtime/templates/reports/html/assets/base.css +6 -3
  88. package/runtime/templates/reports/html/base.template.html +22 -8
  89. package/runtime/templates/reports/html/macros/forms.html +2 -2
  90. package/runtime/templates/reports/html/macros/layout.html +5 -5
  91. package/runtime/templates/reports/html/macros/visualizations.html +11 -1
  92. package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +11 -11
  93. package/runtime/templates/reports/html/tasks/error-analysis.template.html +12 -12
  94. package/runtime/templates/reports/html/tasks/feature-analysis.template.html +9 -9
  95. package/runtime/templates/reports/html/tasks/final-verification.template.html +7 -7
  96. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +12 -12
  97. package/runtime/templates/reports/html/tasks/implementation.template.html +7 -7
  98. package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +6 -6
  99. package/runtime/templates/reports/html/tasks/project-analysis.template.html +11 -11
  100. package/runtime/templates/reports/html/tasks/release-handoff.template.html +5 -5
  101. package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +8 -8
  102. package/runtime/templates/reports/report.js +21 -4
  103. package/runtime/templates/reports/settings.template.json +4 -0
  104. package/runtime/templates/reports/task-brief.template.md +7 -7
  105. package/runtime/validators/validate-run.py +11 -6
  106. package/runtime/validators/validate_session_conformance.py +2 -1
  107. package/src/cli-registry.mjs +4 -4
  108. package/src/commands/execute/codex-dispatch.mjs +7 -10
  109. package/src/commands/execute/render-bundle.mjs +3 -3
  110. package/src/commands/execute/run.mjs +17 -52
  111. package/src/commands/execute/wizard.mjs +4 -1
  112. package/src/commands/lifecycle/doctor.mjs +6 -3
  113. package/src/commands/lifecycle/install.mjs +31 -8
  114. package/src/lib/runtime-manifest.mjs +1 -1
  115. package/src/lib/runtime-resolver.mjs +2 -2
  116. package/src/lib/worker-agent-render.mjs +50 -0
@@ -4,18 +4,18 @@ Pricing is matched by substring against the model id recorded in the session
4
4
  transcript, so keys must reflect the *actual* model id form emitted by each
5
5
  provider:
6
6
 
7
- * Anthropic — `claude-fable-5*`, `claude-opus-5*`, `claude-opus-4-*`, `claude-sonnet-4-*`,
7
+ * Anthropic — `claude-fable-5*`, `claude-opus-5*`, `claude-opus-4-*`, `claude-sonnet-5*`, `claude-sonnet-4-*`,
8
8
  `claude-haiku-4-5-*`, `claude-3-5-sonnet-*`, `claude-3-5-haiku-*`,
9
9
  `claude-3-opus-*`, `claude-3-haiku-*`.
10
10
  * OpenAI / Codex — `gpt-5*`, `gpt-4o*`, `gpt-4*`.
11
- * Google / Gemini — `gemini-3.5-flash*`, `gemini-3.1-pro*`, `gemini-3-flash*`,
11
+ * Google / Gemini — `gemini-3.6-flash*`, `gemini-3.5-flash*`, `gemini-3.1-pro*`, `gemini-3-flash*`,
12
12
  `gemini-2.5-pro*`, `gemini-2.5-flash*`, `gemini-2.0-flash*`.
13
13
 
14
14
  Matching prefers the longest key that is a substring of the model id (most
15
15
  specific wins), so table ordering does not affect the result. Update when
16
16
  providers change list pricing.
17
17
 
18
- Sources (last verified 2026-05-17, public list prices, USD per 1M tokens):
18
+ Sources (last verified 2026-08-03, public list prices, USD per 1M tokens):
19
19
  * Anthropic: https://www.anthropic.com/pricing
20
20
  * OpenAI: https://openai.com/api/pricing
21
21
  * Google: https://ai.google.dev/gemini-api/docs/pricing
@@ -55,6 +55,9 @@ CLAUDE_PRICING = {
55
55
  # Claude Opus 5.
56
56
  "opus-5": (5.0, 6.25, 0.50, 25.0), # Opus 5 (cache prices derived from ratios)
57
57
 
58
+ # Claude Sonnet 5.
59
+ "sonnet-5": (3.0, 3.75, 0.30, 15.0), # Sonnet 5 (cache prices derived from ratios)
60
+
58
61
  # Claude 4 point releases (explicit so future divergence is easy to see).
59
62
  "opus-4-8": (5.0, 6.25, 0.50, 25.0), # Opus 4.8 (cache prices derived from ratios)
60
63
  "opus-4-7": (5.0, 6.25, 0.50, 25.0), # Opus 4.7 (cache prices derived from ratios)
@@ -107,6 +110,8 @@ _LEGACY_CODEX_PRICING = {
107
110
  # catalog + legacy: catalog owns selectable prices, legacy covers the rest. No
108
111
  # key overlaps today; if one ever collided, the legacy (right) side would win.
109
112
  CODEX_PRICING = {**catalog_pricing("codex"), **_LEGACY_CODEX_PRICING}
113
+ GROK_PRICING = catalog_pricing("grok")
114
+ KIMI_PRICING = catalog_pricing("kimi")
110
115
 
111
116
  GEMINI_PRICING = {
112
117
  # model substring -> (input USD/1M, output USD/1M).
@@ -121,6 +126,8 @@ GEMINI_PRICING = {
121
126
  # forms appear in the wild, so include both for the new 3.x families.
122
127
 
123
128
  # Gemini 3 series.
129
+ "3.6-flash": (1.50, 7.50),
130
+ "3-6-flash": (1.50, 7.50),
124
131
  "3.5-flash": (1.50, 9.00),
125
132
  "3-5-flash": (1.50, 9.00),
126
133
  "3.1-pro": (2.00, 12.0),
@@ -213,6 +220,31 @@ def codex_cost_usd(model: str | None, input_t: int, cached_input_t: int, output_
213
220
  return round((fresh * pi + cached_input_t * pci + output_t * po) / 1_000_000, 4)
214
221
 
215
222
 
223
+ def provider_cost_usd(
224
+ provider: str,
225
+ model: str | None,
226
+ input_t: int,
227
+ cached_input_t: int,
228
+ output_t: int,
229
+ ) -> float | None:
230
+ """Price a three-rate provider without fabricating an unknown model rate."""
231
+ tables = {
232
+ "codex": CODEX_PRICING,
233
+ "grok": GROK_PRICING,
234
+ "kimi": KIMI_PRICING,
235
+ }
236
+ pricing = _match_pricing(model, tables.get(provider, {}))
237
+ if pricing is None:
238
+ return None
239
+ input_price, cached_price, output_price = pricing
240
+ fresh_input = max(0, input_t - cached_input_t)
241
+ return round((
242
+ fresh_input * input_price
243
+ + cached_input_t * cached_price
244
+ + output_t * output_price
245
+ ) / 1_000_000, 4)
246
+
247
+
216
248
  def antigravity_cost_usd(model: str | None, input_t: int, output_t: int) -> float | None:
217
249
  p = _match_pricing(model, GEMINI_PRICING)
218
250
  if p is None:
@@ -111,9 +111,9 @@
111
111
  "taskKey": { "type": "string", "minLength": 1 },
112
112
  "createdAt": { "type": "string", "minLength": 1 },
113
113
  "taskType": { "$ref": "#/$defs/TaskType" },
114
- "reportOwner": { "const": "Claude lead" },
114
+ "reportOwner": { "const": "Okstra lead" },
115
115
  "reportAuthor": {
116
- "enum": ["Report writer worker", "Claude lead"],
116
+ "enum": ["Report writer worker", "Okstra lead"],
117
117
  "description": "Lead-authored fallback is only valid for release-handoff or recorded report-writer dispatch failure."
118
118
  },
119
119
  "leadModel": { "type": "string", "minLength": 1 },
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: okstra-run
3
- description: Use when the user wants to start an okstra task (cross-verification run) directly from the current Claude Code session — without spawning a new claude process. Equivalent in effect to `okstra.sh --task-type ...` but driven through interactive prompts. Trigger words include "okstra run", "okstra start", "start okstra", "begin okstra task", "run okstra in this session", "okstra here", "continue on", "run the next phase", "start okstra in this session".
3
+ description: Use when the user wants to start or continue an okstra cross-verification task directly from the current agent session. Trigger words include "okstra run", "okstra start", "start okstra", "begin okstra task", "run okstra in this session", "okstra here", "continue on", and "run the next phase".
4
4
  ---
5
5
 
6
6
  # OKSTRA Run (in-session)
7
7
 
8
- Launch an okstra task — gather inputs interactively via the **wizard state machine** (`okstra wizard ...`), then take over as `Claude lead` in the current session.
8
+ Launch an okstra task — gather inputs interactively via the **wizard state machine** (`okstra wizard ...`), then take over as `Okstra lead` in the current session using the adapter selected for that host.
9
9
 
10
10
  **Single authority**: this skill drives `okstra wizard`, which owns every step (ordering, branching, validation). The skill is just a thin prompt-relay loop — it never decides "what to ask next" itself. If the flow needs to change, edit `scripts/okstra_ctl/wizard.py`, not this file.
11
11
 
@@ -13,12 +13,12 @@ Launch an okstra task — gather inputs interactively via the **wizard state mac
13
13
 
14
14
  ## When to Use
15
15
 
16
- - The user is inside a Claude Code session and asks to start an okstra task ("run okstra here", "start an error-analysis on this branch", "okstra implementation-planning for INV-1234").
17
- - Continue an existing task (next phase) without leaving the current claude session.
16
+ - The user is inside a supported agent host and asks to start an okstra task ("run okstra here", "start an error-analysis on this branch", "okstra implementation-planning for INV-1234").
17
+ - Continue an existing task (next phase) without leaving the current agent session.
18
18
 
19
19
  ## When NOT to Use
20
20
 
21
- - User explicitly asks to spawn a new terminal / new claude — use `okstra-inspect history.4` (resume command) or instruct them to run `okstra` in another terminal.
21
+ - User explicitly asks to spawn a new terminal / new agent session — use `okstra-inspect history.4` (resume command) or instruct them to run `okstra` in another terminal.
22
22
  - User wants status only — use `okstra-inspect status`.
23
23
  - User wants past runs — use `okstra-inspect history`.
24
24
 
@@ -57,18 +57,27 @@ Never invent additional questions. Never reorder. **Never drop, hide, or merge a
57
57
 
58
58
  ## Step 1: Preflight
59
59
 
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:
61
+
62
+ - Claude Code host → `claude-code`
63
+ - Codex host → `codex`
64
+ - Antigravity CLI host → `antigravity`
65
+ - other host → `external`
66
+
67
+ 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
+
60
69
  Run one Bash tool call (Bash invocation rule from the top of this file applies):
61
70
 
62
71
  ```bash
63
- okstra preflight --runtime claude-code --json
72
+ okstra preflight --runtime <host-runtime> --json
64
73
  ```
65
74
 
66
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=...`.
67
76
 
68
- On `ok: true`, inspect `runtimeReadiness.checks` for the check whose `id` is `workspace-trust`. This is a Claude Code host-runtime check, independent of whether the worker providers are Claude, Codex, or Antigravity.
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.
69
78
 
70
- - `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.
71
- - `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.
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.
72
81
  - `status: accepted` or `status: not-applicable` → carry `projectRoot` and `projectId` as literal strings into Step 2.
73
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.
74
83
  - Any other `workspace-trust` status is unrecognized runtime output: show the status and stop before Step 2 instead of guessing that trust exists.
@@ -89,7 +98,8 @@ Then initialize the wizard with the literal `projectRoot` / `projectId` you pars
89
98
  okstra wizard init \
90
99
  --state-file /var/folders/.../okstra-wizard.AbCd.json \
91
100
  --project-root /abs/path/to/project \
92
- --project-id my-project-id
101
+ --project-id my-project-id \
102
+ --host-runtime <host-runtime>
93
103
  ```
94
104
 
95
105
  Output: the same `{ok, next}` JSON described above. The first `next` is always `step: "task_pick"`.
@@ -101,7 +111,7 @@ Repeat until `next.kind == "done"` (or `"aborted"` — terminal cancel, see "How
101
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.
102
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.
103
113
  - `pick` + `multi: false` → `AskUserQuestion` with `multiSelect: false`, `label`, and `options`. The user's chosen option's `value` is the answer string.
104
- - `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,antigravity"`) and submit it as a single `--answer "claude,codex,antigravity"`. Empty selection submits `--answer ""` and the wizard re-prompts.
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.
105
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>'`.
106
116
  - `text` → plain text message containing `label`. Consume the user's next reply verbatim as the answer string (empty reply = empty string).
107
117
  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 `$(...)`):
@@ -185,38 +195,17 @@ Before rendering the next phase's bundle — and between worker rounds within a
185
195
 
186
196
  Build the `okstra render-bundle` invocation from `outcome.renderArgs`, passing each key as `--<key>` and the value verbatim (including empty strings — they are intentional `use phase default` markers).
187
197
 
198
+ Analysis sidetracks therefore forward wizard-owned entries such as `--analysis-target "<args.analysis-target>"` and `--evidence-inputs "<args.evidence-inputs>"` when those keys are present. These are examples of the generic mapping rule, not a separate hard-coded argument list.
199
+
188
200
  Step 3's empty-answer and escaping rules apply verbatim: every flag whose value is the empty string MUST still be passed explicitly as `--<key> ""` (e.g. `--workers ""`, `--directive ""`) — `render-bundle` distinguishes "flag absent" from "flag present with empty value", and the wizard's intent is always the latter.
189
201
 
190
- The okstra-run skill is the Claude Code execution front door, so it marks the host explicitly with `--runtime claude-code` / `--lead-runtime claude-code`; bare `okstra run` cannot invoke Claude Code `TeamCreate` / `Agent(...)` from a terminal.
202
+ Add `--lead-runtime <host-runtime>` from Step 1. Pass every `outcome.renderArgs` entry exactly once; do not enumerate a fixed provider list in this skill because the wizard and provider registry own those keys. If `renderArgs` ever contains `lead-runtime`, replace it with the Step 1 host value rather than emitting the flag twice.
191
203
 
192
204
  ```bash
193
205
  okstra render-bundle \
194
- --lead-runtime claude-code \
195
- --project-root "<args.project-root>" \
196
- --project-id "<args.project-id>" \
197
- --task-group "<args.task-group>" \
198
- --task-id "<args.task-id>" \
199
- --task-type "<args.task-type>" \
200
- --task-brief "<args.task-brief>" \
201
- --analysis-target "<args.analysis-target>" \
202
- --evidence-inputs "<args.evidence-inputs>" \
203
- --executor "<args.executor>" \
204
- --critic "<args.critic>" \
205
- --approved-plan "<args.approved-plan>" \
206
- --stage "<args.stage>" \
207
- --stages "<args.stages>" \
208
- --base-ref "<args.base-ref>" \
209
- --workers "<args.workers>" \
210
- --directive "<args.directive>" \
211
- --lead-model "<args.lead-model>" \
212
- --claude-model "<args.claude-model>" \
213
- --codex-model "<args.codex-model>" \
214
- --antigravity-model "<args.antigravity-model>" \
215
- --report-writer-model "<args.report-writer-model>" \
216
- --related-tasks "<args.related-tasks>" \
217
- --clarification-response "<args.clarification-response>" \
218
- --pr-template-path "<args.pr-template-path>" \
219
- --fix-cycle "<args.fix-cycle>"
206
+ --lead-runtime <host-runtime> \
207
+ --<first-renderArgs-key> "<first-renderArgs-value>" \
208
+ --<each-remaining-renderArgs-key> "<corresponding-value>"
220
209
  ```
221
210
 
222
211
  `render-bundle` auto-supplies `--workspace-root` and forces `--render-only`. Stdout prints `okstra task root:`, `okstra instruction-set:`, and the full rendered lead prompt. Parse the labelled lines for `TASK_ROOT` and `INSTRUCTION_SET_PATH`. Also watch for an optional `okstra concurrent-run stages:` label line — present only when a concurrent run is detected (see "Concurrent-run detection branch" below).
@@ -265,14 +254,14 @@ If `render-bundle` fails with a `PrepareError` containing `Recorded stage SHAs n
265
254
 
266
255
  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`.
267
256
 
268
- ## Step 6: Take over as Claude lead
257
+ ## Step 6: Take over as Okstra lead
269
258
 
270
- Read `<INSTRUCTION_SET_PATH>/claude-execution-prompt.md` verbatim and enter `Claude lead` mode. The lead prompt now 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.
259
+ 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.
271
260
 
272
261
  Then proceed through the phases exactly as the lead prompt directs (Phase 1 context → Phase 2+ worker dispatch → final synthesis → final report).
273
262
 
274
263
  Inform the user with one short line:
275
- > Took over as Claude lead for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
264
+ > Took over as Okstra lead (`<host-runtime>`) for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
276
265
 
277
266
  ## Step 7: implementation unattended chaining (chain-stages)
278
267
 
@@ -281,7 +270,7 @@ When `task-type == implementation` and Step 5 render-args' `chain-stages` CSV ha
281
270
  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:
282
271
 
283
272
  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`.
284
- 2. As in Step 6, become Claude 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).
273
+ 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).
285
274
  3. After confirming that `done` row was written, reclaim the completed teammate panes of the stage you just finished: run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<the run dir of the stage you just completed>"` (add `--keep report-writer-worker` if the report writer is still in flight). Then move to the next stage.
286
275
  4. One-line report at each stage start/finish: `stage N/<total> start` / `stage N done → next K`.
287
276
 
@@ -9,7 +9,7 @@ This file is used when there is no custom PR template. Priority:
9
9
 
10
10
  To use your own templates in project or global settings, add the `prTemplatePath` key to one of the paths above. (Absolute path or relative path based on `project_root`)
11
11
 
12
- The placeholder is filled in directly by the Claude lead for the release-handoff based on the following inputs:
12
+ The placeholder is filled in directly by the Okstra lead for the release-handoff based on the following inputs:
13
13
  - The intent and scope of the run brief
14
14
  - The grounds for the verdict in the referenced final-verification report
15
15
  - The commit range from `git log --oneline <base>..HEAD`
@@ -5,7 +5,8 @@ body { margin: 0; background: Canvas; color: CanvasText; }
5
5
  .skip-link:focus { left: 1rem; top: 1rem; z-index: 20; background: Canvas; padding: .6rem; }
6
6
  .human-report-header, main, .audit { width: min(1120px, calc(100% - 2rem)); margin-inline: auto; }
7
7
  .human-report-header { padding: 3rem 0 1.5rem; }
8
- .human-report-header h1 { max-width: 22ch; font-size: clamp(2rem, 6vw, 4rem); line-height: 1.05; margin: .25rem 0 1rem; }
8
+ .human-report-header h1 { max-width: 30ch; font-size: clamp(1.5rem, 3vw, 2.25rem); line-height: 1.15; margin: .25rem 0 .75rem; }
9
+ .lede { max-width: 70ch; font-size: clamp(1.05rem, 2vw, 1.35rem); line-height: 1.4; margin: 0 0 1rem; }
9
10
  .eyebrow { text-transform: uppercase; letter-spacing: .08em; font-size: .78rem; color: GrayText; }
10
11
  main { display: grid; gap: 1rem; padding-bottom: 3rem; }
11
12
  section { padding: 1.4rem; border: 1px solid color-mix(in srgb, CanvasText 14%, transparent); border-radius: 16px; background: color-mix(in srgb, Canvas 94%, CanvasText 6%); }
@@ -19,8 +20,9 @@ h3 { margin-bottom: .35rem; }
19
20
  figure { margin: 1rem 0 0; }
20
21
  figcaption { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem; margin-bottom: .7rem; }
21
22
  .visualization { overflow-x: auto; border-radius: 12px; background: color-mix(in srgb, CanvasText 4%, Canvas); }
22
- svg { width: 100%; min-width: 520px; height: auto; }
23
+ svg { max-width: 100%; min-width: 520px; height: auto; }
23
24
  svg .edge { stroke: GrayText; stroke-width: 2; }
25
+ svg .edge-arrow-head { fill: GrayText; }
24
26
  svg .node rect { fill: color-mix(in srgb, Highlight 14%, Canvas); stroke: Highlight; }
25
27
  svg .node text { fill: CanvasText; font-size: 13px; }
26
28
  table { width: 100%; border-collapse: collapse; margin-top: 1rem; font-size: .9rem; }
@@ -31,8 +33,9 @@ th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-m
31
33
  .audit { margin-bottom: 3rem; padding: 1rem; border: 1px solid GrayText; border-radius: 12px; }
32
34
  .audit pre { overflow: auto; white-space: pre-wrap; }
33
35
  .human-report-footer { width: min(1120px, calc(100% - 2rem)); margin: 0 auto 2rem; display: flex; flex-wrap: wrap; gap: .6rem; }
34
- .human-report-footer pre { flex-basis: 100%; white-space: pre-wrap; }
36
+ .human-report-footer pre { flex-basis: 100%; white-space: pre-wrap; max-height: 14em; overflow: auto; }
35
37
  fieldset, label { display: block; margin: .7rem 0; }
36
38
  textarea, select { width: 100%; max-width: 60rem; padding: .55rem; font: inherit; }
37
39
  @media (max-width: 640px) { section { padding: 1rem; } .visualization { display: none; } }
38
40
  @media print { .skip-link, .audit, script { display: none !important; } body { color: #000; background: #fff; } section { break-inside: avoid; border-color: #bbb; } .visualization-fallback { display: table; } }
41
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; padding: .1em .3em; border-radius: 4px; background: color-mix(in srgb, CanvasText 8%, Canvas); }
@@ -4,45 +4,59 @@
4
4
  <head>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="width=device-width,initial-scale=1">
7
- <title>{{ humanSummary.headline }}</title>
7
+ <title>{{ (runMeta.task_key.split(':') | last)[:16] }} · {{ taskType }} #{{ runMeta.seq }}</title>
8
8
  <style>{{ css | safe }}</style>
9
9
  </head>
10
10
  <body>
11
11
  <a class="skip-link" href="#main-content">Skip to report content</a>
12
12
  <header class="human-report-header">
13
13
  <p class="eyebrow">{{ taskType }}</p>
14
- <h1>{{ humanSummary.headline }}</h1>
15
- <p>{{ humanSummary.outcome }}</p>
14
+ <h1>{{ taskType }} #{{ runMeta.seq }}</h1>
15
+ <p class="lede">{{ humanSummary.headline | inline_code }}</p>
16
+ <p>{{ humanSummary.outcome | inline_code }}</p>
16
17
  <button type="button" data-action="export-user-response">사용자 응답 내보내기</button>
17
18
  </header>
18
19
  <main id="main-content" data-report-role="human-main">
19
20
  {% block human_content %}{% endblock %}
20
21
  {{ clarification_responses(clarificationItems) }}
22
+ {% if evidenceIndex %}
23
+ <section data-report-section="evidence-ledger">
24
+ <h2>근거 대장</h2>
25
+ <table>
26
+ <thead><tr><th>ID</th><th>내용</th><th>출처 · 신뢰도</th></tr></thead>
27
+ <tbody>
28
+ {% for row_id, row in evidenceIndex | dictsort %}
29
+ <tr id="ev-{{ row_id }}"><td>{{ row_id }}</td><td>{{ row.text | inline_code }}</td><td>{% if row.codeEvidence %}{{ row.codeEvidence | code_evidence }}{% else %}{{ row.source | inline_code }}{% endif %}</td></tr>
30
+ {% endfor %}
31
+ </tbody>
32
+ </table>
33
+ </section>
34
+ {% endif %}
21
35
  </main>
22
36
  <details id="audit-details" class="audit" data-report-role="audit">
23
37
  <summary>감사 정보</summary>
24
38
  <section data-audit-field="crossVerification">
25
39
  <h2>Cross Verification</h2>
26
- <pre>{{ audit.crossVerification | tojson(indent=2) }}</pre>
40
+ <pre>{{ audit.crossVerification | audit_json }}</pre>
27
41
  </section>
28
42
  <section data-audit-field="executionStatus">
29
43
  <h2>Execution Status by Agent</h2>
30
- <pre>{{ audit.executionStatus | tojson(indent=2) }}</pre>
44
+ <pre>{{ audit.executionStatus | audit_json }}</pre>
31
45
  </section>
32
46
  <section data-audit-field="tokenUsage">
33
47
  <h2>Token Usage</h2>
34
- <pre>{{ audit.tokenUsage | tojson(indent=2) }}</pre>
48
+ <pre>{{ audit.tokenUsage | audit_json }}</pre>
35
49
  </section>
36
50
  {% if audit.planBodyVerification is defined %}
37
51
  <section data-audit-field="planBodyVerification">
38
52
  <h2>Plan Body Verification</h2>
39
- <pre>{{ audit.planBodyVerification | tojson(indent=2) }}</pre>
53
+ <pre>{{ audit.planBodyVerification | audit_json }}</pre>
40
54
  </section>
41
55
  {% endif %}
42
56
  {% if audit.deliveryEvidence is defined %}
43
57
  <section data-audit-field="deliveryEvidence">
44
58
  <h2>Delivery Evidence</h2>
45
- <pre>{{ audit.deliveryEvidence | tojson(indent=2) }}</pre>
59
+ <pre>{{ audit.deliveryEvidence | audit_json }}</pre>
46
60
  </section>
47
61
  {% endif %}
48
62
  </details>
@@ -22,7 +22,7 @@
22
22
  <label for="approval-option">구현 옵션</label>
23
23
  <select id="approval-option"{% if state.disabled_reason %} disabled{% endif %}>
24
24
  {% for name in state.option_names %}
25
- {% if name == state.recommended_option %}<option data-recommended="true" selected value="{{ name }}">{{ name }} (권장)</option>{% else %}<option value="{{ name }}">{{ name }}</option>{% endif %}
25
+ {% if name == state.recommended_option %}<option data-recommended="true" selected value="{{ name }}">{{ name | inline_code }} (권장)</option>{% else %}<option value="{{ name }}">{{ name | inline_code }}</option>{% endif %}
26
26
  {% endfor %}
27
27
  </select>
28
28
  <input type="checkbox" id="approval-checkbox"{% if state.disabled_reason %} disabled{% endif %}><label for="approval-checkbox">이 계획을 승인합니다.</label>
@@ -37,7 +37,7 @@
37
37
  <table><thead><tr><th>ID</th><th>질문</th><th>기대 형식</th><th>사용자 응답</th></tr></thead><tbody>
38
38
  {% for row in items %}
39
39
  <tr data-response-id="{{ row.id }}" data-kind="{{ row.kind }}">
40
- <td>{{ row.id }}</td><td>{{ row.statement }}</td><td>{{ row.expectedForm }}</td>
40
+ <td>{{ row.id }}</td><td>{{ row.statement | inline_code }}</td><td>{{ row.expectedForm | inline_code }}</td>
41
41
  <td><label for="response-{{ row.id }}">{{ row.id }} 사용자 응답</label><textarea id="response-{{ row.id }}" data-response-id="{{ row.id }}" rows="3">{{ row.userInput | default('') }}</textarea></td>
42
42
  </tr>
43
43
  {% endfor %}
@@ -1,19 +1,19 @@
1
- {% macro narrative(value, field_name) -%}
1
+ {% macro narrative(value, field_name, evidence_index={}) -%}
2
2
  <div class="narrative" data-report-field="{{ field_name }}">
3
- <p>{{ value.text }}</p>
4
- <p class="evidence-refs">근거: {{ value.evidenceRefs | join(', ') }}</p>
3
+ {{ value.text | paragraphs }}
4
+ <p class="evidence-refs">근거: {{ value.evidenceRefs | evidence_refs(evidence_index) }}</p>
5
5
  </div>
6
6
  {%- endmacro %}
7
7
 
8
8
  {% macro action_list(actions) -%}
9
9
  <ol class="action-list">
10
- {% for action in actions %}<li>{{ action }}</li>{% endfor %}
10
+ {% for action in actions %}<li>{{ action | inline_code }}</li>{% endfor %}
11
11
  </ol>
12
12
  {%- endmacro %}
13
13
 
14
14
  {% macro summary_card(title, body, tone='neutral') -%}
15
15
  <article class="summary-card tone-{{ tone }}">
16
16
  <h3>{{ title }}</h3>
17
- <p>{{ body }}</p>
17
+ <p>{{ body | inline_code }}</p>
18
18
  </article>
19
19
  {%- endmacro %}
@@ -9,9 +9,19 @@
9
9
  <thead><tr><th>ID</th><th>이름</th><th>그룹</th><th>상태</th><th>설명</th></tr></thead>
10
10
  <tbody>
11
11
  {% for node in model.nodes %}
12
- <tr data-fallback-id="{{ node.id }}"><td>{{ node.id }}</td><td>{{ node.label }}</td><td>{{ node.group }}</td><td>{{ node.status }}</td><td>{{ node.detail }}</td></tr>
12
+ <tr data-fallback-id="{{ node.id }}"><td>{{ node.id }}</td><td>{{ node.label | inline_code }}</td><td>{{ node.group | inline_code }}</td><td>{{ node.status }}</td><td>{{ node.detail | inline_code }}</td></tr>
13
13
  {% endfor %}
14
14
  </tbody>
15
15
  </table>
16
+ {% if model.edges %}
17
+ <table class="visualization-fallback visualization-edges">
18
+ <thead><tr><th>출발</th><th>도착</th><th>관계</th></tr></thead>
19
+ <tbody>
20
+ {% for edge in model.edges %}
21
+ <tr data-fallback-edge="{{ edge.source }}-{{ edge.target }}"><td>{{ edge.source }}</td><td>{{ edge.target }}</td><td>{{ edge.label | inline_code }}</td></tr>
22
+ {% endfor %}
23
+ </tbody>
24
+ </table>
25
+ {% endif %}
16
26
  </figure>
17
27
  {%- endmacro %}
@@ -6,15 +6,15 @@
6
6
  {% block human_content %}
7
7
  <section data-report-section="change-overview">
8
8
  <h2>변경 개요</h2>
9
- {{ render_narrative(narrative.changeExplanation, "changeImpactAnalysis.userNarrative.changeExplanation") }}
10
- <p>{{ change.changeRequest.summary }}</p>
9
+ {{ render_narrative(narrative.changeExplanation, "changeImpactAnalysis.userNarrative.changeExplanation", evidenceIndex) }}
10
+ <p>{{ change.changeRequest.summary | inline_code }}</p>
11
11
  </section>
12
12
 
13
13
  <section data-report-section="affected-and-unaffected">
14
14
  <h2>영향받는 영역과 영향 없는 경계</h2>
15
- {{ render_narrative(narrative.impactExplanation, "changeImpactAnalysis.userNarrative.impactExplanation") }}
16
- <div data-report-field="changeImpactAnalysis.impactItems"><h3>영향받는 영역</h3>{% for row in change.impactItems %}{{ summary_card(row.id ~ " · " ~ row.target, row.impactKind ~ " / 영향 " ~ row.level ~ " / 신뢰 " ~ row.confidence) }}{% endfor %}</div>
17
- <div data-report-field="changeImpactAnalysis.unaffectedBoundaries"><h3>영향 없는 경계</h3>{{ render_narrative(narrative.unaffectedExplanation, "changeImpactAnalysis.userNarrative.unaffectedExplanation") }}<ul>{% for row in change.unaffectedBoundaries %}<li>{{ row.summary }}</li>{% endfor %}</ul></div>
15
+ {{ render_narrative(narrative.impactExplanation, "changeImpactAnalysis.userNarrative.impactExplanation", evidenceIndex) }}
16
+ <div data-report-field="changeImpactAnalysis.impactItems"><h3>영향받는 영역</h3>{% for row in change.impactItems %}{{ summary_card(row.id ~ " · " ~ (row.target | inline_code), row.impactKind ~ " / 영향 " ~ row.level ~ " / 신뢰 " ~ row.confidence) }}{% endfor %}</div>
17
+ <div data-report-field="changeImpactAnalysis.unaffectedBoundaries"><h3>영향 없는 경계</h3>{{ render_narrative(narrative.unaffectedExplanation, "changeImpactAnalysis.userNarrative.unaffectedExplanation", evidenceIndex) }}<ul>{% for row in change.unaffectedBoundaries %}<li>{{ row.summary | inline_code }}</li>{% endfor %}</ul></div>
18
18
  </section>
19
19
 
20
20
  <section data-report-section="blast-radius" data-report-field="changeImpactAnalysis.dependencyBlastRadius">
@@ -24,16 +24,16 @@
24
24
 
25
25
  <section data-report-section="test-and-operations">
26
26
  <h2>테스트·운영·호환성</h2>
27
- <div data-report-field="changeImpactAnalysis.compatibilityImpact"><h3>호환성</h3><ul>{% for row in change.compatibilityImpact %}<li>{{ row.summary }}</li>{% endfor %}</ul></div>
28
- <div data-report-field="changeImpactAnalysis.migrationImpact"><h3>마이그레이션</h3><ul>{% for row in change.migrationImpact %}<li>{{ row.summary }}</li>{% endfor %}</ul></div>
29
- <div data-report-field="changeImpactAnalysis.rollbackImpact"><h3>롤백</h3><ul>{% for row in change.rollbackImpact %}<li>{{ row.summary }}</li>{% endfor %}</ul></div>
30
- <div data-report-field="changeImpactAnalysis.securityAndPerformanceImpact"><h3>보안과 성능</h3><ul>{% for row in change.securityAndPerformanceImpact %}<li>{{ row.summary }}</li>{% endfor %}</ul></div>
27
+ <div data-report-field="changeImpactAnalysis.compatibilityImpact"><h3>호환성</h3><ul>{% for row in change.compatibilityImpact %}<li>{{ row.summary | inline_code }}</li>{% endfor %}</ul></div>
28
+ <div data-report-field="changeImpactAnalysis.migrationImpact"><h3>마이그레이션</h3><ul>{% for row in change.migrationImpact %}<li>{{ row.summary | inline_code }}</li>{% endfor %}</ul></div>
29
+ <div data-report-field="changeImpactAnalysis.rollbackImpact"><h3>롤백</h3><ul>{% for row in change.rollbackImpact %}<li>{{ row.summary | inline_code }}</li>{% endfor %}</ul></div>
30
+ <div data-report-field="changeImpactAnalysis.securityAndPerformanceImpact"><h3>보안과 성능</h3><ul>{% for row in change.securityAndPerformanceImpact %}<li>{{ row.summary | inline_code }}</li>{% endfor %}</ul></div>
31
31
  </section>
32
32
 
33
33
  <section data-report-section="planning-readiness" data-report-field="changeImpactAnalysis.planningInputs">
34
34
  <h2>계획 준비도</h2>
35
- {{ render_narrative(narrative.planningReadiness, "changeImpactAnalysis.userNarrative.planningReadiness") }}
36
- <ul>{% for row in change.planningInputs %}<li>{{ row.constraint }}{% if row.unknown %} — 미확정{% endif %}</li>{% endfor %}</ul>
35
+ {{ render_narrative(narrative.planningReadiness, "changeImpactAnalysis.userNarrative.planningReadiness", evidenceIndex) }}
36
+ <ul>{% for row in change.planningInputs %}<li>{{ row.constraint | inline_code }}{% if row.unknown %} — 미확정{% endif %}</li>{% endfor %}</ul>
37
37
  </section>
38
38
 
39
39
  {{ analysis_review(analysisReviewIds) }}
@@ -5,36 +5,36 @@
5
5
  {% block human_content %}
6
6
  <section data-report-section="symptom">
7
7
  <h2>사용자가 겪은 증상</h2>
8
- {{ render_narrative(narrative.symptomExplanation, "errorAnalysis.userNarrative.symptomExplanation") }}
9
- <blockquote data-report-field="errorAnalysis.symptomVerbatim">{{ error.symptomVerbatim }}</blockquote>
10
- <p data-report-field="errorAnalysis.observableFailure">{{ error.observableFailure }}</p>
8
+ {{ render_narrative(narrative.symptomExplanation, "errorAnalysis.userNarrative.symptomExplanation", evidenceIndex) }}
9
+ <blockquote data-report-field="errorAnalysis.symptomVerbatim">{{ error.symptomVerbatim | inline_code }}</blockquote>
10
+ <p data-report-field="errorAnalysis.observableFailure">{{ error.observableFailure | inline_code }}</p>
11
11
  </section>
12
12
 
13
13
  <section data-report-section="reproduction">
14
14
  <h2>재현 상태</h2>
15
15
  <p><span class="status status-{{ error.reproduction.status }}">{{ error.reproduction.status }}</span></p>
16
- <ul>{% for item in error.reproduction.evidence %}<li>{{ item }}</li>{% endfor %}</ul>
17
- {% if error.reproduction.blockedReason %}<p>{{ error.reproduction.blockedReason }}</p>{% endif %}
16
+ <ul>{% for item in error.reproduction.evidence %}<li>{{ [item] | evidence_refs(evidenceIndex) }}</li>{% endfor %}</ul>
17
+ {% if error.reproduction.blockedReason %}<p>{{ error.reproduction.blockedReason | inline_code }}</p>{% endif %}
18
18
  </section>
19
19
 
20
20
  <section data-report-section="causes">
21
21
  <h2>원인 후보</h2>
22
- {{ render_narrative(narrative.causeExplanation, "errorAnalysis.userNarrative.causeExplanation") }}
22
+ {{ render_narrative(narrative.causeExplanation, "errorAnalysis.userNarrative.causeExplanation", evidenceIndex) }}
23
23
  {{ figure(causeFigure) }}
24
- <div class="summary-grid">{% for row in error.causeCandidates %}<article class="summary-card tone-{{ row.confidence }}"><p class="eyebrow">{{ row.id }} · 신뢰도 {{ row.confidence }}</p><h3>{{ row.statement }}</h3><p class="evidence-refs">지지 근거: {{ row.supportingEvidence | join(', ') }}</p></article>{% else %}<p>현재 확인된 원인 후보가 없습니다.</p>{% endfor %}</div>
24
+ <div class="summary-grid">{% for row in error.causeCandidates %}<article class="summary-card tone-{{ row.confidence }}"><p class="eyebrow">{{ row.id }} · 신뢰도 {{ row.confidence }}</p><h3>{{ row.statement | inline_code }}</h3><p class="evidence-refs">지지 근거: {{ row.supportingEvidence | evidence_refs(evidenceIndex) }}</p></article>{% else %}<p>현재 확인된 원인 후보가 없습니다.</p>{% endfor %}</div>
25
25
  </section>
26
26
 
27
27
  <section data-report-section="uncertainty">
28
28
  <h2>아직 확정할 수 없는 부분</h2>
29
- {{ render_narrative(narrative.uncertaintyExplanation, "errorAnalysis.userNarrative.uncertaintyExplanation") }}
30
- {% for row in error.causeCandidates %}<article class="summary-card"><h3>{{ row.id }}</h3><p data-report-field="errorAnalysis.causeCandidates.confidence"><strong>현재 신뢰도:</strong> {{ row.confidence }}</p><p data-report-field="errorAnalysis.causeCandidates.falsifyingEvidenceChecked"><strong>확인한 반증:</strong> {{ row.falsifyingEvidenceChecked | join(', ') }}</p><p data-report-field="errorAnalysis.causeCandidates.disproveWith"><strong>이 가설을 기각하는 방법:</strong> {{ row.disproveWith }}</p></article>{% endfor %}
29
+ {{ render_narrative(narrative.uncertaintyExplanation, "errorAnalysis.userNarrative.uncertaintyExplanation", evidenceIndex) }}
30
+ {% for row in error.causeCandidates %}<article class="summary-card"><h3>{{ row.id }}</h3><p data-report-field="errorAnalysis.causeCandidates.confidence"><strong>현재 신뢰도:</strong> {{ row.confidence }}</p><p data-report-field="errorAnalysis.causeCandidates.falsifyingEvidenceChecked"><strong>확인한 반증:</strong> {{ row.falsifyingEvidenceChecked | join(', ') | inline_code }}</p><p data-report-field="errorAnalysis.causeCandidates.disproveWith"><strong>이 가설을 기각하는 방법:</strong> {{ row.disproveWith | inline_code }}</p></article>{% endfor %}
31
31
  </section>
32
32
 
33
33
  <section data-report-section="next-diagnostic">
34
34
  <h2>다음 진단 한 가지</h2>
35
- {{ render_narrative(narrative.diagnosticGuidance, "errorAnalysis.userNarrative.diagnosticGuidance") }}
35
+ {{ render_narrative(narrative.diagnosticGuidance, "errorAnalysis.userNarrative.diagnosticGuidance", evidenceIndex) }}
36
36
  {{ summary_card("실행", error.nextDiagnostic.action, "important") }}
37
- <p><strong>확인 신호:</strong> {{ error.nextDiagnostic.confirmingSignal }}</p>
38
- <p><strong>기각 신호:</strong> {{ error.nextDiagnostic.rejectingSignal }}</p>
37
+ <p><strong>확인 신호:</strong> {{ error.nextDiagnostic.confirmingSignal | inline_code }}</p>
38
+ <p><strong>기각 신호:</strong> {{ error.nextDiagnostic.rejectingSignal | inline_code }}</p>
39
39
  </section>
40
40
  {% endblock %}
@@ -6,33 +6,33 @@
6
6
  {% block human_content %}
7
7
  <section data-report-section="feature-overview">
8
8
  <h2>기능 개요</h2>
9
- {{ render_narrative(narrative.userBehaviorExplanation, "featureAnalysis.userNarrative.userBehaviorExplanation") }}
10
- <p><strong>대상:</strong> {{ feature.target.requestedValue }}</p>
11
- <ul>{% for row in feature.actors %}<li>{{ row.description }}</li>{% endfor %}</ul>
9
+ {{ render_narrative(narrative.userBehaviorExplanation, "featureAnalysis.userNarrative.userBehaviorExplanation", evidenceIndex) }}
10
+ <p><strong>대상:</strong> {{ feature.target.requestedValue | inline_code }}</p>
11
+ <ul>{% for row in feature.actors %}<li>{{ row.description | inline_code }}</li>{% endfor %}</ul>
12
12
  </section>
13
13
 
14
14
  <section data-report-section="behavior-flow" data-report-field="featureAnalysis.flows">
15
15
  <h2>사용자 행동 흐름</h2>
16
- {{ render_narrative(narrative.flowExplanation, "featureAnalysis.userNarrative.flowExplanation") }}
16
+ {{ render_narrative(narrative.flowExplanation, "featureAnalysis.userNarrative.flowExplanation", evidenceIndex) }}
17
17
  {{ figure(flowFigure) }}
18
- <div class="summary-grid">{% for row in feature.flows %}<article class="summary-card" data-flow-kind="{{ 'alternate' if row.kind == 'alternative' else row.kind }}"><p class="eyebrow">{{ row.id }} · {{ row.kind }}</p><ol>{% for step in row.steps %}<li>{{ step.action }}</li>{% endfor %}</ol></article>{% endfor %}</div>
18
+ <div class="summary-grid">{% for row in feature.flows %}<article class="summary-card" data-flow-kind="{{ 'alternate' if row.kind == 'alternative' else row.kind }}"><p class="eyebrow">{{ row.id }} · {{ row.kind }}</p><ol>{% for step in row.steps %}<li>{{ step.action | inline_code }}</li>{% endfor %}</ol></article>{% endfor %}</div>
19
19
  </section>
20
20
 
21
21
  <section data-report-section="rules-and-state">
22
22
  <h2>규칙과 상태 변경</h2>
23
- {{ render_narrative(narrative.rulesAndStateExplanation, "featureAnalysis.userNarrative.rulesAndStateExplanation") }}
24
- <div data-report-field="featureAnalysis.domainRules"><h3>도메인 규칙</h3>{% for row in feature.domainRules %}{{ summary_card(row.id ~ " · " ~ row.condition, row.outcome) }}{% endfor %}</div>
23
+ {{ render_narrative(narrative.rulesAndStateExplanation, "featureAnalysis.userNarrative.rulesAndStateExplanation", evidenceIndex) }}
24
+ <div data-report-field="featureAnalysis.domainRules"><h3>도메인 규칙</h3>{% for row in feature.domainRules %}{{ summary_card(row.id ~ " · " ~ (row.condition | inline_code), row.outcome) }}{% endfor %}</div>
25
25
  <div data-report-field="featureAnalysis.stateChanges"><h3>상태 변경</h3>{% for row in feature.stateChanges %}{{ summary_card(row.id ~ " · " ~ row.field, row.change) }}{% endfor %}</div>
26
26
  </section>
27
27
 
28
28
  <section data-report-section="external-interactions" data-report-field="featureAnalysis.externalInteractions">
29
29
  <h2>외부 상호작용</h2>
30
- {% for row in feature.externalInteractions %}{{ summary_card(row.target, row.input ~ " → " ~ row.output ~ ". 실패 시: " ~ row.failureHandling) }}{% endfor %}
30
+ {% for row in feature.externalInteractions %}{{ summary_card(row.target | inline_code, row.input ~ " → " ~ row.output ~ ". 실패 시: " ~ row.failureHandling) }}{% endfor %}
31
31
  </section>
32
32
 
33
33
  <section data-report-section="test-gaps" data-report-field="featureAnalysis.testCoverage">
34
34
  <h2>테스트와 공백</h2>
35
- {{ render_narrative(narrative.testGapExplanation, "featureAnalysis.userNarrative.testGapExplanation") }}
35
+ {{ render_narrative(narrative.testGapExplanation, "featureAnalysis.userNarrative.testGapExplanation", evidenceIndex) }}
36
36
  {% for row in feature.testCoverage %}{{ summary_card(row.id, "테스트: " ~ (row.testPaths | join(', ')) ~ "; 공백: " ~ ((row.gaps | join(', ')) or '없음')) }}{% endfor %}
37
37
  </section>
38
38