okstra 0.22.0 → 0.24.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 (33) hide show
  1. package/README.kr.md +3 -0
  2. package/README.md +3 -0
  3. package/bin/okstra +5 -0
  4. package/docs/kr/architecture.md +2 -2
  5. package/docs/kr/cli.md +1 -0
  6. package/docs/project-structure-overview.md +4 -1
  7. package/package.json +1 -1
  8. package/runtime/BUILD.json +2 -2
  9. package/runtime/agents/workers/claude-worker.md +3 -1
  10. package/runtime/agents/workers/codex-worker.md +3 -1
  11. package/runtime/agents/workers/gemini-worker.md +3 -1
  12. package/runtime/agents/workers/report-writer-worker.md +17 -2
  13. package/runtime/prompts/profiles/release-handoff.md +16 -0
  14. package/runtime/python/okstra_ctl/render.py +25 -2
  15. package/runtime/python/okstra_ctl/wizard.py +1249 -0
  16. package/runtime/python/okstra_token_usage/collect.py +12 -1
  17. package/runtime/skills/okstra-report-writer/SKILL.md +1 -0
  18. package/runtime/skills/okstra-run/SKILL.md +115 -234
  19. package/runtime/skills/okstra-setup/SKILL.md +37 -0
  20. package/runtime/skills/okstra-team-contract/SKILL.md +47 -1
  21. package/runtime/templates/prd/brief.template.md +1 -0
  22. package/runtime/templates/project-docs/task-index.template.md +1 -0
  23. package/runtime/templates/reports/error-analysis-input.template.md +1 -0
  24. package/runtime/templates/reports/final-report.template.md +1 -0
  25. package/runtime/templates/reports/final-verification-input.template.md +1 -0
  26. package/runtime/templates/reports/implementation-input.template.md +1 -0
  27. package/runtime/templates/reports/implementation-planning-input.template.md +1 -0
  28. package/runtime/templates/reports/quick-input.template.md +1 -0
  29. package/runtime/templates/reports/release-handoff-input.template.md +1 -0
  30. package/runtime/templates/reports/schedule.template.md +1 -0
  31. package/runtime/templates/reports/task-brief.template.md +1 -0
  32. package/src/config.mjs +392 -0
  33. package/src/wizard.mjs +105 -0
@@ -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`.
@@ -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,41 +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
- ## Authority Files (disk-only no env var caching for per-run identity)
23
+ ## How the wizard talks to you
24
24
 
25
- Every step reads disk afresh. The `OKSTRA_*` env vars below identify the
26
- **runtime installation** (stable across runs) — they are NOT per-task identity.
25
+ Every wizard call returns JSON. The two shapes you'll see:
27
26
 
28
- - `~/.okstra/version` — okstra runtime version stamp
29
- - `<PROJECT_ROOT>/.project-docs/okstra/project.json`
30
- - `<PROJECT_ROOT>/.project-docs/okstra/discovery/{task-catalog,latest-task}.json`
31
- - `<task-root>/task-manifest.json`
27
+ ```json
28
+ { "ok": true, "echo": "task-group: backend-api",
29
+ "next": { "step": "task_id", "kind": "text", "label": "...", "options": [], "echoTemplate": "..." } }
30
+ ```
31
+
32
+ ```json
33
+ { "ok": false, "error": "approved plan has no APPROVED marker: ...",
34
+ "current": { "step": "approved_plan", "kind": "text", "label": "..." } }
35
+ ```
36
+
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.
32
38
 
33
- ## Step 0: Verify okstra runtime + project setup
39
+ The wizard tells you *which UI to use* via `kind`:
34
40
 
35
- Do NOT hard-code or guess any okstra path. Every run loads them fresh from
36
- the single authority `okstra`:
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.
44
+
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.
46
+
47
+ ## Step 1: Verify okstra runtime + project setup
37
48
 
38
49
  ```bash
39
- # 0) Resolve runner: prefer PATH (npm-installed) over npx (avoids per-call registry lookup).
40
- # If the user installed okstra via npm, they control upgrade timing — do not force @latest.
41
50
  if command -v okstra >/dev/null 2>&1; then
42
51
  OKSTRA_CMD="okstra"
43
52
  else
44
53
  OKSTRA_CMD="npx -y okstra@latest"
