okstra 0.214.1 → 0.216.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 (118) hide show
  1. package/README.md +2 -1
  2. package/dist/cli-registry.d.mts +14 -0
  3. package/dist/cli-registry.mjs +12 -0
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/chat/chat.mjs +2 -56
  6. package/dist/commands/chat/chat.mjs.map +1 -1
  7. package/dist/commands/execute/render-bundle.mjs +5 -2
  8. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  9. package/dist/commands/execute/wizard.mjs +6 -0
  10. package/dist/commands/execute/wizard.mjs.map +1 -1
  11. package/dist/commands/memory/memory.mjs +57 -6
  12. package/dist/commands/memory/memory.mjs.map +1 -1
  13. package/dist/lib/file-lock.d.mts +1 -0
  14. package/dist/lib/file-lock.mjs +60 -0
  15. package/dist/lib/file-lock.mjs.map +1 -0
  16. package/dist/lib/python-command.mjs +11 -1
  17. package/dist/lib/python-command.mjs.map +1 -1
  18. package/dist/lib/skill-catalog.mjs +1 -0
  19. package/dist/lib/skill-catalog.mjs.map +1 -1
  20. package/dist/lib/types.d.mts +1 -1
  21. package/docs/architecture.md +2 -2
  22. package/docs/cli.md +36 -14
  23. package/docs/project-structure-overview.md +6 -3
  24. package/package.json +1 -1
  25. package/runtime/BUILD.json +2 -2
  26. package/runtime/bin/lib/okstra/cli.sh +9 -2
  27. package/runtime/bin/lib/okstra/globals.sh +2 -0
  28. package/runtime/bin/lib/okstra/interactive.sh +8 -2
  29. package/runtime/bin/lib/okstra/usage.sh +3 -1
  30. package/runtime/bin/okstra.sh +7 -1
  31. package/runtime/prompts/duties/discussion-worker.json +32 -0
  32. package/runtime/prompts/lead/convergence.md +58 -136
  33. package/runtime/prompts/lead/okstra-lead-contract.md +45 -12
  34. package/runtime/prompts/profiles/_common-contract.md +1 -1
  35. package/runtime/prompts/wizard/prompts.ko.json +14 -1
  36. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +4 -0
  37. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +4 -11
  38. package/runtime/python/okstra_ctl/agent/invocation.py +1 -0
  39. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +2 -2
  40. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +4 -4
  41. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +2 -2
  42. package/runtime/python/okstra_ctl/analysis_packet.py +3 -0
  43. package/runtime/python/okstra_ctl/consumers.py +8 -2
  44. package/runtime/python/okstra_ctl/convergence.py +143 -29
  45. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +57 -0
  46. package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +106 -0
  47. package/runtime/python/okstra_ctl/convergence_engine.py +140 -95
  48. package/runtime/python/okstra_ctl/convergence_reverify_prompt.py +3 -27
  49. package/runtime/python/okstra_ctl/convergence_store.py +11 -4
  50. package/runtime/python/okstra_ctl/dispatch_core.py +4 -1
  51. package/runtime/python/okstra_ctl/domain/role.py +1 -0
  52. package/runtime/python/okstra_ctl/manager_cli.py +27 -1
  53. package/runtime/python/okstra_ctl/manager_launch.py +44 -4
  54. package/runtime/python/okstra_ctl/manager_sync.py +14 -1
  55. package/runtime/python/okstra_ctl/manager_view.py +53 -1
  56. package/runtime/python/okstra_ctl/next_phase.py +1 -0
  57. package/runtime/python/okstra_ctl/phases/catalog.py +2 -1
  58. package/runtime/python/okstra_ctl/phases/discussion/__init__.py +1 -0
  59. package/runtime/python/okstra_ctl/phases/discussion/boundary.json +11 -0
  60. package/runtime/python/okstra_ctl/phases/discussion/cli.py +512 -0
  61. package/runtime/python/okstra_ctl/phases/discussion/entry.py +116 -0
  62. package/runtime/python/okstra_ctl/phases/discussion/followup.py +876 -0
  63. package/runtime/python/okstra_ctl/phases/discussion/memory.py +294 -0
  64. package/runtime/python/okstra_ctl/phases/discussion/overview.py +85 -0
  65. package/runtime/python/okstra_ctl/phases/discussion/profile.json +31 -0
  66. package/runtime/python/okstra_ctl/phases/discussion/profile.md +48 -0
  67. package/runtime/python/okstra_ctl/phases/discussion/report.py +73 -0
  68. package/runtime/python/okstra_ctl/phases/discussion/report_assets/discussion.template.html +15 -0
  69. package/runtime/python/okstra_ctl/phases/discussion/report_assets/discussion.template.md +6 -0
  70. package/runtime/python/okstra_ctl/phases/discussion/spec.md +197 -0
  71. package/runtime/python/okstra_ctl/phases/discussion/store.py +1143 -0
  72. package/runtime/python/okstra_ctl/phases/discussion/validation.py +111 -0
  73. package/runtime/python/okstra_ctl/phases/discussion/wizard.py +90 -0
  74. package/runtime/python/okstra_ctl/phases/discussion/workflow.py +667 -0
  75. package/runtime/python/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md +1 -1
  76. package/runtime/python/okstra_ctl/phases/implementation_planning/host-rules.md +4 -1
  77. package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +1 -1
  78. package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +1 -1
  79. package/runtime/python/okstra_ctl/plan_run_root.py +8 -0
  80. package/runtime/python/okstra_ctl/render.py +7 -20
  81. package/runtime/python/okstra_ctl/render_final_report.py +2 -1
  82. package/runtime/python/okstra_ctl/report_assembly.py +22 -0
  83. package/runtime/python/okstra_ctl/report_contract.py +2 -0
  84. package/runtime/python/okstra_ctl/report_narrative.py +13 -4
  85. package/runtime/python/okstra_ctl/report_synthesis_packet.py +10 -0
  86. package/runtime/python/okstra_ctl/run.py +40 -7
  87. package/runtime/python/okstra_ctl/stage_close.py +46 -11
  88. package/runtime/python/okstra_ctl/stage_reopen.py +151 -0
  89. package/runtime/python/okstra_ctl/verdict_blocks.py +11 -35
  90. package/runtime/python/okstra_ctl/wizard/cli.py +8 -0
  91. package/runtime/python/okstra_ctl/wizard/confirmation.py +4 -1
  92. package/runtime/python/okstra_ctl/wizard/engine.py +8 -0
  93. package/runtime/python/okstra_ctl/wizard/ids.py +12 -0
  94. package/runtime/python/okstra_ctl/wizard/registry.py +35 -3
  95. package/runtime/python/okstra_ctl/wizard/render.py +4 -1
  96. package/runtime/python/okstra_ctl/wizard/sources.py +2 -2
  97. package/runtime/python/okstra_ctl/wizard/state.py +11 -1
  98. package/runtime/python/okstra_ctl/wizard/steps_identity.py +4 -4
  99. package/runtime/python/okstra_ctl/worker_prompt_policy.py +24 -6
  100. package/runtime/python/okstra_token_usage/pricing.py +2 -0
  101. package/runtime/schemas/convergence-groups-v1.0.schema.json +1 -1
  102. package/runtime/schemas/convergence-groups-v2.0.schema.json +1 -1
  103. package/runtime/schemas/final-report-v3.0.schema.json +90 -1
  104. package/runtime/skills/_fragments/wizard-bash-invocation-rule.md +1 -0
  105. package/runtime/skills/_fragments/wizard-confirm-step.md +7 -0
  106. package/runtime/skills/_fragments/wizard-lead-takeover.md +8 -0
  107. package/runtime/skills/_fragments/wizard-preflight.md +75 -0
  108. package/runtime/skills/_fragments/wizard-prompt-loop.md +22 -0
  109. package/runtime/skills/_fragments/wizard-relay-protocol.md +40 -0
  110. package/runtime/skills/_fragments/wizard-render-bundle.md +74 -0
  111. package/runtime/skills/okstra-discussion/SKILL.md +347 -0
  112. package/runtime/skills/okstra-pr-gen/SKILL.md +7 -0
  113. package/runtime/skills/okstra-run/SKILL.md +22 -2
  114. package/runtime/templates/manager/view.template.html +3 -0
  115. package/runtime/templates/reports/html/i18n/en.json +5 -0
  116. package/runtime/templates/reports/html/i18n/ko.json +5 -0
  117. package/runtime/validators/validate-brief.py +3 -2
  118. package/runtime/validators/validate-run.py +4 -6
