okstra 0.26.0 → 0.27.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 (43) hide show
  1. package/README.kr.md +15 -0
  2. package/README.md +15 -0
  3. package/docs/kr/architecture.md +2 -6
  4. package/docs/kr/cli.md +40 -6
  5. package/docs/kr/performance-improvement-plan-v2.md +23 -0
  6. package/docs/kr/performance-improvement-plan.md +22 -0
  7. package/package.json +1 -1
  8. package/runtime/BUILD.json +2 -2
  9. package/runtime/bin/okstra.sh +0 -1
  10. package/runtime/prompts/profiles/_common-contract.md +25 -1
  11. package/runtime/prompts/profiles/error-analysis.md +12 -0
  12. package/runtime/prompts/profiles/implementation-planning.md +20 -0
  13. package/runtime/prompts/profiles/requirements-discovery.md +20 -0
  14. package/runtime/python/lib/okstra/cli.sh +1 -7
  15. package/runtime/python/lib/okstra/globals.sh +0 -1
  16. package/runtime/python/lib/okstra/usage.sh +1 -4
  17. package/runtime/python/okstra_ctl/render.py +3 -0
  18. package/runtime/python/okstra_ctl/run.py +0 -6
  19. package/runtime/python/okstra_ctl/run_context.py +1 -1
  20. package/runtime/python/okstra_ctl/wizard.py +25 -2
  21. package/runtime/python/okstra_token_usage/blocks.py +5 -1
  22. package/runtime/python/okstra_token_usage/claude.py +16 -1
  23. package/runtime/python/okstra_token_usage/collect.py +17 -3
  24. package/runtime/python/okstra_token_usage/pricing.py +159 -24
  25. package/runtime/skills/okstra-brief/SKILL.md +532 -65
  26. package/runtime/skills/okstra-context-loader/SKILL.md +25 -11
  27. package/runtime/skills/okstra-convergence/SKILL.md +37 -13
  28. package/runtime/skills/okstra-history/SKILL.md +68 -37
  29. package/runtime/skills/okstra-logs/SKILL.md +26 -4
  30. package/runtime/skills/okstra-report-finder/SKILL.md +49 -22
  31. package/runtime/skills/okstra-report-writer/SKILL.md +59 -64
  32. package/runtime/skills/okstra-run/SKILL.md +35 -34
  33. package/runtime/skills/okstra-schedule/SKILL.md +51 -20
  34. package/runtime/skills/okstra-setup/SKILL.md +31 -12
  35. package/runtime/skills/okstra-status/SKILL.md +20 -8
  36. package/runtime/skills/okstra-team-contract/SKILL.md +27 -15
  37. package/runtime/skills/okstra-time-summary/SKILL.md +53 -16
  38. package/runtime/templates/reports/settings.template.json +7 -4
  39. package/runtime/validators/lib/fixtures.sh +10 -2
  40. package/runtime/validators/lib/validate-assets.sh +50 -24
  41. package/runtime/validators/validate-brief.py +385 -0
  42. package/runtime/validators/validate-brief.sh +35 -0
  43. package/runtime/validators/validate-workflow.sh +7 -33
@@ -8,7 +8,7 @@ user-invocable: false
8
8
 
9
9
  ## File-author ownership (BLOCKING)
10
10
 
11
- The final-report file at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md` is authored by the `Report writer worker` subagent when that worker is in the run's roster. Claude lead reviews the file but does NOT write it itself in that case. Lead-authored fallback is permitted only after a real Report writer worker dispatch attempt with a recorded non-`completed` terminal status (`error` / `timeout` / `not-run`) and a logged reason (`okstra-error-log.py`).
11
+ The final-report file at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md` is authored by the `Report writer worker` subagent when that worker is in the run's roster. Claude lead reviews the file but does NOT write it itself in that case. Lead-authored fallback is permitted only after a real Report writer worker dispatch attempt with a recorded non-`completed` terminal status (`error` / `timeout` / `not-run`) and a logged reason (`okstra-error-log.py`). **Except for `release-handoff`**, which has no worker roster — the Claude lead authors the final-report directly by design (see "Release-handoff section contract" below), and the fallback rules in this section do not apply.
12
12
 
13
13
  If you are reading this skill **as the report-writer-worker subagent**, YOU are the one calling the `Write` tool against the result path. Do not return the report inline — the file on disk is the canonical artifact.
14
14
 
@@ -60,7 +60,7 @@ A resumed lead session can ALWAYS dispatch a fresh Report writer worker. The Age
60
60
 
61
61
  ### Lead-authored fallback (only if dispatch failed)
62
62
 
63
- Lead-authored fallback is permitted only if all of the following are true and recorded in team-state:
63
+ Except for `release-handoff` (which is single-lead by design and never dispatches a Report writer worker — see "Release-handoff section contract" below), lead-authored fallback is permitted only if all of the following are true and recorded in team-state:
64
64
 
65
65
  1. A Report writer worker dispatch was actually attempted (Agent call was issued).
66
66
  2. The attempt recorded a terminal status of `error`, `timeout`, or `not-run` with a concrete reason (tool error message, timeout duration, or external blocker).
@@ -68,50 +68,43 @@ Lead-authored fallback is permitted only if all of the following are true and re
68
68
 
69
69
  Speculative reasons such as "session resume constraint", "team object no longer exists", or "lead can do it faster" are NOT valid.
70
70
 
71
- ## Phase 7 follow-up task spawner (BLOCKING when Section 7 is non-empty)
71
+ ## Phase 6 → Phase 7 execution sequence (BLOCKING order)
72
72
 
73
- After the token-usage collector finishes (the next subsection), Phase 7 must run the follow-up task spawner against the final-report file. This step is what turns the report's `## 7. Follow-up Tasks (후속 작업)` table into actual `tasks/<task-group>/<new-task-id>/` stubs that show up in `okstra-status`.
73
+ The four steps below MUST execute in this exact order. Reordering them is the recurring root cause of reports shipping with unsubstituted `{{LEAD_TOTAL_TOKENS}}` placeholders, Section 6 missing the follow-up entries, or Section 7 rows never spawning.
74
74
 