45
54
  fi
46
55
 
47
- # 1) Ensure runtime is fresh (idempotent, cached when up-to-date)
48
56
  $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
49
57
  echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
50
58
  exit 1
51
59
  }
52
60
 
53
- # 2) Load all runtime paths into the shell as OKSTRA_* exports
54
61
  eval "$($OKSTRA_CMD paths --shell)"
55
62
  export PYTHONPATH="$OKSTRA_PYTHONPATH"
56
63
 
57
- # 3) Verify the current project has okstra metadata (project.json + projectId)
58
64
  OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
59
65
  echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
60
66
  echo "$OKSTRA_PROJECT_INFO" >&2
@@ -62,243 +68,101 @@ OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
62
68
  }
63
69
  ```
64
70
 
65
- 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.
66
72
 
67
- | Variable | Meaning |
68
- |---|---|
69
- | `$OKSTRA_WORKSPACE` | passed to python as `workspace_root` (prompts/, templates/, validators/, agents/ root) |
70
- | `$OKSTRA_AGENTS_DIR` | source dir of worker `*.md` (subagent definitions) |
71
- | `$OKSTRA_PYTHONPATH` | already exported as `PYTHONPATH` |
72
- | `$OKSTRA_BIN` | bash entrypoints (`okstra.sh`, codex/gemini exec wrappers) |
73
- | `$OKSTRA_HOME` | `~/.okstra` (recent.jsonl, locks, projects/, archive/) |
74
- | `$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`.
75
74
 
76
- ## Step 1: Resolve PROJECT_ROOT and projectId
77
-
78
- 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
79
76
 
80
77
  ```bash
81
- okstra check-project --cwd "$(pwd)"
82
- ```
78
+ STATE_FILE="$(mktemp -t okstra-wizard.XXXX.json)"
83
79
 
84
- - If `ok: true`: read `projectRoot` and `projectId` from the JSON.
85
- - If `ok: false`: ask the user (`AskUserQuestion`, free text) for an absolute project-root path; rerun with `okstra check-project --cwd <their input>`.
86
-
87
- ## Step 2: Choose task — existing vs new
88
-
89
- ```bash
90
- okstra task-list --project "$PROJECT_ROOT"
80
+ okstra wizard init \
81
+ --state-file "$STATE_FILE" \
82
+ --project-root "$projectRoot" \
83
+ --project-id "$projectId"
91
84
  ```
92
85
 
93
- Output is JSON `{ok, projectRoot, tasks: [...], latest: {...}|null}`.
94
-
95
- Use `AskUserQuestion`:
96
-
97
- - **Label**: "Which task?"
98
- - **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.
99
-
100
- For an existing pick, read its `task-manifest.json` to capture `taskType` and `workflow.nextRecommendedPhase`.
101
-
102
- ## Step 3: For new tasks — collect identity
103
-
104
- Skip if continuing existing.
86
+ Output: the same `{ok, next}` JSON described above. The first `next` is always `step: "task_pick"`.
105
87
 
106
- `AskUserQuestion` (free text, one at a time):
88
+ ## Step 3: Run the prompt loop
107
89
 
108
- 1. `"Task group (e.g. backend-api, INV-1234, refactor)"` → `task_group`
109
- 2. `"Task id (e.g. login-error-analysis, dev-9043)"` → `task_id`
90
+ Repeat until `next.kind == "done"`:
110
91
 
111
- Validate that slugified `task_group` and `task_id` each contain at least one alphanumeric character. Re-ask if not.
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).
112
102
 
113
- ## Step 4: Choose task-type
103
+ That is the entire interactive flow. The wizard handles:
114
104
 
115
- `AskUserQuestion` with six fixed options:
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.
116
113
 
117
- | Option | Description |
118
- |---|---|
119
- | `requirements-discovery` | Classify request and route to next safe phase |
120
- | `error-analysis` | Evidence-based root-cause analysis (no code changes) |
121
- | `implementation-planning` | Plan options + request user approval |
122
- | `implementation` | Execute approved plan (requires `--approved-plan`) |
123
- | `final-verification` | Acceptance + residual-risk review |
124
- | `release-handoff` | Drive commit / push / PR with user-selected actions after `accepted` final-verification |
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.
125
115
 
126
- For existing tasks, present `nextRecommendedPhase` as the first option (recommended default).
116
+ ## Step 4: Show the confirmation block before the final Proceed
127
117
 
128
- If `implementation` chosen, ask two more `AskUserQuestion` in order:
129
- - `"Path to the approved final-report.md (must contain APPROVED marker)"` — the underlying python `prepare_task_bundle` re-validates the marker, but you can pre-check with `grep`.
130
- - `"Executor provider for this run (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`.
131
-
132
- ## Step 4.6: Base ref for the task worktree (first phase only)
133
-
134
- `okstra prepare` provisions a per-task git worktree on first phase of a task-key
135
- and reuses it on every subsequent phase. The base ref of that worktree is the
136
- **user's choice**, not the caller's current `HEAD`, so the worktree never
137
- silently inherits an unrelated branch you happen to be checked out on.
138
-
139
- First, decide whether to ask:
118
+ When `next.step == "confirm"`, before relaying the picker, fetch the human-readable selection summary:
140
119
 
