okstra 0.23.0 → 0.25.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.
@@ -68,7 +68,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
68
68
  " - trade-off matrix across options (complexity, risk, reversibility, test cost, rollout cost) and recommended option with rationale tied to isolation / single-responsibility / YAGNI principles\n"
69
69
  " - bite-sized stepwise execution order for the recommended option (each step ~2-5 min, exact file paths and commands, TDD ordering when applicable, no placeholders)\n"
70
70
  " - dependency / migration risk assessment, validation checklist (pre / mid / post with exact commands), rollback strategy with revert path and trigger signal\n"
71
- " - `Open Questions` block listing every unresolved ambiguity\n"
71
+ " - every unresolved ambiguity registered as a `Blocks=approval` row in the `## 5. Clarification Items` table (do NOT create a separate `Open Questions` block under `4.5.x` the unified table is the single home)\n"
72
72
  " - explicit `User Approval Request` block awaiting human approval\n"
73
73
  " - self-review confirmation (spec coverage, placeholder scan, internal consistency, ambiguity, scope)"
74
74
  ),
@@ -90,8 +90,19 @@ def collect(team_state_path: Path, project_root: Path | None = None) -> dict:
90
90
  # when TeamCreate succeeded); only fall back to the `okstra-<task-id>`
91
91
  # convention if team-state did not record one. Matching downstream is
92
92
  # case-insensitive so either casing works.
93
+ # Lead-written teamName lives at one of two paths depending on which
94
+ # version of the contract the run was authored under:
95
+ # - nested: state.team.teamName (current documented schema)
96
+ # - root: state.teamName (older convention; still common in
97
+ # actual runs because the team
98
+ # contract docs did not pin the
99
+ # location until v0.24)
100
+ # Read both; whichever is non-empty wins. The fallback derives a short
101
+ # team name from task-id only and routinely mis-matches multi-segment
102
+ # task keys (e.g. `okstra-fontsninja-classifier-v2:DEV-9389:DEV-9389`),
103
+ # so it is a last resort.
93
104
  state_team = (state.get("team") or {})
94
- team_name = state_team.get("teamName") or ""
105
+ team_name = state_team.get("teamName") or state.get("teamName") or ""
95
106
  if not team_name:
96
107
  task_id = task_key.rsplit(":", 1)[-1] if task_key else ""
97
108
  team_name = f"okstra-{task_id}" if task_id else ""
@@ -169,6 +169,7 @@ Place this section immediately after the execution status table.
169
169
  ```
170
170
 
171
171
  Token Summary Generation Rules:
172
+ - **You author this section in Phase 6, BEFORE Phase 7 runs the collector.** Therefore you MUST leave the 10 placeholders (`{{LEAD_TOTAL_TOKENS}}`, `{{LEAD_BILLABLE_TOKENS}}`, `{{LEAD_COST_USD}}`, `{{WORKER_TOTAL_TOKENS}}`, `{{WORKER_BILLABLE_TOKENS}}`, `{{WORKER_COST_USD}}`, `{{GRAND_TOTAL_TOKENS}}`, `{{GRAND_BILLABLE_TOKENS}}`, `{{GRAND_COST_USD}}`, `{{CLI_COST_USD}}`) verbatim in the table cells — `okstra-token-usage.py --substitute-final-report` will fill them in Phase 7. Never replace any of these cells with a literal number, `not-collected`, `N/A`, `--`, `0`, or any other sentinel: that erases the substitution target, and the report ships with no token numbers. Also do not insert a note like "Phase 7 has not run yet" — the report is read AFTER Phase 7, so that statement is wrong on arrival.
172
173
  - All values come from `usageSummary` (populated by `scripts/okstra-token-usage.py` at the start of Phase 7). Do not estimate or invent.
173
174
  - **Lead** row: `usageSummary.leadTotalTokens` / `usageSummary.leadBillableEquivalentTokens` / `usageSummary.estimatedCostUsd.lead`.
174
175
  - **Worker 합계** row: `usageSummary.workerTotalTokens` / `usageSummary.workerBillableEquivalentTokens` / `usageSummary.estimatedCostUsd.claudeWorkers`.
@@ -223,7 +224,7 @@ Skipping this file because "the real report is in `reports/`" is wrong. Both fil
223
224
 
224
225
  Section numbering matches `okstra-final-report.template.md`. Section 0 is the carry-in reconciliation that runs first when a clarification response was provided; sections 1–7 follow the template's main body order.
225
226
 
226
- 0. **Clarification Response Carried In** - if `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty, read `instruction-set/clarification-response.md`, reconcile every prior `Q*` row, and record the outcome (`resolved`/`obsolete`) plus the new evidence in this section before drafting the verdict
227
+ 0. **Clarification Response Carried In** - if `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty, read `instruction-set/clarification-response.md`, walk every `C-*` row of the prior report's `## 5. Clarification Items` table, reconcile each one against new evidence, and record the outcome (`resolved`/`obsolete`) plus the citation in this section before drafting the verdict. If the prior report uses the deprecated `4.5.9 Open Questions` / `5.1` / `5.2` layout with `OQ-*`/`A*`/`Q*` IDs, follow the legacy-carry-in mapping rule in `final-report-template.md` section 0.
227
228
  1. **Problem or Verification Summary** - Key summary based on the brief and data (3–5 bullet points)
228
229
  2. **Cross Verification Results** (Use 4 categories when convergence is enabled, per `okstra-convergence`)