75
- ```bash
76
- python3 scripts/okstra-spawn-followups.py \
77
- <runDirectoryPath>/reports/final-report-<task-type>-<seq>.md \
78
- --project-root <project_root> \
79
- --task-group <task-group> \
80
- --parent-task-key <task-key>
81
- ```
82
-
83
- Behaviour contract:
84
-
85
- - The script is **idempotent**: rows whose target directory already exists are reported as `existing` and skipped without modification. Re-running the spawner across reruns of the same parent task is safe.
86
- - Rows with `Auto-spawn? != yes` are reported as `skipped` and never written to disk. Surface them in the final-report's Section 6 if the user should still take manual action.
87
- - A row with an invalid `Origin`, `Suggested task-type`, missing `Title`, or missing `Reason / Why deferred` causes the script to exit `1`. The report-writer worker MUST refuse to ship a final-report whose Section 7 contains such rows. Either fix the row or change `Auto-spawn?` to `no` and document why in Section 6.
88
- - For `task-type` ∈ {`implementation`, `final-verification`, `release-handoff`}, Section 7 must be present in the final report. An empty section is acceptable and is expressed as the single line `- 후속 작업 없음.` under the heading. The spawner treats a missing or empty section as a no-op (exit `0`).
89
-
90
- After the spawner completes, the report-writer worker MUST update Section 6 ("Recommended Next Steps") to list every newly created task-key together with its entry command, so the user can pick the follow-up up immediately:
75
+ 1. **Phase 6 — Report writer worker drafts the final-report file** at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md`. Token placeholders are left verbatim; Section 6 lists prioritized actions but does NOT yet include auto-spawned follow-ups (they don't exist yet).
76
+ 2. **Phase 7 step 1 — Token-usage collector with `--substitute-final-report`** (BLOCKING). One invocation aggregates `leadUsage` / `workers[].usage` / `usageSummary` into team-state AND substitutes the 10 placeholders in the final-report file. Skipping the flag ships literal `{{...}}` in the Token Usage Summary table.
91
77
 
92
- ```
93
- - Follow-up: `<task-group>/<new-task-id>` — Claude Code 세션 안 `/okstra-run task-key=<task-group>/<new-task-id> task-type=<suggested>` / 별도 터미널 `scripts/okstra.sh --task-key <task-group>/<new-task-id> --task-type <suggested>`
94
- ```
78
+ ```bash
79
+ python3 scripts/okstra-token-usage.py \
80
+ <runDirectoryPath>/state/team-state-<task-type>-<seq>.json \
81
+ --write --summary \
82
+ --substitute-final-report <runDirectoryPath>/reports/final-report-<task-type>-<seq>.md
83
+ ```
95
84
 
96
- ## Phase 7 token-usage collector (BLOCKING)
85
+ The 10 substituted 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}}`. The final-report file MUST already exist (Phase 6 output).
86
+ 3. **Phase 7 step 2 — Follow-up task spawner** (BLOCKING when Section 7 is non-empty). Turns the report's `## 7. Follow-up Tasks (후속 작업)` rows into `tasks/<task-group>/<new-task-id>/` stubs.
97
87
 
98
- At the start of Phase 7, run the token-usage collector with the final-report substitution flag. This step is BLOCKING — both the team-state aggregation AND the final-report placeholder substitution happen here, in one invocation:
99
-
100
- ```bash
101
- python3 scripts/okstra-token-usage.py \
102
- <runDirectoryPath>/state/team-state-<task-type>-<seq>.json \
103
- --write --summary \
104
- --substitute-final-report <runDirectoryPath>/reports/final-report-<task-type>-<seq>.md
105
- ```
88
+ ```bash
89
+ python3 scripts/okstra-spawn-followups.py \
90
+ <runDirectoryPath>/reports/final-report-<task-type>-<seq>.md \
91
+ --project-root <project_root> \
92
+ --task-group <task-group> \
93
+ --parent-task-key <task-key>
94
+ ```
106
95
 
107
- This:
96
+ Behaviour contract:
97
+ - Idempotent: rows whose target dir exists are reported as `existing` and skipped. Reruns of the same parent task are safe.
98
+ - Rows with `Auto-spawn? != yes` are reported as `skipped` and never written; surface them in Section 6 if manual action is still needed.
99
+ - An invalid `Origin`, `Suggested task-type`, missing `Title`, or missing `Reason / Why deferred` exits `1`. The report-writer MUST refuse to ship a Section 7 with such rows.
100
+ - **Canonical spawn rule (single source of truth):** the spawner runs when `task-type` ∈ {`implementation`, `final-verification`, `release-handoff`}, OR when Section 7 is non-empty for any other task-type. For the listed task-types Section 7 must be present in the report; an empty section renders as `- 후속 작업 없음.`. Missing / empty sections are no-ops (exit `0`). All other references to this rule (including the Persistence Checklist) defer to this statement.
101
+ 4. **Phase 7 step 3 — Update Section 6** after the spawner. The report-writer MUST append one row per newly spawned task-key with its entry command:
108
102
 
109
- - Populates `leadUsage`, every `workers[].usage`, and `usageSummary` in team-state from session transcripts.
110
- - Substitutes the 10 token-related 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}}`) in the final-report file with concrete values from the freshly computed usageSummary.
103
+ ```
104
+ - Follow-up: `<task-group>/<new-task-id>` Claude Code 세션 `/okstra-run task-key=<task-group>/<new-task-id> task-type=<suggested>` / 별도 터미널 `scripts/okstra.sh --task-key <task-group>/<new-task-id> --task-type <suggested>`
105
+ ```
111
106
 
112
- Skipping `--substitute-final-report` is the recurring root cause of reports shipping with literal `{{LEAD_TOTAL_TOKENS}}` etc. in their Token Usage Summary table. Always pass the flag — never run the collector without it during Phase 7.
113
-
114
- The final-report file MUST already exist before this step runs (it's authored by Report writer worker in Phase 6, or by Lead in the documented fallback case). The status file can be written after this step completes.
107
+ The status file is written after step 3 completes.
115
108
 
116
109
  ## Final Report Structure
117
110
 
@@ -181,7 +174,7 @@ Token Summary Generation Rules:
181
174
 
182
175
  ### Implementation-planning section heading contract (BLOCKING)
183
176
 
184
- When the run's `task-type` is `implementation-planning`, the final report MUST contain section headings whose **lines include each of the 8 literal English substrings below**. The validator (`validators/validate-run.py`) does plain substring matching on the report text — 7-of-8 missing was a real, repeatedly observed failure mode caused by translating the headings to Korean.
177
+ When the run's `task-type` is `implementation-planning`, the final report MUST contain section headings whose **lines include each of the 9 literal English substrings below**. The validator (`validators/validate-run.py`) does plain substring matching on the report text — missing headings was a real, repeatedly observed failure mode caused by translating the headings to Korean.
185
178
 
186
179
  | # | Required substring | Recommended heading form |
187
180
  |---|--------------------|--------------------------|
@@ -193,11 +186,26 @@ When the run's `task-type` is `implementation-planning`, the final report MUST c
193
186
  | 6 | `Validation Checklist` | `### Validation Checklist (검증 체크리스트)` |
194
187
  | 7 | `Rollback` | `### Rollback Strategy (롤백 전략)` |