141
120
  ```bash
142
- okstra worktree-lookup "<project-id>" "<task-group>" "<task-id>"
121
+ okstra wizard confirmation --state-file "$STATE_FILE"
143
122
  ```
144
123
 
145
- Output JSON: `{ok: true, entry: null}` means no active worktree **ASK**. A
146
- non-null `entry` with `status: "active"` → **REUSE**.
147
-
148
- - `REUSE` → the registered worktree is reused; set `base_ref=""` and skip the
149
- question (the registered base is authoritative).
150
- - `ASK` → this is the first phase for this task-key. Continue.
124
+ Output: `{ok: true, text: "선택 확인:\n task-type : ...\n ..."}`. Print `text` to the user, then render the `confirm` picker (Proceed / Edit).
151
125
 
152
- `AskUserQuestion` (mirrors the `release-handoff` PR-base picker):
126
+ ## Step 5: Render the task bundle
153
127
 
154
- - **Label**: `" task worktree base branch?"`
155
- - **Options** (single-select):
156
- 1. `main` (recommended)
157
- 2. `dev`
158
- 3. `staging`
159
- 4. `preprod`
160
- 5. `prod`
161
- 6. Free text — let the user type any local ref (branch, tag, or full/short SHA).
162
-
163
- Validate the chosen ref exists in the MAIN worktree before continuing:
128
+ When `next.kind == "done"`, fetch the final args:
164
129
 