@@ -26,6 +26,7 @@
26
26
  "additionalProperties": false,
27
27
  "properties": {
28
28
  "technicalVerification": {"$ref": "#/$defs/TechnicalVerification"},
29
+ "discussion": {"$ref": "#/$defs/Discussion"},
29
30
  "schemaVersion": {
30
31
  "const": "3.0",
31
32
  "description": "Schema version. Renderer refuses to process unknown versions."
@@ -1860,6 +1861,48 @@
1860
1861
  ]
1861
1862
  }
1862
1863
  },
1864
+ {
1865
+ "description": "discussion requires the revision-reference block and no other task deliverable; the block is assembly-owned.",
1866
+ "if": {
1867
+ "properties": {
1868
+ "header": {
1869
+ "properties": {
1870
+ "taskType": {
1871
+ "const": "discussion"
1872
+ }
1873
+ }
1874
+ }
1875
+ },
1876
+ "required": [
1877
+ "header"
1878
+ ]
1879
+ },
1880
+ "then": {
1881
+ "required": [
1882
+ "discussion"
1883
+ ],
1884
+ "properties": {
1885
+ "technicalVerification": false,
1886
+ "requirementsDiscovery": false,
1887
+ "improvementDiscovery": false,
1888
+ "errorAnalysis": false,
1889
+ "analysisCommon": false,
1890
+ "projectAnalysis": false,
1891
+ "featureAnalysis": false,
1892
+ "changeImpactAnalysis": false,
1893
+ "implementationOptionSelection": false,
1894
+ "implementationPlanning": false,
1895
+ "releaseHandoff": false,
1896
+ "implementation": false,
1897
+ "finalVerification": false
1898
+ }
1899
+ },
1900
+ "else": {
1901
+ "properties": {
1902
+ "discussion": false
1903
+ }
1904
+ }
1905
+ },
1863
1906
  {
1864
1907
  "description": "error-analysis task-type requires a structured diagnosis block.",
1865
1908
  "if": {
@@ -2297,6 +2340,7 @@
2297
2340
  }
2298
2341
  ],