195
188
  | 8 | `User Approval Request` | `### User Approval Request (사용자 승인 요청)` |
189
+ | 9 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (계획 본문 검증)` containing a `Gate result:` line — copy `okstra-final-report.template.md §4.5.9` verbatim. Validator (`validators/validate-run.py` lines 531, 548) checks both substrings. |
196
190
 
197
191
  The Korean translation in parentheses is optional but the English keyword is mandatory. The body of each section is written in Korean per the writing rules below. For non-`implementation-planning` runs, omit this entire block — these headings are NOT validator-checked for other task-types.
198
192
 
199
193
  The final-report template `okstra-final-report.template.md` Section 4.5 already encodes this contract — copy that block verbatim and fill in.
200
194
 
195
+ ### Final-verification verdict token contract (BLOCKING)
196
+
197
+ When the run's `task-type` is `final-verification`, the report's `## 2. Final Verdict` table MUST contain a `Verdict Token` row whose value is **exactly one of** the literal strings below. The `release-handoff` profile reads this row as its entry gate; any other value blocks the next phase.
198
+
199
+ | # | Required substring | Meaning |
200
+ |---|--------------------|---------|
201
+ | 1 | `accepted` | All acceptance criteria pass; `release-handoff` may proceed. |
202
+ | 2 | `conditional-accept` | Acceptance passes with caveats; user must resolve listed conditions before `release-handoff`. |
203
+ | 3 | `blocked` | Acceptance failed; routing returns to `error-analysis` or `implementation-planning`. |
204
+
205
+ For every other task-type, set the `Verdict Token` cell to `not-applicable`. Do NOT omit the row — the template renders it for all task-types and downstream tooling expects the field to exist.
206
+
207
+ The final-report template `okstra-final-report.template.md` Section 2 already encodes this contract — copy that block verbatim and fill in.
208
+
201
209
  ### Release-handoff section contract (release-handoff runs only)
202
210
 
203
211
  When the run's `task-type` is `release-handoff`, the final report MUST include Section `## 4.6 Release Handoff Deliverables` with all seven sub-sections (`4.6.1` Source Verification Report, `4.6.2` Feature Branch & Working-Tree State, `4.6.3` User Selections, `4.6.4` Executed Commands, `4.6.5` Commit List, `4.6.6` Pull Request Outcome, `4.6.7` Routing Recommendation). Every entry is dictated by the lead's recorded git/gh command log and the user's verbatim answers to the H1/H2/H3 menu prompts. H1 choices are `local only`, `push + PR`, or `skip`; release-handoff records existing implementation commits and MUST NOT create new commits. If the user picked `skip` (H1) or `cancel` (H3), keep 4.6.3 populated but leave 4.6.4–4.6.6 explicitly empty per the template's empty-state lines.
@@ -216,35 +224,22 @@ runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md
216
224
 
217
225
  This file is checked by the validator whenever the role's terminal status is `completed`. Without it the run fails with `report-writer is completed but worker result file is missing`.
218
226
 
219
- The file content is short: it begins with the standard worker-result header from `okstra-team-contract`, then names the canonical final-report path you wrote, lists the input artifacts you reconciled, and records any structural deviations from `final-report-template.md`. Do NOT duplicate the full final-report body here — it's an audit pointer, not a second copy.
227
+ **Frontmatter + header schema** — both the worker-results audit file AND the final-report file are governed by `okstra-team-contract` ("Result Frontmatter" and the standard worker-result header sections). That document is the single source of truth; do NOT restate the field list here. Use `workerId: "report-writer"` for both files and copy every other frontmatter value verbatim from `analysis-material.md`. The body of this audit file is short: name the canonical final-report path you wrote, list the input artifacts you reconciled, and record any structural deviations from `final-report.template.md`. Do NOT duplicate the full final-report body here — it's an audit pointer, not a second copy.
220
228
 
221
229
  Skipping this file because "the real report is in `reports/`" is wrong. Both files are required.
222
230
 
223
231
  ### Main Body Section
224
232
 
