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.
- package/README.md +21 -7
- package/docs/architecture/storage-model.md +34 -61
- package/docs/architecture.md +51 -49
- package/docs/cli.md +38 -21
- package/docs/for-ai/skills/okstra-run.md +13 -34
- package/docs/performance-improvement-plan-v2.md +2 -2
- package/docs/pr-template-usage.md +1 -1
- package/docs/project-structure-overview.md +10 -8
- package/docs/task-process/README.md +4 -4
- package/docs/task-process/common-flow.md +12 -12
- package/docs/task-process/final-verification.md +2 -2
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +2 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +2 -2
- package/runtime/agents/workers/grok-worker.md +256 -0
- package/runtime/agents/workers/kimi-worker.md +256 -0
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/lib/okstra/cli.sh +13 -1
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/usage.sh +17 -12
- package/runtime/bin/okstra-grok-exec.sh +5 -0
- package/runtime/bin/okstra-kimi-exec.sh +5 -0
- package/runtime/bin/okstra-provider-exec.py +235 -0
- package/runtime/bin/okstra.sh +3 -0
- package/runtime/prompts/lead/adapters/antigravity.md +48 -0
- package/runtime/prompts/lead/adapters/claude-code.md +13 -11
- package/runtime/prompts/lead/adapters/codex.md +7 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +4 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +12 -12
- package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
- package/runtime/prompts/profiles/error-analysis.md +2 -0
- package/runtime/prompts/profiles/feature-analysis.md +2 -0
- package/runtime/prompts/profiles/final-verification.md +3 -1
- package/runtime/prompts/profiles/forbidden-actions.json +4 -4
- package/runtime/prompts/profiles/implementation-planning.md +3 -1
- package/runtime/prompts/profiles/implementation.md +2 -2
- package/runtime/prompts/profiles/improvement-discovery.md +3 -1
- package/runtime/prompts/profiles/project-analysis.md +2 -0
- package/runtime/prompts/profiles/release-handoff.md +7 -7
- package/runtime/prompts/profiles/requirements-discovery.md +2 -0
- package/runtime/prompts/wizard/prompts.ko.json +9 -1
- package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
- package/runtime/python/okstra_ctl/lead_events.py +1 -1
- package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
- package/runtime/python/okstra_ctl/models.py +156 -8
- package/runtime/python/okstra_ctl/path_hints.py +9 -25
- package/runtime/python/okstra_ctl/paths.py +1 -1
- package/runtime/python/okstra_ctl/render.py +172 -74
- package/runtime/python/okstra_ctl/report_html/common.py +38 -2
- package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
- package/runtime/python/okstra_ctl/report_html/render.py +7 -0
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/visualizations.py +32 -6
- package/runtime/python/okstra_ctl/run.py +264 -45
- package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
- package/runtime/python/okstra_ctl/team.py +2 -7
- package/runtime/python/okstra_ctl/wizard.py +194 -21
- package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
- package/runtime/python/okstra_ctl/workers.py +3 -1
- package/runtime/python/okstra_ctl/workflow.py +4 -2
- package/runtime/python/okstra_token_usage/__init__.py +1 -0
- package/runtime/python/okstra_token_usage/collect.py +32 -23
- package/runtime/python/okstra_token_usage/pricing.py +35 -3
- package/runtime/schemas/final-report-v2.0.schema.json +2 -2
- package/runtime/skills/okstra-run/SKILL.md +31 -42
- package/runtime/templates/prd/pr-body.template.md +1 -1
- package/runtime/templates/reports/html/assets/base.css +6 -3
- package/runtime/templates/reports/html/base.template.html +22 -8
- package/runtime/templates/reports/html/macros/forms.html +2 -2
- package/runtime/templates/reports/html/macros/layout.html +5 -5
- package/runtime/templates/reports/html/macros/visualizations.html +11 -1
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +12 -12
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +9 -9
- package/runtime/templates/reports/html/tasks/final-verification.template.html +7 -7
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +12 -12
- package/runtime/templates/reports/html/tasks/implementation.template.html +7 -7
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +6 -6
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +5 -5
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +8 -8
- package/runtime/templates/reports/report.js +21 -4
- package/runtime/templates/reports/settings.template.json +4 -0
- package/runtime/templates/reports/task-brief.template.md +7 -7
- package/runtime/validators/validate-run.py +11 -6
- package/runtime/validators/validate_session_conformance.py +2 -1
- package/src/cli-registry.mjs +4 -4
- package/src/commands/execute/codex-dispatch.mjs +7 -10
- package/src/commands/execute/render-bundle.mjs +3 -3
- package/src/commands/execute/run.mjs +17 -52
- package/src/commands/execute/wizard.mjs +4 -1
- package/src/commands/lifecycle/doctor.mjs +6 -3
- package/src/commands/lifecycle/install.mjs +31 -8
- package/src/lib/runtime-manifest.mjs +1 -1
- package/src/lib/runtime-resolver.mjs +2 -2
- 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-
|
|
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": "
|
|
114
|
+
"reportOwner": { "const": "Okstra lead" },
|
|
115
115
|
"reportAuthor": {
|
|
116
|
-
"enum": ["Report writer worker", "
|
|
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
|
|
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 `
|
|
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
|
|
17
|
-
- Continue an existing task (next phase) without leaving the current
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
257
|
+
## Step 6: Take over as Okstra lead
|
|
269
258
|
|
|
270
|
-
Read `<INSTRUCTION_SET_PATH>/
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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>{{
|
|
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>{{
|
|
15
|
-
<p>{{ humanSummary.
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
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
|
-
|
|
4
|
-
<p class="evidence-refs">근거: {{ value.evidenceRefs |
|
|
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 |
|
|
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
|
|