165
130
  ```bash
166
- git -C "$(git -C "$PROJECT_ROOT" rev-parse --path-format=absolute --git-common-dir | xargs dirname)" \
167
- rev-parse --verify --quiet "<chosen-ref>^{commit}" >/dev/null \
168
- || { echo "ref not found locally: <chosen-ref>"; exit 1; }
169
- ```
170
-
171
- Re-ask on failure. Echo the resolved short SHA back to the user
172
- (`base 확정: <ref> (<short-sha>)`) and capture `base_ref=<chosen-ref>` for
173
- Step 7.
174
-
175
- ## Step 5: Brief path
176
-
177
- - New task: `AskUserQuestion` (free text) `"Path to the task brief markdown (relative to project root)"`. Verify file exists; re-ask on failure.
178
- - Existing task: default to the manifest's `taskBriefPath`. Show it; ask whether to keep or change.
179
-
180
- ## Step 6 (optional): Directive / workers / models / related / clarification
181
-
182
- Single `AskUserQuestion` first: `"기본 워커/모델로 진행할까요, 아니면 커스터마이즈할까요?"` (options: `Use defaults`, `Customize`).
183
-
184
- - `Use defaults` → all overrides remain empty.
185
- - `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.
186
-
187
- ### Model selection options (used by 6a and 6b)
188
-
189
- 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".
190
-
191
- - **Claude (lead / claude-worker / report-writer)** options: `default`, `opus`, `sonnet`, `haiku`
192
- - **Codex (codex-worker)** options: `default`, `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`
193
- - **Gemini (gemini-worker)** options: `default`, `gemini-3-pro-preview`, `gemini-3-flash-preview`, `auto`
194
-
195
- 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.
196
-
197
- ### 6a. `implementation` phase (executor-driven)
198
-
199
- 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:
200
-
201
- 1. `AskUserQuestion` `"리더(Claude lead) 모델?"` (Claude options) → `lead_model`
202
- 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).
203
- 3. `AskUserQuestion` `"리포트 작성자(report-writer) 모델?"` (Claude options) → `report_writer_model`
204
- 4. `AskUserQuestion` `"추가 directive (선택, 빈 칸 가능)"` (free text) → `directive`
205
- 5. `AskUserQuestion` `"관련 task id 목록, 쉼표 구분 (선택, 빈 칸 가능)"` (free text) → `related_tasks_raw`
206
-
207
- Do NOT ask for `workers_override` in implementation — the profile's required roster must be preserved (verifier slots are mandatory). Leave `workers_override=""`.
208
-
209
- ### 6b. Other phases (`requirements-discovery`, `error-analysis`, `implementation-planning`, `final-verification`, `release-handoff`)
210
-
211
- **Before asking any worker/model question, resolve the profile's allowed roster:**
212
-
213
- ```python
214
- from okstra_ctl.workers import resolve_profile_workers
215
- profile_workers = resolve_profile_workers(Path("<OKSTRA_PROMPTS_PROFILES_DIR>/<task-type>.md"))
131
+ okstra wizard render-args --state-file "$STATE_FILE"
216
132
  ```
217
133
 
218
- This is the **only** set of worker IDs you may show or ask about. Never offer
219
- workers outside this list. Special cases:
220
-
221
- - If `profile_workers` is empty (e.g., `release-handoff` is lead-only with no
222
- `- Required workers:` block), **skip the worker question and all
223
- worker-model questions entirely** — only ask lead model, directive, related,
224
- clarification. The backend forces `workers=[]` for these profiles.
225
- - Otherwise, the worker question must enumerate **only** `profile_workers` —
226
- do NOT show `claude, codex, gemini, report-writer` blindly.
227
-
228
- Ask each in turn (model prompts use `AskUserQuestion` with the option lists above; others are free text). Skip any worker-model prompt whose worker is not in `profile_workers`.
229
-
230
- 1. (only when `profile_workers` is non-empty) `AskUserQuestion` `"참여 워커 목록 (쉼표 구분, 빈 칸 = 프로필 기본값 <profile_workers_csv>). 선택지: <profile_workers_csv>"` (free text) → `workers_override`. Validate the answer is a subset of `profile_workers`; re-ask on failure. (Backend will also reject violations with `WorkersError`.)
231
- 2. `AskUserQuestion` `"리더(Claude lead) 모델?"` (Claude options) → `lead_model`
232
- 3. (only if `claude` ∈ resolved workers) `AskUserQuestion` `"claude 워커 모델?"` (Claude options) → `claude_model`
233
- 4. (only if `codex` ∈ resolved workers) `AskUserQuestion` `"codex 워커 모델?"` (Codex options) → `codex_model`
234
- 5. (only if `gemini` ∈ resolved workers) `AskUserQuestion` `"gemini 워커 모델?"` (Gemini options) → `gemini_model`
235
- 6. (only if `report-writer` ∈ resolved workers) `AskUserQuestion` `"리포트 작성자 모델?"` (Claude options) → `report_writer_model`
236
- 7. `AskUserQuestion` `"추가 directive (선택, 빈 칸 가능)"` (free text) → `directive`
237
- 8. `AskUserQuestion` `"관련 task id 목록, 쉼표 구분 (선택, 빈 칸 가능)"` (free text) → `related_tasks_raw`
238
- 9. `AskUserQuestion` `"clarification-response 파일 경로 (follow-up 시에만, 빈 칸 가능)"` (free text) → `clarification_response_path`
239
- 10. (only when `task_type == "release-handoff"`) `AskUserQuestion` `"PR 본문 템플릿 경로 1회성 override (빈 칸 = project.json → ~/.okstra/config.json → 스킬 디폴트 순으로 자동 해석)"` (free text) → `pr_template_path`. The backend (`okstra_ctl.pr_template.resolve_pr_template_path`) validates the file exists and surfaces `PrTemplateError` on failure. If the user wants to persist the choice instead of a one-shot override, tell them to set `prTemplatePath` in `<project_root>/.project-docs/okstra/project.json` (project scope) or `~/.okstra/config.json` (global scope).
240
-
241
- 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.
242
-
243
- ## Step 6.5: Confirm selections before rendering
244
-
245
- 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:
246
-
247
- ```
248
- 선택 확인:
249
- task-type : implementation
250
- task-key : <group>/<id>
251
- base-ref : main (resolved <short-sha>) ← worktree base, first phase only
252
- executor : codex
253
- workers : (프로필 기본 — executor + verifier 2 + report-writer)
254
- lead-model : default (opus)
255
- codex-model : gpt-5.5 ← executor model
256
- claude-model : default (sonnet) ← verifier
257
- gemini-model : default (auto) ← verifier
258
- report-writer : default (opus)
259
- directive : (none)
260
- approved-plan : <abs path>
261
- ```
262
-
263
- Then `AskUserQuestion`: `"이대로 진행할까요?"` with options `Proceed` / `Edit`. On `Edit`, return to the relevant Step 6 sub-prompt.
264
-
265
- ## Step 7: Call `okstra render-bundle`
266
-
267
- This is the single command that materializes the entire task bundle. The
268
- subcommand auto-supplies `--workspace-root` (from `okstra paths --field
269
- workspace`) and forces `--render-only`, so the current claude session itself
270
- 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).
271
135
 
272
136
  ```bash