225
- Section numbering matches `okstra-final-report.template.md`. Section 0 is the carry-in reconciliation that runs first when a clarification response was provided; sections 1–7 follow the template's main body order.
226
-
227
- 0. **Clarification Response Carried In** - if `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty, read `instruction-set/clarification-response.md`, walk every `C-*` row of the prior report's `## 5. Clarification Items` table, reconcile each one against new evidence, and record the outcome (`resolved`/`obsolete`) plus the citation in this section before drafting the verdict. If the prior report uses the deprecated `4.5.9 Open Questions` / `5.1` / `5.2` layout with `OQ-*`/`A*`/`Q*` IDs, follow the legacy-carry-in mapping rule in `final-report-template.md` section 0.
228
- 1. **Problem or Verification Summary** - Key summary based on the brief and data (3–5 bullet points)
229
- 2. **Cross Verification Results** (Use 4 categories when convergence is enabled, per `okstra-convergence`)
230
- - Round History sub-table (convergence-enabled runs only): one row per executed round with columns `Round | inputQueueSize | resolvedCount | carriedForwardCount | dispatches (worker:status:durationMs) | skippedWorkers (worker:reason)`. Add a one-line note immediately under the table with `round2SkippedReason: <value>` (always present, even when `"not-skipped"`). Pull all values verbatim from `convergence-<task-type>-<seq>.json`.
231
- - Full Consensus: Findings agreed upon by all workers
232
- - Partial Consensus: Agreed upon by a majority of workers; dissenting opinions are specified
233
- - Contested: No consensus after the last executed round; each worker’s position specified. Empty contested list is shown as the literal line `- 합의 미달 항목 없음.`
234
- - Worker-Unique: Verified only by the discoverer; verification history specified
235
- - In runs with convergence disabled, maintain the existing Consensus/Differences format and omit the Round History sub-table.
236
- 3. **Final Verdict** - Conclusion based on comprehensive evidence; direction provided. For `final-verification`, include a `Verdict Token` field whose value is exactly `accepted`, `conditional-accept`, or `blocked`; `release-handoff` uses that field as its entry gate.
237
- 4. **Evidence and Detailed Analysis**
238
- - Key Evidence: File path, line number, actual evidence
239
- - If explicit expected values are present in `reference-expectations.md`, specify whether they match or differ from the expected values in config files / deployment manifests
240
- - Supporting evidence or alternative interpretations
241
- 5. **Missing Information and Risks** - Uncertain/I don't know items
242
- 6. **Clarification Items** - single unified table (`C-001`, `C-002`, ...) the user fills inline before reruns. Columns: `ID`, `Ticket ID`, `Kind` (`material` / `decision` / `data-point`), `Statement`, `Expected form`, `Blocks` (`approval` / `next-phase` / `none`), `Status`, `User input`. Replaces the legacy `4.5.9 Open Questions` / `5.1` / `5.2` triple; never create those sub-sections — same item appearing in two places is the failure mode this table prevents.
243
- - Required for `task-type` `error-analysis` and `requirements-discovery` whenever blocking uncertainty remains
244
- - Optional for other task-types; explicitly state "no clarification needed" when none
245
- - Follow the table format from `final-report-template.md` exactly (columns: Question ID, Blocking, Why this matters, Question, Expected answer shape, Status, Answer)
246
- - Use stable `Q1`, `Q2`, ... ids and never delete prior ids on rerun; mark them `resolved` or `obsolete` instead
247
- 7. **Recommended Next Steps** - Actions by Priority
233
+ Section numbering follows `templates/reports/final-report.template.md` exactly that file is the single source of truth. Below is a one-line summary of each section's writer obligation; consult the template for full body structure.
234
+
235
+ 0. **Clarification Response Carried In** if `{{CLARIFICATION_RESPONSE_RELATIVE_PATH}}` is non-empty, walk every `C-*` row of the prior report's `## 5. Clarification Items` table, reconcile each one against new evidence, and record the outcome (`resolved` / `obsolete`) with citation before drafting the verdict. Legacy `4.5.9 / 5.1 / 5.2` carry-in mapping is documented in the template's Section 0.
236
+ 1. **Cross Verification Results** — 4 categories (Full / Partial / Contested / Worker-Unique) when convergence is enabled, per `okstra-convergence`. Prepend the Round History sub-table (columns: `Round | inputQueueSize | resolvedCount | carriedForwardCount | dispatches | skippedWorkers`) plus a `round2SkippedReason: <value>` note, pulled verbatim from `convergence-<task-type>-<seq>.json`. Empty contested list renders as `- 합의 미달 항목 없음.`. Convergence-disabled runs use the legacy Consensus/Differences format and omit the round table.
237
+ 2. **Final Verdict** `Direction` `continue-investigation` / `begin-implementation` / `approve` / `reject` / `hold`. **Verdict Token** is `not-applicable` for every task-type except `final-verification` — see "Final-verification verdict token contract" below for that case.
238
+ 3. **Evidence and Detailed Analysis** primary evidence rows (file path, line, snippet); secondary evidence / alternate interpretations. If `reference-expectations.md` lists explicit expected values, record match/gap per row.
239
+ 4. **Missing Information and Risks** — uncertain / "I don't know" items. `implementation-planning` adds §4.5 (see heading contract below); `release-handoff` adds §4.6.
240
+ 5. **Clarification Items** single unified `C-*` table; column schema, ID convention, and rerun behaviour are owned by `_common-contract.md §Clarification request policy` (8-column SSOT). Never recreate the deprecated `4.5.9 Open Questions` / `5.1` / `5.2` sub-sections.
241
+ 6. **Recommended Next Steps** prioritized actions. After Phase 7's follow-up spawner runs, append a row per newly created task-key (see "Phase 6 Phase 7 execution sequence" above).
242
+ 7. **Follow-up Tasks** — auto-spawn-eligible table. Each row drives `okstra-spawn-followups.py`; see template §7 for the row schema.
248
243
 
249
244
  ### Writing Guidelines
250
245
 
@@ -280,7 +275,7 @@ Persistence steps that must be performed in Phase 7:
280
275
  - [ ] 5. **Update task-index.md**: Refresh human-readable summary
281
276
  - [ ] 6. **Generate final status file**: `runs/<task-type>/status/final-<task-type>-<seq>.status` (if necessary)
282
277
  - [ ] 7. **Save convergence state**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (when convergence is enabled)
283
- - [ ] 8. **Spawn follow-up task stubs**: run `scripts/okstra-spawn-followups.py` against the final-report when the run's `task-type` {`implementation`, `final-verification`, `release-handoff`}, OR when Section 7 is non-empty for any other task-type. See "Phase 7 follow-up task spawner" above for the exact command and contract. The script is idempotent across reruns.
278
+ - [ ] 8. **Spawn follow-up task stubs**: run `scripts/okstra-spawn-followups.py` against the final-report per the canonical spawn rule defined in "Phase 7 follow-up task spawner" above. Do not restate the trigger condition here — that section is the single source of truth. The script is idempotent across reruns.
284
279
 
285
280
  ### Response after Persistence
286
281
 
@@ -18,7 +18,7 @@ Launch an okstra task — gather inputs interactively via the **wizard state mac
18
18
 
19
19
  ## When NOT to Use
20
20
 
21
- - User explicitly asks to spawn a new terminal / new claude — use `okstra-history` Step 4 (resume command) or instruct them to run `okstra.sh` in another terminal.
21
+ - User explicitly asks to spawn a new terminal / new claude — use `okstra-history` Step 4 (resume command) or instruct them to run `okstra` in another terminal.
22
22
  - User wants status only — use `okstra-status`.
23
23
  - User wants past runs — use `okstra-history`.
24
24
 
@@ -48,29 +48,26 @@ Never invent additional questions. Never reorder. Never use `AskUserQuestion` fo
48
48
 
49
49
  ## Step 1: Verify okstra runtime + project setup
50
50
 
51
- ```bash
52
- if command -v okstra >/dev/null 2>&1; then
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; }
57
- else
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; }
62
- fi
63
- ```
51
+ Run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&` — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's JSON output and decides what to do next in natural language — never in shell.
64
52
 
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.
53
+ 1. `okstra ensure-installed`
54
+ If this exits non-zero, tell the user: "okstra runtime missing — run `npx okstra@latest install` once and retry." Then stop.
66
55
 
67
- Parse `projectRoot` and `projectId` from that JSON output.
56
+ 2. `okstra paths --json`
57
+ Read `pythonPath` and other path fields from the JSON output. **Do not** `export PYTHONPATH=...` from this skill — every subsequent `okstra <subcmd>` call already self-bootstraps its Python path. Keep the parsed values in mind only as diagnostic context.
68
58
 
69
- ## Step 2: Initialize the wizard
59
+ 3. `okstra check-project --json`
60
+ Reads the project from the current working directory. Parse the JSON from stdout. If `ok` is `false`, ask the user with a **plain text prompt** for an absolute project-root path, then rerun as a separate tool call (still literal-token-first):
61
+ `okstra check-project --cwd /abs/path/from/user --json`
62
+ Substitute the literal absolute path the user gave you (no `$(pwd)`, no shell variables). Re-prompt with plain text on continued failure.
63
+
64
+ Parse `projectRoot` and `projectId` from the successful `check-project --json` output and carry them as literal strings into Step 2.
70
65
 
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.
66
+ > If the `okstra` binary is not on `PATH` at all, none of the commands above will run. In that case tell the user verbatim: "okstra not installed run `npx okstra@latest install` once, then retry this skill." Do **not** try to invoke `npx -y okstra@latest ...` from this skill `npx` is not on the literal-token allow-list and will force a confirmation prompt on every wizard call afterward.
72
67
 
73
- First, generate a state-file path:
68
+ ## Step 2: Initialize the wizard
69
+
70
+ First, generate a state-file path (Bash invocation rule from the top of this file applies to every command below):
74
71
 
75
72
  ```bash