2299
2342
  "oneOf": [
2343
+ {"required": ["discussion"]},
2300
2344
  {"required": ["technicalVerification"]},
2301
2345
  {
2302
2346
  "required": [
@@ -4492,7 +4536,52 @@
4492
4536
  "implementation-planning",
4493
4537
  "implementation",
4494
4538
  "final-verification",
4495
- "release-handoff"
4539
+ "release-handoff",
4540
+ "discussion"
4541
+ ]
4542
+ },
4543
+ "Discussion": {
4544
+ "description": "Reference to the discussion outcome. The canonical record is the immutable revision under <task-root>/discussion/revisions/; the report carries only its id and content digest, or the draft version when the user has not confirmed completion. Report assembly writes this block from the discussion record; the report writer never authors it.",
4545
+ "oneOf": [
4546
+ {
4547
+ "type": "object",
4548
+ "additionalProperties": false,
4549
+ "required": [
4550
+ "outcome",
4551
+ "revisionId",
4552
+ "contentDigest"
4553
+ ],
4554
+ "properties": {
4555
+ "outcome": {
4556
+ "const": "revision"
4557
+ },
4558
+ "revisionId": {
4559
+ "type": "string",
4560
+ "pattern": "^[^:\\s]+:[^:\\s]+:[^:\\s/]+/revision-[0-9]{3,}$"
4561
+ },
4562
+ "contentDigest": {
4563
+ "type": "string",
4564
+ "pattern": "^sha256:[0-9a-f]{64}$"
4565
+ }
4566
+ }
4567
+ },
4568
+ {
4569
+ "type": "object",
4570
+ "additionalProperties": false,
4571
+ "required": [
4572
+ "outcome",
4573
+ "draftVersion"
4574
+ ],
4575
+ "properties": {
4576
+ "outcome": {
4577
+ "const": "draft"
4578
+ },
4579
+ "draftVersion": {
4580
+ "type": "string",
4581
+ "pattern": "^sha256:[0-9a-f]{64}$"
4582
+ }
4583
+ }
4584
+ }
4496
4585
  ]
4497
4586
  },
4498
4587
  "HumanSummary": {
@@ -0,0 +1 @@
1
+ **Bash invocation rule (permission-friendly)**: every Bash command in this skill begins with the literal token `okstra` (or another already-allowed binary) and passes literal argument values. Do not introduce shell variables (`$STATE_FILE`, `$ANSWER`, `$projectRoot`, ...), `$(...)` command substitution, or leading `VAR=...` assignments — any of those make the leading token non-literal, defeat the `Bash(okstra:*)` permission match, and force a confirmation prompt on every wizard call. When a prior tool call emitted a path or value, read it from the tool output and paste the literal string into the next command.
@@ -0,0 +1,7 @@
1
+ ## Step 4: The confirm step's question text is the selection summary
2
+
3
+ When `next.step == "confirm"`, the prompt's `label` already carries the full selection summary (`선택 확인:` followed by one line per resolved input — task-type, task-key, brief, base-ref, worktree, every role slot, directive, related-tasks, clarification, stage, …) and ends with the question. Render that `label` **verbatim, every line, as the question text** of the `confirm` picker (Proceed / Edit / Abort) — the same way you render any other pick step's label. Do not re-author it as a table, do not summarise it into prose, do not drop a line because its value is `(none)`: the user confirms what is on screen, and a line you left out is a setting they never saw (observed: a lead's own table omitted `directive`, `base-ref` and `brief`). Render the picker **as the final output of that turn** — emit nothing after the picker call.
4
+
5
+ `okstra wizard confirmation --state-file <path>` returns the same block as `{ok: true, text: ...}` for a text-only host or when the user asks to see the summary again; it is not a substitute for the label.
6
+
7
+ Keep the provider data-transfer disclosure in that label alongside the selected roles/models and task inputs. The user's actual `Proceed` response authorizes the displayed recipients and task-material scope; it does not grant host execution privileges. When dispatching, follow the host relay's guidance to carry that response and scope into the execution-permission request. A freshly generated `confirmationText` does not retroactively add disclosure to an older user response.
@@ -0,0 +1,8 @@
1
+ ## Step 6: Take over as Okstra lead
2
+
3
+ Read `resources.leadExecutionPromptPath` from the run manifest at `<RUN_MANIFEST_PATH>` — the same manifest Step 5 already opened for `resources.leadPromptMetadataPath` — then read that file verbatim and take over as `Okstra lead` in the current host-native session. Do not construct the path yourself: the prompt is written per run under `runs/<task-type>/prompts/lead-execution-prompt-<task-type>-<seq>.md`, not in the instruction set, and the `<seq>` is assigned at prepare time. 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.
4
+
5
+ Then proceed through the phases exactly as the lead prompt directs (Phase 1 context → Phase 2+ worker dispatch → final synthesis → final report).
6
+
7
+ Inform the user with one short line:
8
+ > Took over as Okstra lead (`<host-runtime>`) for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
@@ -0,0 +1,75 @@
1
+ ## Step 1: Preflight
2
+
3
+ Use the launcher's `OKSTRA_RUNTIME_HOST` value when it is present. Otherwise use the registered host ID that the current harness declares for this session. This is a harness capability declaration, never a worker-provider choice. Do not infer it from an executable or `PATH`, and do not silently substitute another host.
4
+
5
+ Record the semantic functions that the live harness can actually perform. Always include `plain_text_input`. Record `native_single_select`, `native_multi_select`, or `native_question_group` only when the current harness exposes that function. Do not infer functions from the host ID; the effective declaration is computed from the relay contract after preflight.
6
+
7
+ 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.
8
+
9
+ Run one Bash tool call (Bash invocation rule from the top of this file applies):
10
+
11
+ ```bash
12
+ okstra preflight --runtime <host-runtime>
13
+ ```
14
+
15
+ Read the fixed text lines. If the first line is `Okstra preflight: failed`, show `Runtime readiness` and every repeated `Readiness check`, `Readiness check status`, and `Readiness check action`, then stop before Step 2. Show `Reason` and `Recovery` for every failure. Do not relabel a host login, license, daemon, trust, or other readiness failure as missing Okstra setup. 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=...`.
16
+
17
+ On `Okstra preflight: ready`, require `Runtime readiness: ready` before Step 2. These checks come from the selected host adapter and are independent of worker-provider choices. Any other readiness value is unrecognized runtime output: show it and stop before Step 2 instead of guessing that the host is ready.
18
+
19
+ Carry the fixed `Lead entry mode` line into Step 2's `--entry-mode` as a literal. It is the mode the run must launch in, which is not always the mode this session is in: a host adapter may report that this session cannot host the lead while the run itself is ready. `spawn-process` means okstra starts its own lead process and this session is not the lead — when the line reads `spawn-process` and a readiness check carries `action: spawn-unsandboxed-codex-lead`, tell the user in one line that their current Codex session is sandboxed so okstra will open an unsandboxed lead instead, then continue. Never substitute `current-session` for a `spawn-process` answer, and never recompute the value from the host ID.
20
+
21
+ For a ready response, read the absolute path in the fixed `Relay contract` line with the current host's file-read primitive. Do not derive the path from the host ID or search `PATH`. In that file, find the `Wizard interaction relay` JSON block, require `schemaVersion: 1` and `runtime` equal to the fixed `Runtime` line, then take its `semanticFunctions` allowlist and intersect it with the functions the live harness exposes. The live harness does not expose tools named `native_single_select`. Map each allowlist token to the matching `interactions` kind (`native_single_select` → `native-single`, `native_multi_select` → `native-multi`, `native_question_group` → `native-group`). Include the token in the intersection only when this session can call the string in that kind's `function` field. If the kind is absent from `interactions`, omit the token. Pass only that intersection to Step 2; `plain_text_input` must be present. Keep the parsed `interactions` object for Step 3's function/input/response conversion, and the parsed `recovery` object for the branch below. An unreadable file, malformed block, runtime mismatch, absent `plain_text_input`, or later interaction kind missing from the object is a host relay contract failure: show the problem and stop rather than guessing.
22
+
23
+ If the successful fixed projection has `Relay contract: -`, enter the compatibility branch below. In that branch only, declare `plain_text_input` and keep its built-in `interactions` mapping for Step 3; do not assume a native tool from the host ID. The existing `unknown command: preflight` branch remains the authoritative stale-CLI failure.
24
+
25
+ Before calculating the effective intersection, apply the registered relay's client-specific tool selection and live mode restrictions. A callable question tool does not necessarily render a selector in the current client. In Codex, use the synchronous picker when permitted; asynchronous question cards are a desktop fallback, not a terminal picker. A declared native function can still fail at call time: the client refuses the call, the session has no view to present it in, or it returns no answer. That is not a reason to stop, and not a reason to try a different native function. Follow the relay's `recovery.native-question-refused` entry the first time it happens — drop the tokens it names from the intersection, keep `plain_text_input`, tell the user in one line that the host picker is unavailable, and render every remaining screen through the text mapping. Numbered text is that entry's own recovery path, so the ban on printing a numbered list does not apply once it fires. The wizard state file is untouched by a refused call: `okstra wizard step --state-file <path> --no-submit` returns the pending prompt again.
26
+
27
+ Plan adoption (`approve_plan_confirm`) is a workflow choice: present the wizard's existing options using the same selector as other `pick` steps. Follow the relay's distinction between plan decisions and execution permissions; the word "approval" alone is not a reason to replace a selector with a typed confirmation.
28
+
29
+ ### Legacy text relay compatibility
30
+
31
+ Use this built-in mapping only when the successful preflight response omitted `relayContract`. It is not a fallback for an unreadable, malformed, mismatched, or incomplete relay contract. If a present relay contract omits the wizard's interaction kind, keep the fail-closed rule above and stop.
32
+
33
+ ```json
34
+ {
35
+ "scope": "preflight-response-without-relayContract-only",
36
+ "semanticFunctions": ["plain_text_input"],
37
+ "interactions": {
38
+ "numbered-single": {
39
+ "function": "host-text",
40
+ "input": {
41
+ "question": "label-with-progress",
42
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
43
+ },
44
+ "response": { "source": "next-message", "submit": "raw" }
45
+ },
46
+ "numbered-multi": {
47
+ "function": "host-text",
48
+ "input": {
49
+ "question": "label-with-progress",
50
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
51
+ },
52
+ "response": { "source": "next-message", "submit": "raw" }
53
+ },
54
+ "sequential-group": {
55
+ "function": "host-text",
56
+ "input": {
57
+ "questions": "all-one-at-a-time-in-original-order",
58
+ "question": "label-with-progress",
59
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
60
+ },
61
+ "response": {
62
+ "source": "next-message-by-question-position",
63
+ "submit": "compact-step-json-raw"
64
+ }
65
+ },
66
+ "plain-text": {
67
+ "function": "host-text",
68
+ "input": { "question": "label-with-progress" },
69
+ "response": { "source": "next-message", "submit": "raw" }
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ For numbered interactions, render every option as a 1-based Markdown item containing its label followed by its description verbatim, in original order. For `sequential-group`, ask every question in order and build one compact JSON object keyed by `questions[].step`. Submit each user message unchanged; the wizard owns numbered and grouped normalization.
@@ -0,0 +1,22 @@
1
+ Repeat until `next.kind == "done"` (or `"aborted"` — terminal cancel, see "How the wizard talks to you"):
2
+
3
+ 1. **Render** the prompt according to `next.interaction.kind` using the relay rules above. For a native kind, invoke the `function` string from that same `interactions` entry — the Claude Code picker, the Grok picker, and the Codex picker are different names, and substituting one for another is a relay-contract failure. **Always append the progress marker to the rendered question label**: suffix it with ` (<next.progress.label>)` — render `progress.label` exactly as the wizard sent it, never recompute it. Example: label `Step 8/11 · 앞으로 3 스텝 남음` → `Select a model (Step 8/11 · 앞으로 3 스텝 남음)`. 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. **The rendered question is the last thing you emit in that turn.** Every host streams assistant text in emission order, so an echo, a restatement, a rationale, or a status line emitted after the question call renders *below* the picker and pushes the question away from where the user answers. Say whatever you need to say before the call, then call and stop.
4
+ **One pending interaction per wizard state.** For native interactions, display the question and options only through the selected question tool, not as a second text list. An asynchronous acknowledgement such as `{accepted: true}` means the question is pending, not answered. Wait for the actual user reply; do not repeat the tool call or fetch and render the same step again while waiting. If ending the turn while awaiting a reply, do not restate the question in the final message. Re-prompt only on explicit user edits or a validation error after submitting an actual answer. This is a lead relay instruction; wizard state validation does not deduplicate host UI emissions.
5
+ 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 `$(...)`):
6
+ ```bash
7
+ okstra wizard step --state-file /var/folders/.../okstra-wizard.AbCd.json --answer preprod
8
+ ```
9
+ If the answer contains spaces or shell metacharacters, wrap it in double quotes around the literal string only — never inside `"$VAR"`.
10
+
11
+ **MANDATORY: empty answers must pass `--answer ""` explicitly.** If the user's reply is the empty string, the call MUST still include the flag with an empty literal value:
12
+ ```bash
13
+ okstra wizard step --state-file /var/folders/.../okstra-wizard.AbCd.json --answer ""
14
+ ```
15
+ Omitting `--answer` entirely is forbidden. The wizard does not read a missing `--answer` flag as "submit empty": without `--answer` or `--no-submit`, `wizard step` returns `ok: false` naming `--answer` and exits 2, and the step is not submitted. Submitting `--answer ""` is the only way to advance past an intentionally-blank step (e.g. "use phase default").
16
+
17
+ **Escaping rule**: if the literal answer contains `"`, escape each occurrence as `\"` inside the double-quoted argument. Empty values must still be `--answer ""` — the flag itself is mandatory, even when the value is empty.
18
+
19
+ **Enforced:** `tests-js/wizard.test.mjs` pins `parseFlags` on the empty literal (`--answer=` parses to the empty string, a value beginning with `--` is kept) and on the `--no-submit` peek that is the only sanctioned way to see the current prompt without submitting; the Node wrapper (`src/commands/execute/wizard.mts`) is what forwards them to Python.
20
+ 3. **Handle result**:
21
+ - `ok: true` → echo `result.echo` to the user on one short line, then loop with `result.next`.
22
+ - `ok: false` → show `result.error` to the user verbatim, then loop with `result.current` (re-prompt the same step). If `result.current` is `null`, do not loop — the current step cannot render (e.g. corrupt Stage Map); surface the error and stop so the user fixes the plan.
@@ -0,0 +1,40 @@
1
+ ## How the wizard talks to you
2
+
3
+ Every wizard call returns JSON. The two shapes you'll see:
4
+
5
+ ```json
6
+ { "ok": true, "echo": "task-group: backend-api",
7
+ "next": { "step": "task_id", "kind": "text", "label": "...", "options": [], "echoTemplate": "...",
8
+ "progress": { "index": 5, "total": 11, "remaining": 6 } } }
9
+ ```
10
+
11
+ Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit it). It holds `index` (1-based number of the **current screen**; pick_group counts as one screen), `total` (the wizard's forward estimate of the full screen count — may grow by a few when the user opens a branch, e.g. role-add or extra role-model slots), `remaining` (`total − index`), and **`label`** — the ready-to-render marker the wizard already composed (e.g. `Step 10/11 · 앞으로 1 스텝 남음`, or `Step 11/11 · 마지막 단계` on the final screen; the suffix is Korean whatever the report language). **Always suffix the rendered prompt with `progress.label` verbatim** — see Step 3. Do not recompute the marker or decide "final step" yourself; only the wizard knows whether more screens follow.
12
+
13
+ ```json
14
+ { "ok": false, "error": "approved plan §1 has 1 unresolved `Blocks=approval` row(s); ...",
15
+ "current": { "step": "approved_plan", "kind": "text", "label": "..." } }
16
+ ```
17
+
18
+ On `ok: false`, re-prompt with the same `current.step` using the error message. The wizard never advances on validation failure; the user retries the same step. **`current` may be `null`** when the current step itself cannot render (e.g. the approved plan's Stage Map is corrupt) — that case is terminal: show `error` and stop, the user must fix the plan file before retrying. Never re-prompt off a `null` `current`.
19
+
20
+ The wizard tells you which relay operation to use via `next.interaction.kind`. Step 1 loads the current registered host's relay contract. Use the matching entry under its `interactions` object, including the runtime-generated navigation and completion options. If the entry is absent, stop instead of inventing a host function or falling back silently. Use the explicit `Legacy text relay compatibility` mapping only when the preflight response has no `relayContract`.
21
+
22
+ - `native-single` → use the current registered host's relay contract for its native single-select function. Pass every option in its original order and submit the selected `options[].value`.
23
+ - `native-multi` → use the relay contract's native multi-select function. Pass every option in its original order and submit the selected values as one CSV answer; submit an empty selection as `--answer ""`.
24
+ - `native-group` → use the relay contract's native grouped-question function once. Preserve every question and option, then submit one JSON object keyed by `questions[].step`; when a grouped question is multi-select, its step value is the selected option values joined as one CSV string, not a JSON array.
25
+ - `numbered-single` → render every option as a 1-based numbered Markdown list and submit the user's next message unchanged.
26
+ - `numbered-multi` → render every option as a 1-based numbered Markdown list and submit the user's comma-separated reply unchanged.
27
+ - `sequential-group` → render every question in order, preserve each complete option list, collect every answer, and submit one JSON object keyed by `questions[].step`.
28
+ - `plain-text` → write `label` as plain text and submit the user's next message unchanged.
29
+ - `kind: "done"` → input collection finished; move to Step 5.
30
+ - `kind: "aborted"` → the user picked abort; the wizard is terminally cancelled. Tell the user on one short line that the run setup was aborted, delete the state file (`rm` with the literal path), and stop this skill — do NOT call `render-args` or `render-bundle` (the wizard rejects `render-args` on an aborted state).
31
+
32
+ When native selection is available, the runtime keeps choices in that control or in same-screen question tabs when the complete selection fits. Otherwise, it returns a complete numbered list: show all labels and descriptions in one message and accept a number, label, or value (comma-separated for multiple choices). This applies to plan confirmation, stages, role counts, and provider/model lists. Do not paginate choices or add a next-page option. Follow the returned `interaction.kind`, including numbered interactions when the user generally prefers a selector. If the declared selector itself fails, follow the relay's `recovery` object. Genuine text steps still collect text.
33
+
34
+ Submit the answer shape required by `interaction.answerProtocol`; do not add normalization beyond the registered relay's explicit mapping. Invalid, out-of-range, or ambiguous answers return `ok: false` and must re-render the same complete interaction.
35
+
36
+ The final `confirm` step is a normal `pick` step with three options — `Proceed` / `Edit` / `Abort`(abort) — and is rendered the same way (no special handling). `Edit` rewinds to any earlier step (including `base-ref`); `Abort` terminally cancels the wizard. The branch/worktree decision the run will actually use (for `implementation`, the **stage worktree** — not the task-key directory) is folded into the Step 4 confirmation summary block as a `worktree` line, so there is no separate branch-confirm prompt.
37
+
38
+ Never invent additional questions. **Never drop, hide, merge, reorder, or truncate** a `pick` / `pick_group` option — relay every `options[]` entry, including the flagged recommendation. Do not collapse a multi-option pick into a "recommended + Enter directly / Other" shortlist. The wizard's arrays are the complete authoritative choice sets, regardless of the current host UI's usual option limit. The run-prompt recommendation rule (1–2 recommendations + Enter directly) shapes the **option set** only for prompts this skill authors itself, never for wizard-provided options — you may not add, drop, or reorder a wizard option to produce a shortlist. It does not excuse you from recommending: before relaying a wizard step whose answer turns on something readable (the carried report, the sidecar the user already wrote, the prior Stage Map), read it, put what you found in the question body, and name which of the wizard's own options you recommend and why. Relaying a step with no context and no recommendation hands the whole question back to the user — see the lifecycle core contract "Asking the user (BLOCKING)".
39
+
40
+ **One recommendation, shown in one place, and it is option 1.** Wizard labels carry no recommendation marker of their own. The option carrying `recommended: true` is what this run computed, and the wizard already placed it first — except on model-selection steps (`role-models:*`, `role-model:*`), which keep provider display order, so the flagged model may sit anywhere in the list. Append ` (추천)` to that option's label when you render it, and to no other. A checkbox step (`multi: true`) may flag several leading options: they are the recommended set, rendered the same way. Your prose recommendation names one of the flagged options. If you believe a different option is right, do not quietly recommend it in prose while the flagged one still reads as recommended on screen: say you disagree, name both, and let the user pick. When no option carries the flag, the run computed nothing for this step — recommend one from what you read and mark that one, and do not present the first option as a default just because it is first. **Enforced:** `scripts/okstra_ctl/wizard/state.py` `Prompt.__post_init__` refuses a step whose free-input option is not last, whose single-select recommendation is not exactly one option, whose recommendation is not placed first on a single-select step or is not the leading run on a checkbox step (both position checks are skipped on `role-models:*` / `role-model:*` steps), or that marks the free-input / abort escape as a recommendation.
@@ -0,0 +1,74 @@
1
+ ## Step 5: Render the task bundle
2
+
3
+ When `next.kind == "done"`, fetch the public wizard outcome:
4
+
5
+ ```bash
6
+ okstra wizard outcome --state-file /var/folders/.../okstra-wizard.AbCd.json
7
+ ```
8
+
9
+ Output: `{ok: true, outcome: {renderArgv: ["--lead-runtime", "...", ...], renderArgs: {...}, orchestration: {chainStages: "..."}, persistActions: [...], confirmationText: "..."}}`.
10
+
11
+ `renderArgv` is the canonical ordered `okstra render-bundle` argument list. Pass its tokens verbatim and in order. Repeated `--role-count` and `--role-model` flags are intentional. `renderArgs` is a compatibility and inspection view only; do not reconstruct an invocation from it. `orchestration` holds signals this skill acts on itself — never pass its entries to `render-bundle`.
12
+
13
+ `userAuthorization`, when present, preserves the confirmation prompt actually emitted by this wizard and the relayed `proceed` response. Its `--user-authorization-json` token carries that record into the run manifest; preserve it unchanged. Do not synthesize a receipt for older confirmed states. Include the translator's disclosed provider/model and report material when citing this record in host execution requests, including `report-finalize`. The record documents task scope; it does not grant host execution privileges.
14
+
15
+ Before retrying a worker in an existing run, inspect its last invocation attempt: await running work; append an attempt only after `failed-no-mutation` within the retry budget. Preserve every other terminal attempt. `okstra team dispatch` routes the legacy declaration-only implementer failure to a separate same-model evidence-recovery invocation; collect its result before independent verification. Other terminal failures need `okstra agent-prompt materialize` with a new `--invocation-id`, distinct `--prompt`/`--result` paths, and unchanged assignment/model for a bounded correction. Do not restart completed implementation or request renewed approval for unchanged scope. Enforcement: `dispatch_core._next_attempt` and `execution_manifest._validate_next_attempt`.
16
+
17
+ Run every `outcome.persistActions[]` entry BEFORE `render-bundle`. The only supported action is:
18
+
19
+ ```json
20
+ {"command":"config.set","key":"pr-template-path","scope":"project|global","value":"<path>"}
21
+ ```
22
+
23
+ Execute the matching command for `scope`:
24
+
25
+ ```bash
26
+ okstra config set pr-template-path "<value>" --scope project
27
+ okstra config set pr-template-path "<value>" --scope global
28
+ ```
29
+
30
+ If an action has an unknown `command`, `key`, or `scope`, stop and report the wizard output instead of inventing a command.
31
+
32
+ Before rendering the next phase's bundle — and between worker rounds within a phase (reverify/critic/gapverify batches), after you have collected that round's results and token usage and before you dispatch the next round — close the panes of the dispatches that finished in the prior round so they do not accumulate: `okstra team reclaim --project-root <projectRoot> --run-manifest <RUN_MANIFEST_PATH>` closes them, records `phase-batch-cleanup panes=<n>` with the number it closed, and prints that `PROGRESS:` line last — emit it as printed and do not call `okstra lead-progress append` for it. The command reads each dispatch's recorded status, so an in-progress worker keeps its pane whichever moment you call it. It closes only the panes okstra opened and recorded — a pane the harness opened for its own teammate carries no recorded id and is not okstra's to close. `shutdown_request` alone only idles the agent and frees no pane, so it stays part of the run-end sequence for roster/token hygiene. A `cli-wrapper` run holds no pane at all and `team reclaim` refuses it, so record the checkpoint there with `okstra lead-progress append … --phase phase-batch-cleanup --field panes=0`.
33
+
34
+ Before you ask the user for any approval, clarification, or decision after workers have been dispatched, run `okstra team reclaim … --gate` first: it closes the finished panes and prints `PROGRESS: phase-gate-cleanup panes=<n>` for you to emit, without recording a batch cleanup. Then `TaskStop` each completed worker. A `TaskStop` by itself idles the task but leaves the pane open — the `team reclaim` call is what closes it. This keeps a user gate from being shown while finished worker panes remain; in-progress dispatches keep their panes. Then follow `prompts/lead/okstra-lead-contract.md` "User confirmation before an approval blocker": read cited plan items, worker findings, and files before asking, and ask in the user's language with each option's outcome.
35
+
36
+ Build the `okstra render-bundle` invocation from `outcome.renderArgv`, passing every token verbatim and in order (including empty strings — they are intentional `use phase default` markers).
37
+
38
+ Before invoking it, follow the active host relay's execution-permission guidance. For Codex, read "Permission before bundle preparation" in the relay already selected at preflight: bundle preparation can write protected Git metadata while creating a branch and worktree, even with `--render-only`. Apply this guidance to the first invocation and each subsequent phase or stage preparation.
39
+
40
+ Analysis sidetracks therefore forward wizard-owned tokens such as `--analysis-target "<value>"` and `--evidence-inputs "<value>"` when they are present. These are examples of the verbatim token rule, not a separate hard-coded argument list.
41
+
42
+ Step 3's empty-answer and escaping rules apply verbatim: every flag in `renderArgv` whose following value is the empty string MUST still be passed explicitly (e.g. `--related-tasks ""`, `--directive ""`) — the wizard's intent is always "flag present with empty value", even where prepare's default happens to be the same empty value.
43
+
44
+ `renderArgv` already contains exactly one `--lead-runtime <host-runtime>` pair. Do not add or replace it. Do not enumerate a fixed provider list in this skill because the wizard and role model pool own the ordered tokens.
45
+
46
+ ```bash
47
+ okstra render-bundle \
48
+ <outcome.renderArgv token 1> \
49
+ <outcome.renderArgv token 2> \
50
+ <each remaining outcome.renderArgv token in order>
51
+ ```
52
+
53
+ `render-bundle` auto-supplies `--workspace-root` and forces `--render-only`. Stdout prints `okstra task root:`, `okstra instruction-set:`, `okstra run manifest:`, and the full rendered lead prompt. Parse the labelled lines for `TASK_ROOT`, `INSTRUCTION_SET_PATH`, and `RUN_MANIFEST_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).
54
+
55
+ Before acting as the lead, read `resources.leadPromptMetadataPath` from the run
56
+ manifest and execute the host-native specification-link gate:
57
+
58
+ ```bash
59
+ okstra agent-prompt record-dispatch \
60
+ --project-root <projectRoot> \
61
+ --run-manifest <RUN_MANIFEST_PATH> \
62
+ --metadata <projectRoot/resources.leadPromptMetadataPath> \
63
+ --enforcement-mode host-native-spec-link-gate
64
+ ```
65
+
66
+ Do not continue from the rendered prompt if this command fails. This record
67
+ associates the current-session lead with a verified invocation specification;
68
+ it does not claim that the host exposed or attested the delivered prompt bytes.
69
+
70
+ The python function underneath allocates the run sequence number and writes `run-context-*.json` under the per-task lock (`~/.okstra/.locks/<task-key>.lock`), then writes `run-inputs-*.json` + all manifests + discovery files outside that lock, and registers the run in `~/.okstra/recent.jsonl` with status `prepared` under the central `~/.okstra/.lock` (a registration failure is reported on stderr and does not fail prepare).
71
+
72
+ **Not enforced:** `tests-js/wizard.test.mjs` pins the argv the wizard produces (`orderedRenderArgv` preserves the Python role selection bytes and repeated role order), not the argv you pass. Prepare's parser defaults most flags to `""` (e.g. `--directive`), so a dropped empty value is indistinguishable from its default and nothing rejects it — passing every token verbatim is on you.
73
+
74
+ You can delete the literal state-file path after this point — its job is done. Invoke `command rm` with the literal path (e.g. `command rm /var/folders/.../okstra-wizard.AbCd.json`), not a shell variable. `command` is what keeps a `rm='rm -i'` alias from turning this into a confirmation prompt nobody is there to answer.