273
137
  okstra render-bundle \
274
- --project-root "<project-root>" \
275
- --project-id "<project-id>" \
276
- --task-group "<task-group>" \
277
- --task-id "<task-id>" \
278
- --task-type "<task-type>" \
279
- --task-brief "<brief-path-from-user>" \
280
- --executor "<claude|codex|gemini or empty for default>" \
281
- --approved-plan "<approved-plan-or-empty>" \
282
- --base-ref "<chosen-ref-from-step-4.6 or empty when reusing existing worktree>" \
283
- --workers "<comma-separated worker list, or empty for profile default; MUST be empty for implementation>" \
284
- --directive "<directive or empty>" \
285
- --lead-model "..." --claude-model "..." --codex-model "..." \
286
- --gemini-model "..." --report-writer-model "..." \
287
- --related-tasks "..." \
288
- --clarification-response "<clarification-or-empty>" \
289
- --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>"
290
157
  ```
291
158
 
292
- Stdout prints `okstra task root:`, `okstra instruction-set:`, and the full
293
- rendered lead-prompt text (because `--render-only` is on). Parse the labelled
294
- lines to get `TASK_ROOT`, `INSTRUCTION_SET_DIR`, and from there the
295
- `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`.
296
160
 
297
- The python function underneath is mutex-protected (`~/.okstra/.locks/<task-key>.lock`),
298
- writes `run-context-*.json` + `run-inputs-*.json` + all manifests + discovery
299
- files, and registers the run in `~/.okstra/recent.jsonl` with status `prepared`.
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`.
300
162
 
301
- ## Step 8: Take over as Claude lead
163
+ You can delete `$STATE_FILE` after this point — its job is done.
164
+
165
+ ## Step 6: Take over as Claude lead
302
166
 
303
167
  Read these files (do not paraphrase) and enter `Claude lead` mode:
304
168
 