76
73
  okstra wizard new-state-file
@@ -101,6 +98,14 @@ Repeat until `next.kind == "done"`:
101
98
  okstra wizard step --state-file /var/folders/.../okstra-wizard.AbCd.json --answer preprod
102
99
  ```
103
100
  If the answer contains spaces or shell metacharacters, wrap it in double quotes around the literal string only — never inside `"$VAR"`.
101
+
102
+ **MANDATORY: empty answers must pass `--answer ""` explicitly.** If the user's reply is the empty string, the call MUST still include the flag with an empty literal value:
103
+ ```bash
104
+ okstra wizard step --state-file /var/folders/.../okstra-wizard.AbCd.json --answer ""
105
+ ```
106
+ Omitting `--answer` entirely is forbidden. The wizard interprets a missing `--answer` flag as "re-emit the current prompt" (a `get-current-prompt` style no-op), not as "submit empty" — so dropping the flag will loop the same prompt forever. Submitting `--answer ""` is the only way to advance past an intentionally-blank step (e.g. "use phase default").
107
+
108
+ **Escaping rule**: if the literal answer contains `"`, escape each occurrence as `\"` inside the double-quoted argument. Empty values must still be `--answer ""` — the flag itself is mandatory, even when the value is empty.
104
109
  3. **Handle result**:
105
110
  - `ok: true` → echo `result.echo` to the user on one short line, then loop with `result.next`.
106
111
  - `ok: false` → show `result.error` to the user verbatim, then loop with `result.current` (re-prompt the same step).
@@ -126,8 +131,6 @@ When `next.step == "confirm"`, before relaying the picker, fetch the human-reada
126
131
  okstra wizard confirmation --state-file /var/folders/.../okstra-wizard.AbCd.json
127
132
  ```
128
133
 
129
- (Substitute the literal state-file path captured in Step 2 — no `$STATE_FILE`.)
130
-
131
134
  Output: `{ok: true, text: "선택 확인:\n task-type : ...\n ..."}`. Print `text` to the user, then render the `confirm` picker (Proceed / Edit).
132
135
 
133
136
  ## Step 5: Render the task bundle
@@ -138,10 +141,12 @@ When `next.kind == "done"`, fetch the final args:
138
141
  okstra wizard render-args --state-file /var/folders/.../okstra-wizard.AbCd.json
139
142
  ```
140
143
 
141
- (Again: literal state-file path, no `$STATE_FILE`.)
142
-
143
144
  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).
144
145
 
146
+ **Empty-value rule (same as Step 3)**: every flag whose value is the empty string MUST still be passed explicitly as `--<key> ""`. For example: `--workers ""`, `--directive ""`, `--related-tasks ""`. Omitting the flag is forbidden — `render-bundle` distinguishes "flag absent" from "flag present with empty value", and the wizard's intent is always the latter.
147
+
148
+ **Escaping rule**: inside a double-quoted value, escape any literal `"` as `\"`. Do not collapse `--key ""` into `--key` even when the value is empty.
149
+
145
150
  ```bash
146
151
  okstra render-bundle \
147
152
  --project-root "<args.project-root>" \
@@ -173,13 +178,7 @@ You can delete the literal state-file path after this point — its job is done.
173
178
 
174
179
  ## Step 6: Take over as Claude lead
175
180
 
176
- Read these files (do not paraphrase) and enter `Claude lead` mode:
177
-
178
- 1. `<INSTRUCTION_SET_DIR>/claude-execution-prompt.md` — the lead prompt
179
- 2. `<INSTRUCTION_SET_DIR>/analysis-profile.md` — per-task-type allowed outputs / forbidden actions
180
- 3. `<INSTRUCTION_SET_DIR>/analysis-material.md` — task brief + directive
181
- 4. `<INSTRUCTION_SET_DIR>/reference-expectations.md`
182
- 5. `<INSTRUCTION_SET_DIR>/final-report-template.md`
181
+ Read `<INSTRUCTION_SET_DIR>/claude-execution-prompt.md` verbatim and enter `Claude lead` mode. The lead prompt itself enumerates every other instruction-set file to load (`analysis-profile.md`, `analysis-material.md`, `reference-expectations.md`, `final-report-template.md`, the run manifest, the team-state artifact, etc.) — follow its order, do not preempt it.
183
182
 
184
183
  Then proceed through the phases exactly as the lead prompt directs (Phase 1 context → Phase 2+ worker dispatch → final synthesis → final report).
185
184
 
@@ -197,15 +196,17 @@ okstra config set pr-template-path "<path>" --scope project
197
196
  okstra config set pr-template-path "<path>" --scope global
198
197
  ```
199
198
 
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:
199
+ The scope is held in the wizard state but is not yet exposed by any `okstra wizard` subcommand. Until the subcommand below ships, 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`. Do not shell out (`python3 -c`, `jq`, etc.); the literal-token Bash rule rejects them.
200
+
201
+ ## Out-of-scope backlog
201
202
 
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.
203
+ - **`okstra wizard pr-template-scope --state-file PATH`**: add a thin subcommand to `scripts/okstra_ctl/wizard.py` that prints `{ok: true, scope: "once" | "project" | "global"}` so this skill can drop the `Read`-the-raw-state-file detour. The subcommand should reuse the existing `load_state_file` path; no schema changes required.
203
204
 
204
205
  ## Concurrency
205
206
 
206
207
  - `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.
207
- - Each wizard run owns its own `$STATE_FILE`; two parallel skill invocations do not collide.
208
- - 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.
208
+ - Each wizard run owns its own state file (one per `okstra wizard new-state-file`); two parallel skill invocations do not collide.
209
+ - 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.
209
210
 
210
211
  ## Failure Modes
211
212
 
@@ -10,7 +10,7 @@ model: opus
10
10
 
11
11
  Generate a consolidated work schedule for all non-done tasks in a given `task-group`. The skill reads each task's `task-manifest.json` and `latestReport`, classifies tasks into phases by priority and risk, and writes a single Markdown plan file under `.project-docs/okstra/tasks/<task-group>/schedule/`.
12
12
 