229
230
  - Round History sub-table (convergence-enabled runs only): one row per executed round with columns `Round | inputQueueSize | resolvedCount | carriedForwardCount | dispatches (worker:status:durationMs) | skippedWorkers (worker:reason)`. Add a one-line note immediately under the table with `round2SkippedReason: <value>` (always present, even when `"not-skipped"`). Pull all values verbatim from `convergence-<task-type>-<seq>.json`.
@@ -238,7 +239,7 @@ Section numbering matches `okstra-final-report.template.md`. Section 0 is the ca
238
239
  - If explicit expected values are present in `reference-expectations.md`, specify whether they match or differ from the expected values in config files / deployment manifests
239
240
  - Supporting evidence or alternative interpretations
240
241
  5. **Missing Information and Risks** - Uncertain/I don't know items
241
- 6. **Clarification Requests for the Next Run** - structured Q&A table the user fills inline before reruns
242
+ 6. **Clarification Items** - single unified table (`C-001`, `C-002`, ...) the user fills inline before reruns. Columns: `ID`, `Ticket ID`, `Kind` (`material` / `decision` / `data-point`), `Statement`, `Expected form`, `Blocks` (`approval` / `next-phase` / `none`), `Status`, `User input`. Replaces the legacy `4.5.9 Open Questions` / `5.1` / `5.2` triple; never create those sub-sections — same item appearing in two places is the failure mode this table prevents.
242
243
  - Required for `task-type` `error-analysis` and `requirements-discovery` whenever blocking uncertainty remains
243
244
  - Optional for other task-types; explicitly state "no clarification needed" when none
244
245
  - Follow the table format from `final-report-template.md` exactly (columns: Question ID, Blocking, Why this matters, Question, Expected answer shape, Status, Answer)
