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.
- package/README.kr.md +3 -0
- package/README.md +3 -0
- package/bin/okstra +5 -0
- package/docs/kr/architecture.md +2 -2
- package/docs/kr/cli.md +1 -0
- package/docs/project-structure-overview.md +4 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +3 -1
- package/runtime/agents/workers/codex-worker.md +3 -1
- package/runtime/agents/workers/gemini-worker.md +3 -1
- package/runtime/agents/workers/report-writer-worker.md +17 -2
- package/runtime/prompts/profiles/release-handoff.md +16 -0
- package/runtime/python/okstra_ctl/render.py +25 -2
- package/runtime/python/okstra_ctl/wizard.py +1249 -0
- package/runtime/python/okstra_token_usage/collect.py +12 -1
- package/runtime/skills/okstra-report-writer/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +115 -234
- package/runtime/skills/okstra-setup/SKILL.md +37 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +47 -1
- package/runtime/templates/prd/brief.template.md +1 -0
- package/runtime/templates/project-docs/task-index.template.md +1 -0
- package/runtime/templates/reports/error-analysis-input.template.md +1 -0
- package/runtime/templates/reports/final-report.template.md +1 -0
- package/runtime/templates/reports/final-verification-input.template.md +1 -0
- package/runtime/templates/reports/implementation-input.template.md +1 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +1 -0
- package/runtime/templates/reports/quick-input.template.md +1 -0
- package/runtime/templates/reports/release-handoff-input.template.md +1 -0
- package/runtime/templates/reports/schedule.template.md +1 -0
- package/runtime/templates/reports/task-brief.template.md +1 -0
- package/src/config.mjs +392 -0
- 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
|
|
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
|
|
10
|
+
**Single authority**: this skill drives `okstra wizard`, which owns every step (ordering, branching, validation). The skill is just a thin prompt-relay loop — it never decides "what to ask next" itself. If the flow needs to change, edit `scripts/okstra_ctl/wizard.py`, not this file.
|
|
11
11
|
|
|
12
12
|
## When to Use
|
|
13
13
|
|
|
14
|
-
- The user is
|
|
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
|
-
##
|
|
23
|
+
## How the wizard talks to you
|
|
24
24
|
|
|
25
|
-
Every
|
|
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
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
39
|
+
The wizard tells you *which UI to use* via `kind`:
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
the
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
82
|
-
```
|
|
78
|
+
STATE_FILE="$(mktemp -t okstra-wizard.XXXX.json)"
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
88
|
+
## Step 3: Run the prompt loop
|
|
107
89
|
|
|
108
|
-
|
|
109
|
-
2. `"Task id (e.g. login-error-analysis, dev-9043)"` → `task_id`
|
|
90
|
+
Repeat until `next.kind == "done"`:
|
|
110
91
|
|
|
111
|
-
|
|
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
|
-
|
|
103
|
+
That is the entire interactive flow. The wizard handles:
|
|
114
104
|
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
## Step 4: Show the confirmation block before the final Proceed
|
|
127
117
|
|
|
128
|
-
|
|
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
|
|
121
|
+
okstra wizard confirmation --state-file "$STATE_FILE"
|
|
143
122
|
```
|
|
144
123
|
|
|
145
|
-
Output
|
|
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
|
-
|
|
126
|
+
## Step 5: Render the task bundle
|
|
153
127
|
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
276
|
-
--task-group
|
|
277
|
-
--task-id
|
|
278
|
-
--task-type
|
|
279
|
-
--task-brief
|
|
280
|
-
--executor
|
|
281
|
-
--approved-plan "<approved-plan
|
|
282
|
-
--base-ref
|
|
283
|
-
--workers
|
|
284
|
-
--directive
|
|
285
|
-
--lead-model
|
|
286
|
-
--
|
|
287
|
-
--
|
|
288
|
-
--
|
|
289
|
-
--
|
|
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
|
-
|
|
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
|
-
-
|
|
320
|
-
-
|
|
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
|
-
| `
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
330
|
-
| `approved plan has no
|
|
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 `
|
|
337
|
-
- Never invent identity;
|
|
338
|
-
- After Step
|
|
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
|
-
|
|
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
|