13
- The default mode is lightweight (single Claude lead synthesis). The `--cross-verify` option triggers the full okstra multi-agent flow on the schedule itself.
13
+ The skill runs as a single Claude lead synthesis (lightweight mode). A `--cross-verify` multi-agent variant was previously sketched here but never specified end-to-end; it has been dropped pre-1.0 and is tracked as a follow-up if multi-agent schedule verification is needed later.
14
14
 
15
15
  ## When to Use
16
16
 
@@ -30,12 +30,12 @@ The default mode is lightweight (single Claude lead synthesis). The `--cross-ver
30
30
 
31
31
  **Explicit command**:
32
32
  - `okstra schedule <task-group>`
33
- - `okstra schedule <task-group> --cross-verify`
34
33
  - `okstra schedule <task-group> --title "<custom title>"`
34
+ - `okstra schedule <task-group> --directive-file <abs-path>` (optional; see "Directive override" below)
35
35
 
36
36
  If `--title` is omitted, derive a default title from `task-group` (e.g. `uploadFont` → `uploadFont — Work Schedule`).
37
37
 
38
- ## Step 0: Verify okstra runtime + project setup
38
+ ## Preflight: Verify okstra runtime + project setup
39
39
 
40
40
  Run before anything else in this skill:
41
41
 
@@ -81,17 +81,19 @@ This skill performs cross-task synthesis (multi-task classification, dependency
81
81
  ### Step 1: Resolve task-group and collect tasks
82
82
 
83
83
  1. Read `.project-docs/okstra/discovery/task-catalog.json`.
84
- 2. Filter entries where `taskGroupPathSegment == <task-group lowercased>` OR `taskGroup == <task-group>` (case-insensitive match).
84
+ 2. **Normalise the user-supplied `<task-group>` argument:** lowercase it, then strip every character that is not `[a-z0-9]` (drop spaces, hyphens, underscores, dots, etc.). Apply the same transform to each entry's `taskGroupPathSegment`. Match when the two normalised forms are equal. This is the single comparison rule — do NOT also fall back to the raw `taskGroup` field.
85
85
  3. If no tasks found, output `해당 task-group을 찾을 수 없습니다.` and stop.
86
86
  4. For each matched task, read `.project-docs/okstra/tasks/<task-group-segment>/<task-id-segment>/task-manifest.json` directly. Catalog data may be stale; the manifest is authoritative.
87
87
  5. **Derive `<project-id>`** for the schedule header: prefer `task-catalog.json`'s top-level `projectId` field if present, otherwise use the first matched manifest's `projectId` field. Do not invent a value.
88
88
 
89
89
  ### Step 2: Filter by workStatus
90
90
 
91
- For each task-manifest:
92
- - If `workStatus` field is missing or empty → treat as `in-progress` (include).
93
- - If `workStatus == "done"` → exclude.
94
- - Otherwise (`todo` / `in-progress` / `blocked`) → include.
91
+ Since the `feat(scripts/render): project workStatus fields into task-catalog entries` change (commit `c44c36b`), `workStatus` is also surfaced directly inside each catalog entry — Step 1 still re-reads each `task-manifest.json` because the manifest is authoritative, but no extra fetch is needed beyond that.
92
+
93
+ For inference rules when `workStatus` is missing or empty, **defer to the inference table in `skills/okstra-status/SKILL.md` ("Step 4 Inferring workStatus")** instead of duplicating it here. Apply that table to derive a working value, then filter:
94
+
95
+ - If the resolved value is `done` (set by the user OR inferred via `currentStatus == "completed"` + terminal next-phase) → exclude.
96
+ - Otherwise (`todo` / `in-progress` / `blocked` / `phase-done` / explicit user value / inferred non-done) → include.
95
97
 
96
98
  If after filtering 0 tasks remain, output:
97
99
  ```
@@ -133,20 +135,35 @@ If the report file does not exist or cannot be parsed:
133
135
  If parsing fails for a specific section:
134
136
  - Mark the entry with `[PARSE-ERROR: <section>]` and continue.
135
137
 
136
- ### Step 4: Mode branching
138
+ ### Step 4: Synthesis
137
139
 
138
- - **Default (lightweight)**: Claude lead synthesizes the schedule directly using collected data. Proceed to Step 5.
139
- - **`--cross-verify`**: Invoke the parent `okstra` skill flow with the schedule synthesis itself as the analysis target. The schedule generation becomes the cross-verified deliverable.
140
+ Claude lead synthesises the schedule directly using collected data. Proceed to Step 5.
140
141
 
141
142
  ### Step 5: Phase classification heuristic
142
143
 
143
- Classify each task into one of three phases based on report data:
144
+ Classify each task into one of three phases based on report data.
145
+
146
+ **`workCategory` accepted values** (de-facto enum, from `scripts/okstra_ctl/worktree.py::_WORK_CATEGORY_PREFIX` plus the `unknown` fallback emitted by `render.py`):
147
+
148
+ | workCategory | Default phase |
149
+ |--------------|---------------|
150
+ | `bugfix` | Phase 1 (when risk is High/Med-High); otherwise Phase 2 |
151
+ | `feature` | Phase 2 |
152
+ | `improvement` | Phase 2 |
153
+ | `refactor` | Phase 3 |
154
+ | `ops` | Phase 3 |
155
+ | `docs` / `doc` | Phase 2 |
156
+ | `unknown` (or unmatched / missing) | **Phase 2**, with a one-line rationale `> _workCategory '<raw-value>' 미정의 — Phase 2로 기본 분류._` at the top of that phase section |
157
+
158
+ Priority overrides category:
159
+
160
+ - **Phase 1 (안정성/Critical)**: priority `P0`, OR `workCategory == "bugfix"` with High / Med-High risk
161
+ - **Phase 2 (개선/기능)**: priority `P1` or `P2`, OR `workCategory in {"feature", "improvement", "docs", "doc"}`, OR fallback per the table above
162
+ - **Phase 3 (확장/아키텍처)**: priority `P3`, OR `workCategory in {"refactor", "ops"}`, OR multi-repo + infrastructure scope
144
163
 
145
- - **Phase 1 (안정성/Critical)**: priority `P0`, OR `workCategory == "bugfix"` with high risk
146
- - **Phase 2 (개선/기능)**: priority `P1` or `P2`, `workCategory in {"improvement", "feature"}`
147
- - **Phase 3 (확장/아키텍처)**: priority `P3`, OR multi-repo + infrastructure scope
164
+ When the classification is genuinely ambiguous after applying the table + priority override, place the task in the closest phase and add a one-line rationale at the top of that phase section.
148
165
 
149
- When the classification is ambiguous, place the task in the closest phase and add a one-line rationale at the top of that phase section.
166
+ > Note: enum codification (turning this de-facto list into a `validators/`-enforced contract) is out of scope for this skill file as a follow-up if needed.
150
167
 
151
168
  ### Step 6: Write the schedule file
