okstra 0.25.0 → 0.26.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 +1 -0
- package/README.md +1 -0
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/cli.md +10 -1
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +254 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +30 -2
- package/runtime/bin/okstra.sh +1 -0
- package/runtime/prompts/profiles/_common-contract.md +5 -0
- package/runtime/prompts/profiles/implementation-planning.md +3 -0
- package/runtime/python/lib/okstra/cli.sh +8 -1
- package/runtime/python/lib/okstra/globals.sh +3 -0
- package/runtime/python/lib/okstra/usage.sh +8 -1
- package/runtime/python/okstra_ctl/render.py +32 -0
- package/runtime/python/okstra_ctl/run.py +27 -0
- package/runtime/python/okstra_ctl/wizard.py +234 -8
- package/runtime/skills/okstra-convergence/SKILL.md +203 -0
- package/runtime/skills/okstra-run/SKILL.md +37 -32
- package/runtime/templates/reports/final-report.template.md +34 -0
- package/runtime/validators/validate-run.py +71 -0
- package/src/wizard.mjs +21 -5
|
@@ -9,6 +9,8 @@ Launch an okstra task — gather inputs interactively via the **wizard state mac
|
|
|
9
9
|
|
|
10
10
|
**Single authority**: this skill drives `okstra wizard`, which owns every step (ordering, branching, validation). The skill is just a thin prompt-relay loop — it never decides "what to ask next" itself. If the flow needs to change, edit `scripts/okstra_ctl/wizard.py`, not this file.
|
|
11
11
|
|
|
12
|
+
**Bash invocation rule (permission-friendly)**: every Bash command in this skill MUST begin with the literal token `okstra` (or another already-allowed binary) and pass literal argument values. Do not introduce shell variables (`$STATE_FILE`, `$ANSWER`, `$projectRoot`, ...), `$(...)` command substitution, or leading `VAR=...` assignments — any of those make the leading token non-literal, defeat the `Bash(okstra:*)` permission match, and force a confirmation prompt on every wizard call. When a prior tool call emitted a path or value, read it from the tool output and paste the literal string into the next command.
|
|
13
|
+
|
|
12
14
|
## When to Use
|
|
13
15
|
|
|
14
16
|
- 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").
|
|
@@ -48,39 +50,41 @@ Never invent additional questions. Never reorder. Never use `AskUserQuestion` fo
|
|
|
48
50
|
|
|
49
51
|
```bash
|
|
50
52
|
if command -v okstra >/dev/null 2>&1; then
|
|
51
|
-
|
|
53
|
+
okstra ensure-installed >/dev/null 2>&1 || { echo "FAIL: okstra ensure-installed failed" >&2; exit 1; }
|
|
54
|
+
eval "$(okstra paths --shell)"
|
|
55
|
+
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
56
|
+
okstra check-project --json || { echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2; exit 1; }
|
|
52
57
|
else
|
|
53
|
-
|
|
58
|
+
npx -y okstra@latest ensure-installed >/dev/null 2>&1 || { echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2; exit 1; }
|
|
59
|
+
eval "$(npx -y okstra@latest paths --shell)"
|
|
60
|
+
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
61
|
+
npx -y okstra@latest check-project --json || { echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2; exit 1; }
|
|
54
62
|
fi
|
|
55
|
-
|
|
56
|
-
$OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
|
|
57
|
-
echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
|
|
58
|
-
exit 1
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
eval "$($OKSTRA_CMD paths --shell)"
|
|
62
|
-
export PYTHONPATH="$OKSTRA_PYTHONPATH"
|
|
63
|
-
|
|
64
|
-
OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
|
|
65
|
-
echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
|
|
66
|
-
echo "$OKSTRA_PROJECT_INFO" >&2
|
|
67
|
-
exit 1
|
|
68
|
-
}
|
|
69
63
|
```
|
|
70
64
|
|
|
71
|
-
If `
|
|
65
|
+
The `check-project --json` output goes to stdout; read it from the tool result. If its `ok` field is `false`, ask the user with a **plain text prompt** for an absolute project-root path; rerun `okstra check-project --cwd <path> --json`. Re-prompt with plain text on failure.
|
|
72
66
|
|
|
73
|
-
Parse `projectRoot` and `projectId` from
|
|
67
|
+
Parse `projectRoot` and `projectId` from that JSON output.
|
|
74
68
|
|
|
75
69
|
## Step 2: Initialize the wizard
|
|
76
70
|
|
|
71
|
+
> **Permission-friendly invocation rule**: every `okstra wizard ...` / `okstra render-bundle ...` call below MUST start with the literal token `okstra` and use literal argument values copied from prior tool outputs. Do **not** introduce shell variables (`$STATE_FILE`, `$ANSWER`, `$projectRoot`, ...), `$(...)` command substitution, or leading assignments — they break the `Bash(okstra:*)` permission match and force a confirmation prompt on every call.
|
|
72
|
+
|
|
73
|
+
First, generate a state-file path:
|
|
74
|
+
|
|
77
75
|
```bash
|
|
78
|
-
|
|
76
|
+
okstra wizard new-state-file
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This prints one absolute path on stdout (e.g. `/var/folders/.../okstra-wizard.AbCd.json`). Read that path from the tool output and **paste it literally** into every subsequent `--state-file` argument.
|
|
79
80
|
|
|
81
|
+
Then initialize the wizard with the literal `projectRoot` / `projectId` you parsed from Step 1 and the literal state-file path from above:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
80
84
|
okstra wizard init \
|
|
81
|
-
--state-file
|
|
82
|
-
--project-root
|
|
83
|
-
--project-id
|
|
85
|
+
--state-file /var/folders/.../okstra-wizard.AbCd.json \
|
|
86
|
+
--project-root /abs/path/to/project \
|
|
87
|
+
--project-id my-project-id
|
|
84
88
|
```
|
|
85
89
|
|
|
86
90
|
Output: the same `{ok, next}` JSON described above. The first `next` is always `step: "task_pick"`.
|
|
@@ -92,10 +96,11 @@ Repeat until `next.kind == "done"`:
|
|
|
92
96
|
1. **Render** the prompt according to `kind`:
|
|
93
97
|
- `pick` → `AskUserQuestion` with `label` and `options`. The user's chosen option's `value` is the answer string.
|
|
94
98
|
- `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:
|
|
99
|
+
2. **Submit** the answer — call `okstra wizard step` with the literal state-file path from Step 2 and the literal user answer (no shell variables, no `$(...)`):
|
|
96
100
|
```bash
|
|
97
|
-
okstra wizard step --state-file
|
|
101
|
+
okstra wizard step --state-file /var/folders/.../okstra-wizard.AbCd.json --answer preprod
|
|
98
102
|
```
|
|
103
|
+
If the answer contains spaces or shell metacharacters, wrap it in double quotes around the literal string only — never inside `"$VAR"`.
|
|
99
104
|
3. **Handle result**:
|
|
100
105
|
- `ok: true` → echo `result.echo` to the user on one short line, then loop with `result.next`.
|
|
101
106
|
- `ok: false` → show `result.error` to the user verbatim, then loop with `result.current` (re-prompt the same step).
|
|
@@ -118,9 +123,11 @@ Do not second-guess the wizard. If the next prompt seems out of place, the bug i
|
|
|
118
123
|
When `next.step == "confirm"`, before relaying the picker, fetch the human-readable selection summary:
|
|
119
124
|
|
|
120
125
|
```bash
|
|
121
|
-
okstra wizard confirmation --state-file
|
|
126
|
+
okstra wizard confirmation --state-file /var/folders/.../okstra-wizard.AbCd.json
|
|
122
127
|
```
|
|
123
128
|
|
|
129
|
+
(Substitute the literal state-file path captured in Step 2 — no `$STATE_FILE`.)
|
|
130
|
+
|
|
124
131
|
Output: `{ok: true, text: "선택 확인:\n task-type : ...\n ..."}`. Print `text` to the user, then render the `confirm` picker (Proceed / Edit).
|
|
125
132
|
|
|
126
133
|
## Step 5: Render the task bundle
|
|
@@ -128,9 +135,11 @@ Output: `{ok: true, text: "선택 확인:\n task-type : ...\n ..."}`. Prin
|
|
|
128
135
|
When `next.kind == "done"`, fetch the final args:
|
|
129
136
|
|
|
130
137
|
```bash
|
|
131
|
-
okstra wizard render-args --state-file
|
|
138
|
+
okstra wizard render-args --state-file /var/folders/.../okstra-wizard.AbCd.json
|
|
132
139
|
```
|
|
133
140
|
|
|
141
|
+
(Again: literal state-file path, no `$STATE_FILE`.)
|
|
142
|
+
|
|
134
143
|
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).
|
|
135
144
|
|
|
136
145
|
```bash
|
|
@@ -160,7 +169,7 @@ okstra render-bundle \
|
|
|
160
169
|
|
|
161
170
|
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`.
|
|
162
171
|
|
|
163
|
-
You can delete
|
|
172
|
+
You can delete the literal state-file path after this point — its job is done. Invoke `rm` with the literal path (e.g. `rm /var/folders/.../okstra-wizard.AbCd.json`), not a shell variable.
|
|
164
173
|
|
|
165
174
|
## Step 6: Take over as Claude lead
|
|
166
175
|
|
|
@@ -190,11 +199,7 @@ okstra config set pr-template-path "<path>" --scope global
|
|
|
190
199
|
|
|
191
200
|
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
201
|
|
|
193
|
-
|
|
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`).
|
|
202
|
+
Read the JSON state file directly with the `Read` tool (literal path captured in Step 2) and inspect the `pr_template_scope` field — it is a plain serialized `WizardState`. Avoid `python3 -c "...$STATE_FILE"` style commands; they trip Bash static analysis.
|
|
198
203
|
|
|
199
204
|
## Concurrency
|
|
200
205
|
|
|
@@ -225,6 +225,40 @@ revert 경로와 롤백 트리거 신호를 표로 정리합니다. 추상적
|
|
|
225
225
|
- 본 섹션에는 승인 결정에 영향을 주는 *플랜 측 보충 메모*만 적습니다(예: 위험을 줄이기 위한 사전 작업, 승인 전 사용자가 확인해 두어야 할 사항). 승인 마커는 본 섹션이 아니라 상단 블록의 체크박스로만 부여합니다.
|
|
226
226
|
- 사용자가 답하거나 자료를 첨부해야만 승인이 가능한 항목은 **이 섹션에 적지 않습니다** — `## 5. Clarification Items` 표에 한 행으로 등록하고 `Blocks=approval` 로 표시하세요. 같은 항목을 두 위치에 적으면 sync 가 깨집니다.
|
|
227
227
|
|
|
228
|
+
### 4.5.9 Plan Body Verification (워커 사후 검증)
|
|
229
|
+
|
|
230
|
+
> **이 sub-section 은 `task-type` = `implementation-planning` 실행 결과에만 포함하세요.** Phase 6 에서 report-writer 가 합성한 4.5 본문(Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback)을 lead 가 plan-item 단위로 쪼개 워커들에게 다시 던지고 `AGREE / DISAGREE / SUPPLEMENT` 평결을 수집한 결과입니다. 자세한 라운드 프로토콜은 `skills/okstra-convergence/SKILL.md` "Plan-body verification mode" 섹션을 참고하세요.
|
|
231
|
+
|
|
232
|
+
- **Round count**: `<N>` (default: 1)
|
|
233
|
+
- **Gate result**: `<passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result>`
|
|
234
|
+
- `passed` → 본 보고서 상단 `User Approval Request` 체크박스가 렌더됩니다.
|
|
235
|
+
- `passed-with-dissent` → 상단 체크박스가 렌더되되, 반대 의견은 아래 `Dissent log` 에 기록.
|
|
236
|
+
- `blocked-by-disagreement` → 상단 체크박스 라인 자체를 **렌더하지 않습니다**. majority DISAGREE 인 plan item 마다 `## 5. Clarification Items` 에 `Blocks=approval` row 가 추가되며, 사용자가 응답해야 다음 phase 로 넘어갈 수 있습니다.
|
|
237
|
+
- `aborted-non-result` → 워커 dispatch 가 모두 non-result (timeout / error). 상단 체크박스 라인 렌더하지 않음 + `## 5. Clarification Items` 에 "plan-body verification could not run" row 가 추가됩니다.
|
|
238
|
+
|
|
239
|
+
#### Verdict table
|
|
240
|
+
|
|
241
|
+
각 plan item 1 행. `Plan item` 열은 `P-Opt-<N>` / `P-Step-<N>` / `P-Dep-<N>` / `P-Val-<N>` / `P-Rb-<N>` prefix 사용. `Section` 열은 4.5 내부 sub-section 번호 (예: `4.5.4`).
|
|
242
|
+
|
|
243
|
+
| Plan item | Ticket ID | Section | <worker1> | <worker2> | ... | Classification |
|
|
244
|
+
|-----------|-----------|---------|-----------|-----------|-----|----------------|
|
|
245
|
+
| P-Opt-1 | `<id>` | 4.5.1 | AGREE | AGREE | ... | full-consensus |
|
|
246
|
+
| P-Step-3 | `<id>` | 4.5.4 | DISAGREE(a) | DISAGREE(a) | ... | majority-disagree → C-7 |
|
|
247
|
+
|
|
248
|
+
- DISAGREE 셀에는 spec 의 `(a|b|c|d|e)` breakage kind 를 함께 표기 (예: `DISAGREE(a)` = 참조 file path / symbol 불일치).
|
|
249
|
+
- 마지막 열 `Classification` ∈ `{full-consensus, partial-consensus, worker-unique, majority-disagree → C-<N>}`. `majority-disagree → C-<N>` 의 `C-<N>` 은 본 보고서 `## 5. Clarification Items` 표에서 해당 변환 row 의 ID 와 일치해야 합니다.
|
|
250
|
+
|
|
251
|
+
#### Dissent log
|
|
252
|
+
|
|
253
|
+
`partial-consensus` 와 `worker-unique` 로 분류된 plan item 의 반대 의견을 plan item 별로 묶어 적습니다. `majority-disagree` 항목의 반대 의견은 본 섹션 대신 `## 5. Clarification Items` 의 해당 row 의 `Statement` 컬럼에 옮겨 적습니다.
|
|
254
|
+
|
|
255
|
+
- **P-XXX-N**: `<worker-role>` — `<반대 의견 본문 2-3 sentences>`
|
|
256
|
+
|
|
257
|
+
#### Notes
|
|
258
|
+
|
|
259
|
+
- 본 sub-section 이 누락된 채로 task-type = implementation-planning final report 가 생성되면 validator 가 `contract-violated` 로 종료합니다.
|
|
260
|
+
- `Gate result` 가 `blocked-by-disagreement` / `aborted-non-result` 인데 상단 `User Approval Request` 체크박스 라인이 존재하면 동일하게 contract violation 입니다.
|
|
261
|
+
|
|
228
262
|
## 4.6 Release Handoff Deliverables (release-handoff runs only)
|
|
229
263
|
|
|
230
264
|
> **이 섹션은 `task-type` = `release-handoff` 실행 결과에만 포함하세요. 다른 task-type에서는 섹션 전체를 삭제하고 5번 섹션으로 넘어갑니다.**
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
import sys
|
|
9
10
|
from datetime import datetime, timezone
|
|
10
11
|
from pathlib import Path
|
|
@@ -527,6 +528,35 @@ PLANNING_REQUIRED_SECTIONS = (
|
|
|
527
528
|
"Validation Checklist",
|
|
528
529
|
"Rollback",
|
|
529
530
|
"User Approval Request",
|
|
531
|
+
"Plan Body Verification",
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
PLAN_VERIFY_GATE_VALUES = (
|
|
535
|
+
"passed",
|
|
536
|
+
"passed-with-dissent",
|
|
537
|
+
"blocked-by-disagreement",
|
|
538
|
+
"aborted-non-result",
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# `Gate result:` line in §4.5.9 of the final report — captures the value
|
|
542
|
+
# token that follows. Tolerates arbitrary markdown formatting between the
|
|
543
|
+
# label and the value (backticks for inline code, double-asterisks for
|
|
544
|
+
# bold, colons, hyphens, whitespace). The captured value is then
|
|
545
|
+
# validated against `PLAN_VERIFY_GATE_VALUES` below so typo'd or unknown
|
|
546
|
+
# values surface as their own failure rather than silently no-matching.
|
|
547
|
+
_GATE_RESULT_RE = re.compile(
|
|
548
|
+
r"Gate result[^A-Za-z\n]+(?P<value>[a-z][a-z\-]+)",
|
|
549
|
+
re.IGNORECASE,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# Approval marker line — the bullet that toggles to grant approval. Both
|
|
553
|
+
# unchecked (`- [ ] Approved`) and checked (`- [x] Approved`) forms count
|
|
554
|
+
# as "checkbox present" for this gate. Line-anchored to avoid false
|
|
555
|
+
# positives from inline-code prose examples elsewhere in the template
|
|
556
|
+
# (mirrors `APPROVED_PLAN_PATTERN` in scripts/okstra_ctl/run.py:79).
|
|
557
|
+
_APPROVAL_CHECKBOX_RE = re.compile(
|
|
558
|
+
r"^[ \t]*(?:[-*+][ \t]+)?`?\[[ xX]\][ \t]*Approved`?[ \t]*$",
|
|
559
|
+
re.MULTILINE,
|
|
530
560
|
)
|
|
531
561
|
|
|
532
562
|
|
|
@@ -541,6 +571,13 @@ def validate_phase_boundary(
|
|
|
541
571
|
required deliverable sections; absence indicates a planning run that
|
|
542
572
|
skipped its core outputs (or an implementation run that ran under the
|
|
543
573
|
wrong task type).
|
|
574
|
+
|
|
575
|
+
Additionally enforces the Plan Body Verification gate (§4.5.9):
|
|
576
|
+
- gate ∈ {passed, passed-with-dissent} → top-level Approval checkbox
|
|
577
|
+
MUST be present.
|
|
578
|
+
- gate ∈ {blocked-by-disagreement, aborted-non-result} → checkbox
|
|
579
|
+
MUST be absent (lead converted findings into Clarification rows
|
|
580
|
+
instead of opening the gate).
|
|
544
581
|
"""
|
|
545
582
|
if task_type != "implementation-planning":
|
|
546
583
|
return
|
|
@@ -554,6 +591,40 @@ def validate_phase_boundary(
|
|
|
554
591
|
f"`{needle}`"
|
|
555
592
|
)
|
|
556
593
|
|
|
594
|
+
gate_match = _GATE_RESULT_RE.search(content)
|
|
595
|
+
if gate_match is None:
|
|
596
|
+
# The `Plan Body Verification` heading check above already covers
|
|
597
|
+
# the wholly-missing case; a heading present without a `Gate result`
|
|
598
|
+
# line is its own contract violation.
|
|
599
|
+
if "Plan Body Verification" in content:
|
|
600
|
+
failures.append(
|
|
601
|
+
"implementation-planning report has `Plan Body Verification` "
|
|
602
|
+
"section but no `Gate result:` line — required by §4.5.9."
|
|
603
|
+
)
|
|
604
|
+
return
|
|
605
|
+
gate_value = gate_match.group("value").strip().lower()
|
|
606
|
+
if gate_value not in PLAN_VERIFY_GATE_VALUES:
|
|
607
|
+
failures.append(
|
|
608
|
+
"implementation-planning report `Gate result` value "
|
|
609
|
+
f"`{gate_value}` is not one of "
|
|
610
|
+
f"{', '.join(PLAN_VERIFY_GATE_VALUES)}."
|
|
611
|
+
)
|
|
612
|
+
return
|
|
613
|
+
checkbox_present = _APPROVAL_CHECKBOX_RE.search(content) is not None
|
|
614
|
+
if gate_value in ("passed", "passed-with-dissent") and not checkbox_present:
|
|
615
|
+
failures.append(
|
|
616
|
+
"implementation-planning report Gate result is "
|
|
617
|
+
f"`{gate_value}` but the top-level `User Approval Request` "
|
|
618
|
+
"checkbox line (`- [ ] Approved` / `- [x] Approved`) is missing."
|
|
619
|
+
)
|
|
620
|
+
if gate_value in ("blocked-by-disagreement", "aborted-non-result") and checkbox_present:
|
|
621
|
+
failures.append(
|
|
622
|
+
"implementation-planning report Gate result is "
|
|
623
|
+
f"`{gate_value}` but a top-level `User Approval Request` "
|
|
624
|
+
"checkbox line is present — gate must NOT render the checkbox "
|
|
625
|
+
"for this gate result."
|
|
626
|
+
)
|
|
627
|
+
|
|
557
628
|
|
|
558
629
|
def _refresh_task_catalog(project_root: Path, task_manifest: dict) -> tuple[bool, str]:
|
|
559
630
|
"""Regenerate `discovery/task-catalog.json` so it stops trailing the
|
package/src/wizard.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
1
5
|
import { runPythonModule } from "./_python-helper.mjs";
|
|
2
6
|
import { resolvePaths } from "./paths.mjs";
|
|
3
7
|
|
|
@@ -7,12 +11,14 @@ Used by the okstra-run skill to drive the per-step prompt loop. Each
|
|
|
7
11
|
subcommand round-trips a JSON state file held by the skill.
|
|
8
12
|
|
|
9
13
|
Subcommands:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
new-state-file print a fresh absolute state-file path on stdout (no I/O on the file itself)
|
|
15
|
+
init seed a fresh wizard state and emit the first prompt
|
|
16
|
+
step submit an answer (or fetch the current prompt) and emit next
|
|
17
|
+
render-args emit the final --flag/value map for 'okstra render-bundle'
|
|
18
|
+
confirmation emit the multi-line confirmation echo block
|
|
14
19
|
|
|
15
20
|
Usage:
|
|
21
|
+
okstra wizard new-state-file
|
|
16
22
|
okstra wizard init --state-file <path> --project-root <p> --project-id <id>
|
|
17
23
|
okstra wizard step --state-file <path> [--answer <value>]
|
|
18
24
|
okstra wizard render-args --state-file <path>
|
|
@@ -50,11 +56,21 @@ export async function run(args) {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
const [sub, ...rest] = args;
|
|
53
|
-
if (!["init", "step", "render-args", "confirmation"].includes(sub)) {
|
|
59
|
+
if (!["new-state-file", "init", "step", "render-args", "confirmation"].includes(sub)) {
|
|
54
60
|
process.stderr.write(`error: unknown wizard subcommand '${sub}'\n\n${USAGE}`);
|
|
55
61
|
return 2;
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
if (sub === "new-state-file") {
|
|
65
|
+
if (rest.length > 0) {
|
|
66
|
+
process.stderr.write("error: new-state-file takes no arguments\n");
|
|
67
|
+
return 2;
|
|
68
|
+
}
|
|
69
|
+
const dir = mkdtempSync(join(tmpdir(), "okstra-wizard-"));
|
|
70
|
+
process.stdout.write(join(dir, "state.json") + "\n");
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
let flags;
|
|
59
75
|
try {
|
|
60
76
|
flags = parseFlags(rest);
|