@@ -313,26 +177,43 @@ Then proceed through the phases exactly as the lead prompt directs (Phase 1 cont
313
177
  Inform the user with one short line:
314
178
  > Took over as Claude lead for `<taskKey>` (`<task-type>`). Run dir: `<RUN_DIR_RELATIVE_PATH>`. Beginning Phase 1 (context loading).
315
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
+
316
199
  ## Concurrency
317
200
 
318
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.
319
- - 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.
320
- - 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.
321
204
 
322
205
  ## Failure Modes
323
206
 
324
207
  | Symptom | Cause | Fix |
325
208
  |---|---|---|
326
209
  | `okstra runtime missing: ...` | First run on this machine, or stale install | `npx okstra@latest install` once, retry. |
327
- | `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`. |
328
- | `task root not found for <key>` | catalog entry stale or task-key typo | Re-run Step 2 (`okstra task-list`) and show available keys |
329
- | `PROJECT_ROOT 해석할 없습니다` | cwd outside okstra project, no git toplevel | Ask user for absolute path |
330
- | `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 |
331
- | `task brief not found` | brief-path doesn't resolve relative to cwd or project-root | Re-ask Step 5 |
332
- | 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. |
333
214
 
334
215
  ## Output Rules
335
216
 
336
- - Echo each `AskUserQuestion` outcome on one short line so user sees what was captured.
337
- - Never invent identity; re-ask if blank.
338
- - 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.
@@ -209,6 +209,43 @@ To opt out (advanced): replace the symlink with a regular file. okstra
209
209
  will detect that it is no longer a symlink on its next setup call and
210
210
  back it up as `.bak.<timestamp>` rather than overwriting silently.
211
211
 
212
+ ## Step 4.8 (optional): register a project PR body template
213
+
214
+ `release-handoff` fills the PR body from a template. By default it uses the
215
+ bundled skill template at
216
+ `~/.claude/skills/okstra-run/templates/pr-body.template.md`. Most projects
217
+ want their own — e.g. the repo's `.github/PULL_REQUEST_TEMPLATE.md` — to
218
+ keep PRs consistent with what the team already merges manually.
219
+
220
+ Ask the user with `AskUserQuestion` (fixed options, NOT free text — file
221
+ path entry happens in the follow-up plain text prompt per the
222
+ okstra-run prompt convention):
223
+
224
+ - **Question**: `"이 프로젝트에서 release-handoff 가 사용할 PR 본문 템플릿을 등록할까요?"`
225
+ - **Options**:
226
+ 1. `이번 프로젝트만 (project scope)` — write to `<PROJECT_ROOT>/.project-docs/okstra/project.json` `prTemplatePath`.
227
+ 2. `전역 (global scope)` — write to `~/.okstra/config.json` `prTemplatePath`.
228
+ 3. `나중에` — skip.
229
+
230
+ If the user picks scope 1 or 2, follow up with a **plain text prompt**:
231
+ `"PR 본문 템플릿 파일 경로를 알려주세요. project 스코프는 project-root 기준 상대경로 또는 절대경로, global 스코프는 절대경로 또는 ~/ 시작 경로만 허용됩니다."` Consume the next user message, then run:
232
+
233
+ ```bash
234
+ okstra config set pr-template-path "<typed-path>" --scope <project|global>
235
+ ```
236
+
237
+ The command validates the value (global rejects relative paths) and
238
+ writes atomically. Surface its stdout JSON so the user sees which file
239
+ was updated.
240
+
241
+ If the user chose `나중에`, tell them they can register later with one of:
242
+
243
+ - `okstra config set pr-template-path <path> --scope project|global` from a
244
+ terminal, or
245
+ - the per-run 1회성 override prompt during the next release-handoff run
246
+ (the run can also persist that override to project/global scope on the
247
+ spot — see the okstra-run skill).
248
+
212
249
  ## Step 5: Verify
213
250
 
214
251
  ```bash
@@ -202,7 +202,52 @@ Terminal statuses that can be recorded for a worker:
202
202
 
203
203
  **Authoritative source.** If other documents (SKILL.md, worker agent definitions) disagree with this section, this section wins.
204
204
 
205
- A successful worker result must include the following sections in this exact order:
205
+ ### Result Frontmatter (mandatory, precedes Section 0)
206
+
207
+ Every worker result file MUST begin with a YAML frontmatter block. The values are sourced from the corresponding fields of the input files' frontmatter (e.g. `analysis-material.md`, `task-brief.md`) — copy them verbatim; do NOT regenerate them. Only `workerId` and `title` are worker-specific.
208
+
209
+ ```yaml
210
+ ---
211
+ title: OKSTRA <Worker Role> Result - <task-key>
212
+ id: "<task-key with ':' replaced by '-'>"
213
+ aliases: ["<id>-<task-type>"]
214
+ tags: ["obsidian", "okstra", "worker-result", "<task-type>"]
215
+ taskType: "<task-type>"
216
+ workerId: "<claude|codex|gemini|report-writer>"
217
+ task-id: "<task-id>"
218
+ task-group: "<task-group>"
219
+ project-id: "<project-id>"
220
+ date: <YYYY-MM-DD>
221
+ ---
222
+ ```
223
+
224
+ Concrete example for a `claude-worker` result on task-key `fontsninja-classifier-v2:DEV-9388:DEV-9429` and task-type `implementation`:
225
+
226
+ ```yaml
227
+ ---
228
+ title: OKSTRA Claude Worker Result - fontsninja-classifier-v2:DEV-9388:DEV-9429
229
+ id: "fontsninja-classifier-v2-DEV-9388-DEV-9429"
230
+ aliases: ["fontsninja-classifier-v2-DEV-9388-DEV-9429-implementation"]
231
+ tags: ["obsidian", "okstra", "worker-result", "implementation"]
232
+ taskType: "implementation"
233
+ workerId: "claude"
234
+ task-id: "DEV-9429"
235
+ task-group: "DEV-9388"
236
+ project-id: "fontsninja-classifier-v2"
237
+ date: 2026-05-15
238
+ ---
239
+ ```
240
+
241
+ Rules:
242
+ - `id` is the run's `task-key` with `:` replaced by `-`. It is a scalar string, NOT an array.
243
+ - `aliases` is a YAML array containing a single value `"<id>-<task-type>"`.
244
+ - `taskType` mirrors the run's task type (`requirements-discovery`, `error-analysis`, `implementation-planning`, `implementation`, `final-verification`, `release-handoff`).
245
+ - `workerId` identifies which worker produced the file. Required values: `claude` / `codex` / `gemini` / `report-writer`.
246
+ - Other fields (`task-id`, `task-group`, `project-id`, `date`) MUST match the input files' frontmatter exactly. If the input frontmatter is missing or unreadable, the worker MUST record a `tool-failure` and stop instead of guessing.
247
+
248
+ The same frontmatter contract applies to the `Report writer worker`'s final-report file — the report-writer copies these values from its inputs and only swaps `workerId` to `report-writer`.
249
+
250
+ A successful worker result must include the following sections in this exact order, beneath the frontmatter block:
206
251
 
207
252
  0. **Reading Confirmation** — one short line per input file (`task-brief.md`, `analysis-profile.md`, `analysis-material.md` if present, `reference-expectations.md`, `clarification-response.md` if a carry-in was provided, `final-report-template.md`) stating that the worker read it end-to-end. Each line takes the form `- Read <file-name> end-to-end (<line-count> lines).`. If a file was skipped or only partially read, the worker MUST NOT produce sections 1–5; instead it records a `tool-failure` in the errors sidecar and stops. This section exists specifically to counteract the common failure mode where workers skim long inputs because they share structure with the file the run will eventually write into.
208
253
  1. Findings
@@ -441,6 +486,7 @@ The script reads:
441
486
  ## Team State Persistence
442
487
 
443
488
  Information to be recorded in the team-state JSON file:
489
+ - `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"`.
444
490
  - Current status of each worker role
445
491
  - Start/end times for each worker
446
492
  - Prompt history path for each worker
@@ -8,6 +8,7 @@ date: {{TASK_DATE}}
8
8
  task-id: "{{TASK_ID}}"
9
9
  task-group: "{{TASK_GROUP}}"
10
10
  project-id: "{{PROJECT_ID}}"
11
+ taskType: "{{FM_TASK_TYPE}}"
11
12
  ---
12
13
 
13
14
  # OKSTRA Task Brief
@@ -8,6 +8,7 @@ date: {{TASK_DATE}}
8
8
  task-id: "{{TASK_ID}}"
9
9
  task-group: "{{TASK_GROUP}}"
10
10
  project-id: "{{PROJECT_ID}}"
11
+ taskType: "{{FM_TASK_TYPE}}"
11
12
  ---
12
13
 
13
14
  # OKSTRA Task Summary
@@ -8,6 +8,7 @@ date: {{TASK_DATE}}
8
8
  task-id: "{{TASK_ID}}"
9
9
  task-group: "{{TASK_GROUP}}"
10
10
  project-id: "{{PROJECT_ID}}"
11
+ taskType: "{{FM_TASK_TYPE}}"
11
12
  ---
12
13
 
13
14
  # OKSTRA Error Analysis Input
@@ -8,6 +8,7 @@ date: {{TASK_DATE}}
8
8
  task-id: "{{TASK_ID}}"
9
9
  task-group: "{{TASK_GROUP}}"
10
10
  project-id: "{{PROJECT_ID}}"
11
+ taskType: "{{FM_TASK_TYPE}}"
11
12
  ---
12
13
 
13
14
  # {{TASK_KEY}} - Multi-Agent Cross Verification Final Report