@@ -5,13 +5,13 @@ description: Use when the user wants to start an okstra task (cross-verification
5
5
 
6
6
  # OKSTRA Run (in-session)
7
7
 
8
- Launch an okstra task — gather inputs interactively, render the full task bundle through the single python entrypoint, then take over as `Claude lead` in the current session.
8
+ Launch an okstra task — gather inputs interactively via the **wizard state machine** (`okstra wizard ...`), then take over as `Claude lead` in the current session.
9
9
 
10
- **Single authority**: this skill and `okstra.sh` both call the exact same python function `okstra_ctl.run.prepare_task_bundle()`. The skill does NOT shell out to `okstra.sh`that would create a second orchestration path and reintroduce env-var leakage between the parent claude session and child bash.
10
+ **Single authority**: this skill drives `okstra wizard`, which owns every step (ordering, branching, validation). The skill is just a thin prompt-relay loopit never decides "what to ask next" itself. If the flow needs to change, edit `scripts/okstra_ctl/wizard.py`, not this file.
11
11
 
12
12
  ## When to Use
13
13
 
14
- - The user is already inside a Claude Code session and asks to start an okstra task ("run okstra here", "start an error-analysis on this branch", "okstra implementation-planning for INV-1234").
14
+ - The user is inside a Claude Code session and asks to start an okstra task ("run okstra here", "start an error-analysis on this branch", "okstra implementation-planning for INV-1234").
15
15
  - Continue an existing task (next phase) without leaving the current claude session.
16
16
 
17
17
  ## When NOT to Use
@@ -20,51 +20,47 @@ Launch an okstra task — gather inputs interactively, render the full task bund
20
20
  - User wants status only — use `okstra-status`.
21
21
  - User wants past runs — use `okstra-history`.
22
22
 
23
- ## Prompt convention (use the right tool for the right input shape)
23
+ ## How the wizard talks to you
24
24
 
25
- `AskUserQuestion` always renders a picker UI with a forced auto-attached `Other` option. While the user types into `Other`, the picker re-renders and the experience feels out of sync. So:
25
+ Every wizard call returns JSON. The two shapes you'll see:
26
26
 
27
- - **Use `AskUserQuestion` ONLY when the answer is a fixed pick from a short option set** (2–4 distinct, mutually exclusive choices). Examples in this skill: task-type choice (Step 4), executor provider (claude/codex/gemini), model picker (default/opus/sonnet/haiku per provider), Use defaults vs Customize, Proceed vs Edit confirmation.
28
- - **For pure free-text inputs** (file paths, task identifiers, CSV strings, free directives, branch names typed by the user) **do NOT use `AskUserQuestion`**. Instead, write a plain text message (e.g. `"Task group (예: backend-api, INV-1234)을 알려주세요. 빈 줄이면 취소."`) and consume the user's NEXT message as the answer. Then validate and re-prompt with another plain text message on failure.
29
- - **For "menu + free-text" places** (base-ref pickers, PR base branch) — show the menu with `AskUserQuestion` listing only the canonical options + a literal option labeled `직접 입력`. When the user picks `직접 입력`, follow up with a **separate** plain text prompt and consume the next user message. Do NOT rely on `Other` auto-text inside the picker — its re-render is the root cause of the lag.
27
+ ```json
28
+ { "ok": true, "echo": "task-group: backend-api",
29
+ "next": { "step": "task_id", "kind": "text", "label": "...", "options": [], "echoTemplate": "..." } }
30
+ ```
30
31
 
31
- Echo each captured answer on one short line (e.g. `task-group: backend-api`) so the user sees what was registered, regardless of which prompt shape was used.
32
+ ```json
33
+ { "ok": false, "error": "approved plan has no APPROVED marker: ...",
34
+ "current": { "step": "approved_plan", "kind": "text", "label": "..." } }
35
+ ```
32
36
 
33
- ## Authority Files (disk-only no env var caching for per-run identity)
37
+ 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.
34
38
 
35
- Every step reads disk afresh. The `OKSTRA_*` env vars below identify the
36
- **runtime installation** (stable across runs) — they are NOT per-task identity.
39
+ The wizard tells you *which UI to use* via `kind`:
37
40
 
38
- - `~/.okstra/version` okstra runtime version stamp
39
- - `<PROJECT_ROOT>/.project-docs/okstra/project.json`
40
- - `<PROJECT_ROOT>/.project-docs/okstra/discovery/{task-catalog,latest-task}.json`
41
- - `<task-root>/task-manifest.json`
41
+ - `kind: "pick"` render `AskUserQuestion` with `label` and `options[].label` (use `options[].value` to call `--answer`).
42
+ - `kind: "text"` → write `label` as a plain text message and consume the user's NEXT message as the answer.
43
+ - `kind: "done"` → input collection finished; move to Step 5.
42
44
 
43
- ## Step 0: Verify okstra runtime + project setup
45
+ Never invent additional questions. Never reorder. Never use `AskUserQuestion` for `text` prompts — the wizard explicitly chose `text` to avoid the picker-Other re-render lag.
44
46
 
45
- Do NOT hard-code or guess any okstra path. Every run loads them fresh from
46
- the single authority — `okstra`:
47
+ ## Step 1: Verify okstra runtime + project setup
47
48
 
48
49
  ```bash
49
- # 0) Resolve runner: prefer PATH (npm-installed) over npx (avoids per-call registry lookup).
50
- # If the user installed okstra via npm, they control upgrade timing — do not force @latest.
51
50
  if command -v okstra >/dev/null 2>&1; then
52
51
  OKSTRA_CMD="okstra"
53
52
  else
54
53
  OKSTRA_CMD="npx -y okstra@latest"
55
54
  fi
56
55
 
57
- # 1) Ensure runtime is fresh (idempotent, cached when up-to-date)
58
56
  $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
59
57
  echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
60
58
  exit 1
61
59
  }
62
60
 
63
- # 2) Load all runtime paths into the shell as OKSTRA_* exports
64
61
  eval "$($OKSTRA_CMD paths --shell)"
65
62
  export PYTHONPATH="$OKSTRA_PYTHONPATH"
66
63
 
67
- # 3) Verify the current project has okstra metadata (project.json + projectId)
68
64
  OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
69
65
  echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
70
66
  echo "$OKSTRA_PROJECT_INFO" >&2
@@ -72,250 +68,101 @@ OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
72
68
  }
73
69
  ```
74
70
 
75
- After Step 0 the following are guaranteed:
71
+ If `OKSTRA_PROJECT_INFO.ok` is `false`, ask the user with a **plain text prompt** for an absolute project-root path; rerun `okstra check-project --cwd <path>`. Re-prompt with plain text on failure.
76
72
 
77
- | Variable | Meaning |
78
- |---|---|
79
- | `$OKSTRA_WORKSPACE` | passed to python as `workspace_root` (prompts/, templates/, validators/, agents/ root) |
80
- | `$OKSTRA_AGENTS_DIR` | source dir of worker `*.md` (subagent definitions) |
81
- | `$OKSTRA_PYTHONPATH` | already exported as `PYTHONPATH` |
82
- | `$OKSTRA_BIN` | bash entrypoints (`okstra.sh`, codex/gemini exec wrappers) |
83
- | `$OKSTRA_HOME` | `~/.okstra` (recent.jsonl, locks, projects/, archive/) |
84
- | `$OKSTRA_PROJECT_INFO` | JSON: `{ok, projectRoot, projectJsonPath, projectId}` — parse and reuse instead of re-resolving in Step 1 |
73
+ Parse `projectRoot` and `projectId` from `OKSTRA_PROJECT_INFO`.
85
74
 
86
- ## Step 1: Resolve PROJECT_ROOT and projectId
87
-
88
- Prefer `$OKSTRA_PROJECT_INFO` from Step 0 — it already carries `{ok, projectRoot, projectJsonPath, projectId}`. Only re-resolve when that JSON's `ok` is false (cwd outside an okstra project):
75
+ ## Step 2: Initialize the wizard
89
76
 
90
77
  ```bash
91
- okstra check-project --cwd "$(pwd)"
92
- ```
78
+ STATE_FILE="$(mktemp -t okstra-wizard.XXXX.json)"
93
79
 
94
- - If `ok: true`: read `projectRoot` and `projectId` from the JSON.
95
- - If `ok: false`: ask the user with a **plain text prompt** (not `AskUserQuestion` — this is pure free text per the convention above) for an absolute project-root path; rerun with `okstra check-project --cwd <their input>`. Re-prompt with another plain text message on failure.
96
-
97
- ## Step 2: Choose task — existing vs new
98
-
99
- ```bash
100
- okstra task-list --project "$PROJECT_ROOT"
80
+ okstra wizard init \
81
+ --state-file "$STATE_FILE" \
82
+ --project-root "$projectRoot" \
83
+ --project-id "$projectId"
101
84
  ```
102
85
 
103
- Output is JSON `{ok, projectRoot, tasks: [...], latest: {...}|null}`.
104
-
105
- Use `AskUserQuestion`:
106
-
107
- - **Label**: "Which task?"
108
- - **Options**: each existing task with label `"<taskKey> · <currentPhase or taskType> · next: <nextRecommendedPhase>"`; mark the `latest` entry with `(latest)`. Final option: `"Start a brand-new task"`. Limit to 8 candidates per page; add `"More..."` if more exist.
109
-
110
- For an existing pick, read its `task-manifest.json` to capture `taskType` and `workflow.nextRecommendedPhase`.
111
-
112
- ## Step 3: For new tasks — collect identity
113
-
114
- Skip if continuing existing.
115
-
116
- Use **plain text prompts** (one at a time — write the message and consume the user's next reply; do NOT use `AskUserQuestion` for these per the convention above):
117
-
118
- 1. `"Task group 을 알려주세요 (예: backend-api, INV-1234, refactor)"` → `task_group`
119
- 2. `"Task id 를 알려주세요 (예: login-error-analysis, dev-9043)"` → `task_id`
120
-
121
- Validate that slugified `task_group` and `task_id` each contain at least one alphanumeric character. On failure, re-prompt with another plain text message stating the validation failure.
86
+ Output: the same `{ok, next}` JSON described above. The first `next` is always `step: "task_pick"`.
122
87
 
123
- ## Step 4: Choose task-type
88
+ ## Step 3: Run the prompt loop
124
89
 
125
- `AskUserQuestion` with six fixed options:
90
+ Repeat until `next.kind == "done"`:
126
91
 
127
- | Option | Description |
128
- |---|---|
129
- | `requirements-discovery` | Classify request and route to next safe phase |
130
- | `error-analysis` | Evidence-based root-cause analysis (no code changes) |
131
- | `implementation-planning` | Plan options + request user approval |
132
- | `implementation` | Execute approved plan (requires `--approved-plan`) |
133
- | `final-verification` | Acceptance + residual-risk review |
134
- | `release-handoff` | Drive commit / push / PR with user-selected actions after `accepted` final-verification |
92
+ 1. **Render** the prompt according to `kind`:
93
+ - `pick` → `AskUserQuestion` with `label` and `options`. The user's chosen option's `value` is the answer string.
94
+ - `text` plain text message containing `label`. Consume the user's next reply verbatim as the answer string (empty reply = empty string).
95
+ 2. **Submit** the answer:
96
+ ```bash
97
+ okstra wizard step --state-file "$STATE_FILE" --answer "$ANSWER"
98
+ ```
99
+ 3. **Handle result**:
100
+ - `ok: true` → echo `result.echo` to the user on one short line, then loop with `result.next`.
101
+ - `ok: false` → show `result.error` to the user verbatim, then loop with `result.current` (re-prompt the same step).
135
102
 
136
- For existing tasks, present `nextRecommendedPhase` as the first option (recommended default).
103
+ That is the entire interactive flow. The wizard handles:
137
104
 
138
- If `implementation` chosen, ask two more questions in order:
139
- - **Plain text prompt** (file path is pure free text): `"approved final-report.md 경로를 알려주세요 (APPROVED 마커가 있어야 합니다)"`. The underlying python `prepare_task_bundle` re-validates the marker, but you can pre-check with `grep`. Re-prompt with plain text on failure.
140
- - **`AskUserQuestion`** with three options (`claude` / `codex` / `gemini`) only this provider mutates project files; the other two run as read-only verifiers. Default `claude` (or `OKSTRA_DEFAULT_EXECUTOR` if set). Pass the answer through `PrepareInputs.executor`.
105
+ - new-vs-existing task split, task-group / task-id slug validation,
106
+ - task-type pick (with `nextRecommendedPhase` surfaced as recommended for existing tasks),
107
+ - brief path (with `유지 / 변경` for existing tasks),
108
+ - base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
109
+ - `implementation`-only sub-flow: approved-plan path (APPROVED marker check) + executor pick,
110
+ - `Use defaults / Customize` branch with profile-aware worker/model questions,
111
+ - `release-handoff` PR template override + persist scope,
112
+ - final `Proceed / Edit` confirmation; on `Edit` the wizard asks which step to rewind to and clears every later answer.
141
113
 
142
- ## Step 4.6: Base ref for the task worktree (first phase only)
114
+ Do not second-guess the wizard. If the next prompt seems out of place, the bug is in `wizard.py`, not in your interpretation of the user's input.
143
115
 
144
- `okstra prepare` provisions a per-task git worktree on first phase of a task-key
145
- and reuses it on every subsequent phase. The base ref of that worktree is the
146
- **user's choice**, not the caller's current `HEAD`, so the worktree never
147
- silently inherits an unrelated branch you happen to be checked out on.
116
+ ## Step 4: Show the confirmation block before the final Proceed
148
117
 
149
- First, decide whether to ask:
118
+ When `next.step == "confirm"`, before relaying the picker, fetch the human-readable selection summary:
150
119
 
151
120
  ```bash
152
- okstra worktree-lookup "<project-id>" "<task-group>" "<task-id>"
121
+ okstra wizard confirmation --state-file "$STATE_FILE"
153
122
  ```
154
123
 
155
- Output JSON: `{ok: true, entry: null}` means no active worktree **ASK**. A
156
- non-null `entry` with `status: "active"` → **REUSE**.
124
+ Output: `{ok: true, text: "선택 확인:\n task-type : ...\n ..."}`. Print `text` to the user, then render the `confirm` picker (Proceed / Edit).
157
125
 
158
- - `REUSE` the registered worktree is reused; set `base_ref=""` and skip the
159
- question (the registered base is authoritative).
160
- - `ASK` → this is the first phase for this task-key. Continue.
126
+ ## Step 5: Render the task bundle
161
127
 
162
- Use the **menu + free-text two-step pattern** (per the convention above):
163
-
164
- 1. `AskUserQuestion` with label `"이 task worktree 의 base branch?"` and exactly these single-select options (NO auto-Other typing — the literal `직접 입력` option is the typed-input escape hatch):
165
- 1. `main` (recommended)
166
- 2. `dev`
167
- 3. `staging`
168
- 4. `preprod`
169
- 5. `prod`
170
- 6. `직접 입력`
171
- 2. If the user picks `직접 입력`, follow up with a **plain text prompt**: `"base ref 를 입력해주세요 (branch, tag, 또는 short/full SHA)"`. Consume the user's next message as the chosen ref.
172
- 3. Otherwise the picked option label is the chosen ref directly.
173
-
174
- Validate the chosen ref exists in the MAIN worktree before continuing:
128
+ When `next.kind == "done"`, fetch the final args:
175
129
 
176
130
  ```bash
177
- git -C "$(git -C "$PROJECT_ROOT" rev-parse --path-format=absolute --git-common-dir | xargs dirname)" \
178
- rev-parse --verify --quiet "<chosen-ref>^{commit}" >/dev/null \
179
- || { echo "ref not found locally: <chosen-ref>"; exit 1; }
180
- ```
181
-
182
- On failure, re-prompt with a plain text message (or return to step 1's
183
- menu if the user wants to pick a different canonical branch). Echo the
184
- resolved short SHA back to the user (`base 확정: <ref> (<short-sha>)`)
185
- and capture `base_ref=<chosen-ref>` for Step 7.
186
-
187
- ## Step 5: Brief path
188
-
189
- - New task: **plain text prompt** (file path is pure free text per the convention) `"task brief markdown 의 경로를 알려주세요 (project root 기준 상대경로 또는 절대경로)"`. Consume the user's next message; verify the file exists; on failure, re-prompt with another plain text message.
190
- - Existing task: default to the manifest's `taskBriefPath`. Show it; ask `AskUserQuestion` `"기존 경로를 유지할까요?"` with options `유지` / `변경`. On `변경`, follow up with a plain text prompt for the new path.
191
-
192
- ## Step 6 (optional): Directive / workers / models / related / clarification
193
-
194
- Single `AskUserQuestion` first: `"기본 워커/모델로 진행할까요, 아니면 커스터마이즈할까요?"` (options: `Use defaults`, `Customize`).
195
-
196
- - `Use defaults` → all overrides remain empty.
197
- - `Customize` → the prompts you ask depend on the `task_type` chosen in Step 4. Blank answer always means "use default". Never call the prompt label "worker CSV" — use plain Korean labels as shown below.
198
-
199
- ### Model selection options (used by 6a and 6b)
200
-
201
- All model prompts MUST use `AskUserQuestion` with a fixed option list — never free text. This prevents typos like `gpt-5.5-high` (a non-existent model) reaching the manifest. The options below are derived from `scripts/okstra_ctl/models.py` `*_MAPPING` and show "default + 3 latest". Blank/`default` means "use phase default".
202
-
203
- - **Claude (lead / claude-worker / report-writer)** options: `default`, `opus`, `sonnet`, `haiku`
204
- - **Codex (codex-worker)** options: `default`, `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`
205
- - **Gemini (gemini-worker)** options: `default`, `gemini-3-pro-preview`, `gemini-3-flash-preview`, `auto`
206
-
207
- When the user picks `default`, pass an empty string to the corresponding `--*-model` flag. Pick any other option ⇒ pass it verbatim. If the user truly needs a value outside the list (e.g. a pinned long-form id), they can use the question's built-in `Other` to type it — but the four canonical options cover the supported set, so `Other` should be rare.
208
-
209
- ### 6a. `implementation` phase (executor-driven)
210
-
211
- In this phase the roster is fixed by the profile (executor + two verifiers + report-writer). The Step 4 `executor` answer already determines who mutates code; verifier models use phase-specific defaults (`Claude verifier`=sonnet, `Codex verifier`=gpt-5.5, `Gemini verifier`=auto). So ask **only three model prompts** (each via `AskUserQuestion` with options from the table above), plus directive/related/clarification:
212
-
213
- 1. `AskUserQuestion` `"리더(Claude lead) 모델?"` (Claude options) → `lead_model`
214
- 2. `AskUserQuestion` `"실행자({executor-provider}) 모델?"` with options matching the executor's provider (Claude / Codex / Gemini list above) → maps to `claude_model` / `codex_model` / `gemini_model`. The other two provider model fields stay empty (verifiers use defaults).
215
- 3. `AskUserQuestion` `"리포트 작성자(report-writer) 모델?"` (Claude options) → `report_writer_model`
216
- 4. **Plain text prompt** (free text) `"추가 directive 가 있으면 적어주세요 (없으면 빈 줄)"` → `directive`. Consume the user's next message verbatim; an empty line means "no directive".
217
- 5. **Plain text prompt** (free text) `"관련 task id 목록을 쉼표로 구분해서 적어주세요 (없으면 빈 줄)"` → `related_tasks_raw`.
218
-
219
- Do NOT ask for `workers_override` in implementation — the profile's required roster must be preserved (verifier slots are mandatory). Leave `workers_override=""`.
220
-
221
- ### 6b. Other phases (`requirements-discovery`, `error-analysis`, `implementation-planning`, `final-verification`, `release-handoff`)
222
-
223
- **Before asking any worker/model question, resolve the profile's allowed roster:**
224
-
225
- ```python
226
- from okstra_ctl.workers import resolve_profile_workers
227
- profile_workers = resolve_profile_workers(Path("<OKSTRA_PROMPTS_PROFILES_DIR>/<task-type>.md"))
131
+ okstra wizard render-args --state-file "$STATE_FILE"
228
132
  ```
229
133
 
230
- This is the **only** set of worker IDs you may show or ask about. Never offer
231
- workers outside this list. Special cases:
232
-
233
- - If `profile_workers` is empty (e.g., `release-handoff` is lead-only with no
234
- `- Required workers:` block), **skip the worker question and all
235
- worker-model questions entirely** — only ask lead model, directive, related,
236
- clarification. The backend forces `workers=[]` for these profiles.
237
- - Otherwise, the worker question must enumerate **only** `profile_workers` —
238
- do NOT show `claude, codex, gemini, report-writer` blindly.
239
-
240
- Ask each in turn. **Model prompts use `AskUserQuestion`** with the fixed option lists above. **All other prompts use plain text messages** (do NOT wrap free-text inputs in `AskUserQuestion` — the auto-Other re-render lag is what we're avoiding). Skip any worker-model prompt whose worker is not in `profile_workers`.
241
-
242
- 1. (only when `profile_workers` is non-empty) **Plain text prompt** `"참여 워커 목록을 쉼표로 구분해서 적어주세요. 빈 줄이면 프로필 기본값 <profile_workers_csv> 을 그대로 씁니다. 사용 가능한 워커: <profile_workers_csv>"` → `workers_override`. Validate the answer is a subset of `profile_workers`; on failure, re-prompt with another plain text message. (Backend also rejects violations with `WorkersError`.)
243
- 2. `AskUserQuestion` `"리더(Claude lead) 모델?"` (Claude options) → `lead_model`
244
- 3. (only if `claude` ∈ resolved workers) `AskUserQuestion` `"claude 워커 모델?"` (Claude options) → `claude_model`
245
- 4. (only if `codex` ∈ resolved workers) `AskUserQuestion` `"codex 워커 모델?"` (Codex options) → `codex_model`
246
- 5. (only if `gemini` ∈ resolved workers) `AskUserQuestion` `"gemini 워커 모델?"` (Gemini options) → `gemini_model`
247
- 6. (only if `report-writer` ∈ resolved workers) `AskUserQuestion` `"리포트 작성자 모델?"` (Claude options) → `report_writer_model`
248
- 7. `AskUserQuestion` `"추가 directive (선택, 빈 칸 가능)"` (free text) → `directive`
249
- 8. `AskUserQuestion` `"관련 task id 목록, 쉼표 구분 (선택, 빈 칸 가능)"` (free text) → `related_tasks_raw`
250
- 9. `AskUserQuestion` `"clarification-response 파일 경로 (follow-up 시에만, 빈 칸 가능)"` (free text) → `clarification_response_path`
251
- 10. (only when `task_type == "release-handoff"`) **Plain text prompt** `"PR 본문 템플릿 경로 1회성 override (빈 줄이면 project.json → ~/.okstra/config.json → 스킬 디폴트 순으로 자동 해석)"` → `pr_template_path`. The backend (`okstra_ctl.pr_template.resolve_pr_template_path`) validates the file exists and surfaces `PrTemplateError` on failure.
252
- - **Persist follow-up** (only when the user typed a non-empty path AND it differs from any currently-registered project/global value): ask `AskUserQuestion` `"방금 입력한 경로를 영구 저장할까요?"` with three options:
253
- 1. `이번 run 만 (1회성)` — proceed with the override; do NOT touch project.json or global config.
254
- 2. `프로젝트에 저장 (project scope)` — run `okstra config set pr-template-path "<path>" --scope project` and use the override for this run too.
255
- 3. `전역에 저장 (global scope)` — run `okstra config set pr-template-path "<path>" --scope global` (must be absolute or `~/`-prefixed; if not, re-ask with a plain text prompt for an absolute version) and use the override for this run too.
256
- - Skip the persist follow-up entirely when the user left the override blank, or when the typed value matches the value already stored at the scope it would land in (avoid no-op confirmations).
257
-
258
- For prompts whose target worker is NOT in the resolved workers list (after override), present a single confirmation line such as `gemini-model 생략 (workers에 gemini 없음)` so the user can see why the question was skipped.
259
-
260
- ## Step 6.5: Confirm selections before rendering
261
-
262
- Before invoking `okstra render-bundle`, echo the resolved selections back to the user in a compact block so they can verify what will be passed. Show the **effective** values, not the raw input — i.e. when the user left a field blank, display `default` (and where known, the actual default such as `opus` / `sonnet`). Example for an `implementation` run:
263
-
264
- ```
265
- 선택 확인:
266
- task-type : implementation
267
- task-key : <group>/<id>
268
- base-ref : main (resolved <short-sha>) ← worktree base, first phase only
269
- executor : codex
270
- workers : (프로필 기본 — executor + verifier 2 + report-writer)
271
- lead-model : default (opus)
272
- codex-model : gpt-5.5 ← executor model
273
- claude-model : default (sonnet) ← verifier
274
- gemini-model : default (auto) ← verifier
275
- report-writer : default (opus)
276
- directive : (none)
277
- approved-plan : <abs path>
278
- ```
279
-
280
- Then `AskUserQuestion`: `"이대로 진행할까요?"` with options `Proceed` / `Edit`. On `Edit`, return to the relevant Step 6 sub-prompt.
281
-
282
- ## Step 7: Call `okstra render-bundle`
283
-
284
- This is the single command that materializes the entire task bundle. The
285
- subcommand auto-supplies `--workspace-root` (from `okstra paths --field
286
- workspace`) and forces `--render-only`, so the current claude session itself
287
- takes over as lead — no new claude is spawned.
134
+ Output: `{ok: true, args: {"project-root": "...", "task-type": "...", ...}}`. Build the `okstra render-bundle` invocation from `args`, passing each key as `--<key>` and the value verbatim (including empty strings — they are intentional `use phase default` markers).
288
135
 
289
136
  ```bash
290
137
  okstra render-bundle \
291
- --project-root "<project-root>" \
292
- --project-id "<project-id>" \
293
- --task-group "<task-group>" \
294
- --task-id "<task-id>" \
295
- --task-type "<task-type>" \
296
- --task-brief "<brief-path-from-user>" \
297
- --executor "<claude|codex|gemini or empty for default>" \
298
- --approved-plan "<approved-plan-or-empty>" \
299
- --base-ref "<chosen-ref-from-step-4.6 or empty when reusing existing worktree>" \
300
- --workers "<comma-separated worker list, or empty for profile default; MUST be empty for implementation>" \
301
- --directive "<directive or empty>" \
302
- --lead-model "..." --claude-model "..." --codex-model "..." \
303
- --gemini-model "..." --report-writer-model "..." \
304
- --related-tasks "..." \
305
- --clarification-response "<clarification-or-empty>" \
306
- --pr-template-path "<pr-template-override-or-empty; release-handoff only>"
138
+ --project-root "<args.project-root>" \
139
+ --project-id "<args.project-id>" \
140
+ --task-group "<args.task-group>" \
141
+ --task-id "<args.task-id>" \
142
+ --task-type "<args.task-type>" \
143
+ --task-brief "<args.task-brief>" \
144
+ --executor "<args.executor>" \
145
+ --approved-plan "<args.approved-plan>" \
146
+ --base-ref "<args.base-ref>" \
147
+ --workers "<args.workers>" \
148
+ --directive "<args.directive>" \
149
+ --lead-model "<args.lead-model>" \
150
+ --claude-model "<args.claude-model>" \
151
+ --codex-model "<args.codex-model>" \
152
+ --gemini-model "<args.gemini-model>" \
153
+ --report-writer-model "<args.report-writer-model>" \
154
+ --related-tasks "<args.related-tasks>" \
155
+ --clarification-response "<args.clarification-response>" \
156
+ --pr-template-path "<args.pr-template-path>"
307
157
  ```
308
158
 
309
- Stdout prints `okstra task root:`, `okstra instruction-set:`, and the full
310
- rendered lead-prompt text (because `--render-only` is on). Parse the labelled
311
- lines to get `TASK_ROOT`, `INSTRUCTION_SET_DIR`, and from there the
312
- `claude-execution-prompt.md` path used by Step 8.
159
+ `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_DIR`.
160
+
161
+ The python function underneath is mutex-protected (`~/.okstra/.locks/<task-key>.lock`), writes `run-context-*.json` + `run-inputs-*.json` + all manifests + discovery files, and registers the run in `~/.okstra/recent.jsonl` with status `prepared`.
313
162
 
314
- The python function underneath is mutex-protected (`~/.okstra/.locks/<task-key>.lock`),
315
- writes `run-context-*.json` + `run-inputs-*.json` + all manifests + discovery
316
- files, and registers the run in `~/.okstra/recent.jsonl` with status `prepared`.
163
+ You can delete `$STATE_FILE` after this point — its job is done.
317
164
 
318
- ## Step 8: Take over as Claude lead
165
+ ## Step 6: Take over as Claude lead
319
166
 
320
167
  Read these files (do not paraphrase) and enter `Claude lead` mode:
321
168
 
@@ -330,26 +177,43 @@ Then proceed through the phases exactly as the lead prompt directs (Phase 1 cont
330
177
  Inform the user with one short line:
331
178
  > Took over as Claude lead for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
332
179
 
180
+ ## Persisting the PR template scope (release-handoff)
181
+
182
+ When `wizard render-args` returns a non-empty `pr-template-path` AND the state has `pr_template_scope == "project"` or `"global"`, run the matching config command BEFORE `render-bundle`:
183
+
184
+ ```bash
185
+ # project scope
186
+ okstra config set pr-template-path "<path>" --scope project
187
+ # global scope (must be absolute or ~/-prefixed)
188
+ okstra config set pr-template-path "<path>" --scope global
189
+ ```
190
+
191
+ The scope is exposed via `wizard render-args` only as the `pr-template-path` value (1-shot override); the persist hint lives in the wizard state. Read it with:
192
+
193
+ ```bash
194
+ python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('pr_template_scope',''))" "$STATE_FILE"
195
+ ```
196
+
197
+ (or just inspect the JSON state file directly — it is a plain serialized `WizardState`).
198
+
333
199
  ## Concurrency
334
200
 
335
201
  - `prepare_task_bundle` serializes per-task via `~/.okstra/.locks/<task-key>.lock`. Concurrent skill invocations on the same task wait; different tasks proceed in parallel.
336
- - The skill must NOT call `okstra.sh` or any other bash entrypoint that would re-implement the orchestration. The python function is the single authority.
337
- - No env var carries identity across steps every step re-reads disk authority.
202
+ - Each wizard run owns its own `$STATE_FILE`; two parallel skill invocations do not collide.
203
+ - The skill must NOT call `okstra.sh` or any other bash entrypoint that would re-implement the orchestration. The wizard + `render-bundle` is the single authority.
338
204
 
339
205
  ## Failure Modes
340
206
 
341
207
  | Symptom | Cause | Fix |
342
208
  |---|---|---|
343
209
  | `okstra runtime missing: ...` | First run on this machine, or stale install | `npx okstra@latest install` once, retry. |
344
- | `OKSTRA_PYTHONPATH unbound` / `ModuleNotFoundError: okstra_project` | Step 0 was skipped or env vars dropped | Re-run Step 0; never invoke python without exporting `PYTHONPATH=$OKSTRA_PYTHONPATH`. |
345
- | `task root not found for <key>` | catalog entry stale or task-key typo | Re-run Step 2 (`okstra task-list`) and show available keys |
346
- | `PROJECT_ROOT 해석할 없습니다` | cwd outside okstra project, no git toplevel | Ask user for absolute path |
347
- | `approved plan has no recognised user-approval marker` | `implementation` without proper approval | Ask user to add `APPROVED` to the plan, or pick a different task-type |
348
- | `task brief not found` | brief-path doesn't resolve relative to cwd or project-root | Re-ask Step 5 |
349
- | record_start failed | `~/.okstra` lock or disk issue | Non-fatal — bundle is valid; warn and continue |
210
+ | `No module named okstra_ctl.wizard` | Install predates wizard module | `npx okstra@latest install` to refresh. |
211
+ | `wizard step` returns `ok: false` repeatedly | User keeps giving invalid answers | Echo the error verbatim and re-prompt the same step do not advance. |
212
+ | `task root not found for <key>` | catalog entry stale or task-key typo | Restart the wizard (`okstra wizard init`) to refresh the pick list. |
213
+ | `approved plan has no APPROVED marker` | `implementation` without proper approval | Ask the user to add `APPROVED` to the plan, or pick a different task-type. |
350
214
 
351
215
  ## Output Rules
352
216
 
353
- - Echo each `AskUserQuestion` outcome on one short line so user sees what was captured.
354
- - Never invent identity; re-ask if blank.
355
- - After Step 8, begin the lead workflow without re-summarizing the skill itself.
217
+ - Echo each captured answer (`result.echo`) on one short line so the user sees what was registered.
218
+ - Never invent identity; if a `text` prompt returns an empty answer where the wizard rejects it, the user must retry.
219
+ - After Step 6, begin the lead workflow without re-summarizing the skill itself.
@@ -127,13 +127,15 @@ Reading rules:
127
127
  large for one read; if you must page, you MUST cover the entire file
128
128
  before moving on, and you MUST state the page boundaries you used in your
129
129
  Findings section.
130
- - For the carry-in clarification response, read sub-section 0, sub-section
131
- 5.1 (`A1`, `A2`, ... material requests), and sub-section 5.2 (`Q1`,
132
- `Q2`, ... user questions) in full, including every row of every table,
133
- even if the answer column appears blank. The fact that you will write
134
- your output into a file with a structurally similar Section 5 is NOT an
135
- excuse to skim the prior `A*` and `Q*` rows carry context you cannot
136
- reconstruct from the new run alone.
130
+ - For the carry-in clarification response, read sub-section 0 and every
131
+ row of `## 5. Clarification Items` (`C-001`, `C-002`, ...) in full,
132
+ including rows whose `User input` cell is blank. The fact that you
133
+ will write your output into a file with a structurally similar
134
+ section 5 is NOT an excuse to skim the prior `C-*` rows carry
135
+ context you cannot reconstruct from the new run alone. If the prior
136
+ report uses the deprecated `4.5.9 Open Questions` / `5.1` / `5.2`
137
+ layout with `OQ-*` / `A*` / `Q*` IDs, walk all three blocks the
138
+ same way (legacy carry-in transitional rule).
137
139
  - Before writing any Findings, state in one sentence per file that you
138
140
  read it end-to-end. Example: "Read task-brief.md end-to-end (147 lines)."
139
141
  If you cannot truthfully say this for a file, do not produce Findings —
@@ -486,6 +488,7 @@ The script reads:
486
488
  ## Team State Persistence
487
489
 
488
490
  Information to be recorded in the team-state JSON file:
491
+ - `teamName` — record the string that was passed to `TeamCreate(team_name: ...)`. Either `state.teamName` (root) or `state.team.teamName` (nested) is accepted by `scripts/okstra_token_usage/collect.py`. Be consistent within a single run. Without this value the Phase 7 collector falls back to `okstra-<task-id>` (short form), which does NOT match worker jsonls whose team needle carries the full multi-segment task key — every worker will then be recorded as `source: "unavailable"`.
489
492
  - Current status of each worker role
490
493
  - Start/end times for each worker
491
494
  - Prompt history path for each worker