152
169
 
@@ -168,7 +185,7 @@ After writing the file, reply briefly:
168
185
  - 포함 task: N개
169
186
  - 제외(done) task: M개
170
187
  - 예상 소요: X.X ~ Y.Y days (Effort 합산)
171
- - 모드: lightweight | cross-verify
188
+ - 모드: lightweight
172
189
  ```
173
190
 
174
191
  ## Audience (READ FIRST — drives everything below)
@@ -312,7 +329,17 @@ When skipping, follow the skip-reason note rule below — but the skip should be
312
329
 
313
330
  #### Directive override (highest priority)
314
331
 
315
- Before applying the heuristics above, **check `instruction-set/analysis-material.md` for a `## Directive` section** (sourced from the `--directive` CLI flag). If that section is present and expresses any directive that affects Gantt rendering — e.g. "render a Gantt even with single XL task", "no Gantt needed" — that directive **overrides the heuristic and the skip-reason rule** for the affected section. When following a Directive override that contradicts the default heuristic, append a one-line note inside the rendered (or skipped) section: `> _Per Directive directive: <verbatim short excerpt>._` so the reader can trace why the section appeared/disappeared against the default. The Directive may also pre-supply day allocations, phase weights, or sub-task decompositions — use those verbatim as bar lengths in the Gantt.
332
+ Before applying the heuristics above, **check the schedule-level directive source** for a `## Directive` section.
333
+
334
+ **Resolution order (first hit wins):**
335
+
336
+ 1. Absolute path passed via the `--directive-file <abs-path>` argument when invoking the skill.
337
+ 2. `<PROJECT_ROOT>/.project-docs/okstra/tasks/<task-group-segment>/schedule/instruction-set/analysis-material.md` (the canonical schedule-level instruction-set location — okstra writes here when a schedule run is dispatched with `--directive`).
338
+ 3. **No directive file** — fall through and apply the default heuristic without any override. This is the normal case; do not warn, do not block.
339
+
340
+ If a directive source is found and contains a `## Directive` section that affects Gantt rendering — e.g. "render a Gantt even with single XL task", "no Gantt needed" — that directive **overrides the heuristic and the skip-reason rule** for the affected section. When following a Directive override that contradicts the default heuristic, append a one-line note inside the rendered (or skipped) section: `> _Per Directive directive: <verbatim short excerpt>._` so the reader can trace why the section appeared/disappeared against the default. The Directive may also pre-supply day allocations, phase weights, or sub-task decompositions — use those verbatim as bar lengths in the Gantt.
341
+
342
+ A directive file that exists but contains no `## Directive` heading is treated as "no directive" — fall through to the heuristic, no warning required.
316
343
 
317
344
  `## Gantt Chart` is the only `##` section that MAY be added beyond the 11 mandatory ones. Any other extra `##` heading is forbidden.
318
345
 
@@ -415,6 +442,10 @@ Required shapes:
415
442
  | Skipping `## Gantt Chart` because the data is "wide range / single task / part-day allocation pending" | Per the render-by-default rule, these are reasons to render an **estimate-tagged** chart, not to skip. Skip is only legitimate when day data literally does not exist (all-XXL or no effort sizing anywhere) |
416
443
  | Rendering Gantt as a mermaid (`` ```mermaid `` / `gantt` block) or any other graph DSL (PlantUML, Graphviz, etc.) | This skill renders ASCII only. Mermaid output is forbidden — use the ASCII Gantt format described above. Validators may reject mermaid blocks under this heading |
417
444
 
445
+ ## Known Validator Gaps (out of scope here)
446
+
447
+ - The ambiguous-classification rationale required by Step 5 (`> _workCategory '<raw>' 미정의 …_` / nearest-phase rationale) is **not** currently enforced by `validators/validate-schedule.py`. The skill is responsible for emitting it; closing the validator gap requires a validator change and is tracked as a follow-up.
448
+
418
449
  ## Self-Validation Before Reporting Completion
419
450
 
420
451
  After writing the file and before printing the completion message in Step 7, you MUST:
@@ -596,13 +627,13 @@ Markdown bullet list of next concrete engineering actions, one item per line: `-
596
627
 
597
628
  | Case | Handling |
598
629
  |------|----------|
599
- | `workStatus` field absent or empty | Treat as `in-progress` include in schedule |
630
+ | `workStatus` field absent or empty | Resolve via the okstra-status inference table; include unless the resolved value is `done` |
600
631
  | Filtered task count is 0 | Emit "모든 task가 done" message; do NOT create a file |
601
632
  | `latestReportPath` missing or unreadable | Tag entry with `[NEEDS-OKSTRA-RUN]`; include manifest metadata only |
602
633
  | Report parsing fails for a specific section | Tag with `[PARSE-ERROR: <section>]`; continue with the rest |
603
634
  | `task-group` argument matches no tasks | Output "해당 task-group을 찾을 수 없습니다." and stop |
604
635
  | Catalog and manifest disagree on `workStatus` | Manifest wins (catalog may be stale) |
605
- | Multiple `task-group` casings | Match case-insensitively; use the manifest's `taskGroupPathSegment` for path output |
636
+ | Multiple `task-group` casings / punctuation variants | Normalise both sides (lowercase + strip non-`[a-z0-9]`) then compare against `taskGroupPathSegment` only. Use the manifest's `taskGroupPathSegment` verbatim for path output. |
606
637
 
607
638
  ## Output Rules
608
639
 
@@ -40,7 +40,8 @@ npx -y okstra@latest install
40
40
  This single command populates everything the user needs:
41
41
 
42
42
  - `~/.okstra/{lib/python, bin, version}` — python + bash runtime
43
- - `~/.claude/skills/<name>/SKILL.md` — all 11 okstra skills (incl. this one)
43
+ - `~/.claude/skills/<name>/SKILL.md` — all okstra skills (canonical count
44
+ in `~/.okstra/installed-skills.json`)
44
45
  - `~/.okstra/installed-skills.json` — manifest for safe uninstall
45
46
 
46
47
  The skill should run this even if `~/.okstra/version` already exists —
@@ -72,10 +73,11 @@ them from the env vars.
72
73
  ## Step 3: Resolve PROJECT_ROOT
73
74
 
74
75
  ```bash
75
- okstra check-project --cwd "$(pwd)"
76
+ PROJECT_ROOT=$(okstra check-project --cwd "$(pwd)" | jq -r '.projectRoot')
76
77
  ```
77
78
 
78
- The JSON includes `projectRoot` on success. On failure (`ok: false`,
79
+ The JSON includes `projectRoot` on success. Bind it into the shell so
80
+ Step 4 onwards can expand `$PROJECT_ROOT`. On failure (`ok: false`,
79
81
  `stage: "resolve"`) ask the user (`AskUserQuestion`, free text) for an
80
82
  absolute project root and rerun with `--cwd <their answer>`.
81
83
 
@@ -96,7 +98,11 @@ so overwriting requires manually deleting the file first.
96
98
  If the file does NOT exist, ask via `AskUserQuestion`:
97
99
 
98
100
  - **Question**: `"Project id for okstra (e.g. INV-1234, my-app, okstra)"`
99
- - **Validate**: slugified must contain at least one alphanumeric character.
101
+ - **Validate**: the answer must be non-empty AND contain at least one
102
+ alphanumeric character. Re-ask on empty input — `okstra setup --yes`
103
+ with no `--project-id` exits 1 with
104
+ `error: --project-id is required (no existing project.json, not a TTY)`,
105
+ so passing the user's empty answer through is a silent failure path.
100
106
 
101
107
  Then create the file:
102
108
 
@@ -104,6 +110,12 @@ Then create the file:
104
110
  okstra setup --yes --project-root "$PROJECT_ROOT" --project-id "$PROJECT_ID"
105
111
  ```
106
112
 
113
+ > After this, **Steps 4.5–4.8 are all optional**. The built-in defaults
114
+ > work for most projects — skip straight to Step 5 (`doctor`) unless the
115
+ > user explicitly asks to customise worktree sync, declare QA commands,
116
+ > or register a PR-body template. Step 4.7 (settings.local.json symlink)
117
+ > is automatic; it is documented for diagnostic purposes only.
118
+
107
119
  ## Step 4.5 (optional): customise worktree sync dirs
108
120
 
109
121
  Each okstra run provisions a task-scoped git worktree under
@@ -114,14 +126,14 @@ every task sees the shared state. The built-in default is
114
126
 
115
127
  To override per-project, add a `worktreeSyncDirs` array to
116
128
  `project.json`. Empty array disables the feature; the field is
117
- preserved across the runtime's auto-upserts (only `projectId`,
118
- `projectRoot`, `createdAt`, `updatedAt` are runtime-owned).
129
+ preserved across the runtime's auto-upserts (see Step 4.6 for the
130
+ canonical list of runtime-owned fields).
119
131
 
120
132
  ```json
121
133
  {
122
134
  "projectId": "...",
123
135
  "projectRoot": "...",
124
- "worktreeSyncDirs": [".project-docs", ".scratch", ".claude", "my-custom-dir"]
136
+ "worktreeSyncDirs": [".project-docs", ".scratch", "graphify-out", ".claude", "my-custom-dir"]
125
137
  }
126
138
  ```
127
139
 
@@ -130,7 +142,7 @@ field → built-in default. Only edit when defaults don't cover the
130
142
  project's working files (e.g. additional cache or local-config dirs
131
143
  that must follow the executor into the worktree).
132
144
 
133
- ## Step 4.7 (optional but recommended): declare project QA commands
145
+ ## Step 4.6 (optional but recommended): declare project QA commands
134
146
 
135
147
  `implementation`-phase verifiers run an independent QA gate over the
136
148
  executor's diff and need a project-wide baseline of check-only
@@ -178,7 +190,7 @@ The field is preserved across the runtime's auto-upserts of
178
190
  `updatedAt` are runtime-owned, so manual edits to `qaCommands`
179
191
  survive every subsequent `okstra setup` / `okstra run` invocation.
180
192
 
181
- ## Step 4.6 (automatic): project-local Claude settings symlink
193
+ ## Step 4.7 (automatic): project-local Claude settings symlink
182
194
 
183
195
  `okstra setup` (and `okstra run` on its first invocation per project)
184
196
  provisions `<PROJECT_ROOT>/.claude/settings.local.json` as a symlink to
@@ -209,7 +221,7 @@ To opt out (advanced): replace the symlink with a regular file. okstra
209
221
  will detect that it is no longer a symlink on its next setup call and
210
222
  back it up as `.bak.<timestamp>` rather than overwriting silently.
211
223
 
212
- ## Step 4.8 (optional): register a project PR body template
224
+ ## Step 4.8 (optional, opt-in): register a project PR body template
213
225
 
214
226
  `release-handoff` fills the PR body from a template. By default it uses the
215
227
  bundled skill template at
@@ -217,6 +229,11 @@ bundled skill template at
217
229
  want their own — e.g. the repo's `.github/PULL_REQUEST_TEMPLATE.md` — to
218
230
  keep PRs consistent with what the team already merges manually.
219
231
 
232
+ **Pre-registration during setup is opt-in.** The same prompt is also
233
+ offered on the first `release-handoff` run in this project, so it is
234
+ safe to defer this step (`나중에`) — most users should skip unless they
235
+ explicitly want to lock the template in now.
236
+
220
237
  Ask the user with `AskUserQuestion` (fixed options, NOT free text — file
221
238
  path entry happens in the follow-up plain text prompt per the
222
239
  okstra-run prompt convention):
@@ -269,6 +286,8 @@ Inform the user with a short summary:
269
286
  |---|---|---|
270
287
  | `command not found: npx` | Node missing | Install node 18+. |
271
288
  | `okstra ensure-installed` keeps reinstalling | `~/.okstra/version` write fails (permissions) | Check `~/.okstra` ownership and writability. |
272
- | `ResolverError: project_id required` | empty answer to Step 4 prompt | Re-ask Step 4. |
273
- | `projectId 불일치` | `project.json` already exists with a different id | Decide which id is canonical; manually edit the file or pick the existing id. |
289
+ | `error: --project-id is required (no existing project.json, not a TTY)` | `okstra setup --yes` invoked without `--project-id`, or with empty answer to Step 4 prompt | Re-ask Step 4 and pass a non-empty id via `--project-id`. |
290
+ | `projectId mismatch` / `projectId 불일치` | `project.json` already exists with a different id | Decide which id is canonical; manually delete `<PROJECT_ROOT>/.project-docs/okstra/project.json` to re-register, or re-run with the existing id. |
291
+ | `EACCES` writing under `.project-docs/okstra/` | directory owned by another user (e.g. created by a previous root-shell run) | `chown -R "$USER" <PROJECT_ROOT>/.project-docs` or delete and let setup recreate. |
292
+ | `warning: failed to provision .claude/settings.local.json symlink` | a non-symlink `.claude/settings.local.json` already exists and the backup-and-replace step failed | Inspect `<PROJECT_ROOT>/.claude/settings.local.json{,.bak.*}`; manually merge project-specific rules, then re-run setup. |
274
293
  | `npx okstra@latest install` succeeds but `doctor` shows FAIL | runtime/{python,bin,skills} sync not yet performed (pre-release package) | Use dev install: clone the repo and run `node bin/okstra install --link <repo>`. |