okstra 0.111.0 → 0.112.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 (55) hide show
  1. package/README.kr.md +2 -2
  2. package/README.md +2 -2
  3. package/bin/okstra +7 -1
  4. package/docs/for-ai/README.md +2 -2
  5. package/docs/for-ai/skills/okstra-brief.md +2 -3
  6. package/docs/for-ai/skills/okstra-container-build.md +2 -3
  7. package/docs/for-ai/skills/okstra-inspect.md +5 -12
  8. package/docs/for-ai/skills/okstra-rollup.md +3 -4
  9. package/docs/for-ai/skills/okstra-run.md +3 -5
  10. package/docs/for-ai/skills/okstra-schedule.md +2 -7
  11. package/docs/for-ai/skills/okstra-setup.md +1 -1
  12. package/docs/kr/architecture/storage-model.md +1 -2
  13. package/docs/kr/architecture.md +2 -2
  14. package/docs/kr/cli.md +4 -2
  15. package/docs/project-structure-overview.md +5 -3
  16. package/package.json +1 -1
  17. package/runtime/BUILD.json +2 -2
  18. package/runtime/prompts/coding-preflight/overview.md +3 -1
  19. package/runtime/prompts/launch.template.md +1 -5
  20. package/runtime/prompts/lead/context-loader.md +2 -4
  21. package/runtime/prompts/lead/convergence.md +11 -240
  22. package/runtime/prompts/lead/okstra-lead-contract.md +70 -34
  23. package/runtime/prompts/lead/plan-body-verification.md +240 -0
  24. package/runtime/prompts/lead/report-writer.md +7 -7
  25. package/runtime/prompts/lead/team-contract.md +15 -17
  26. package/runtime/prompts/profiles/_common-contract.md +1 -37
  27. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  28. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  29. package/runtime/prompts/profiles/final-verification.md +1 -1
  30. package/runtime/prompts/profiles/implementation-planning.md +2 -2
  31. package/runtime/prompts/profiles/implementation.md +1 -1
  32. package/runtime/python/okstra_ctl/path_hints.py +1 -0
  33. package/runtime/python/okstra_ctl/paths.py +3 -0
  34. package/runtime/python/okstra_ctl/render.py +8 -0
  35. package/runtime/python/okstra_ctl/set_work_status.py +147 -0
  36. package/runtime/python/okstra_ctl/team_reconcile.py +1 -1
  37. package/runtime/skills/okstra-brief/SKILL.md +32 -176
  38. package/runtime/skills/okstra-brief/references/reporter-confirmations.md +71 -0
  39. package/runtime/skills/okstra-brief/references/tracker-recursion.md +90 -0
  40. package/runtime/skills/okstra-container-build/SKILL.md +7 -20
  41. package/runtime/skills/okstra-graphify/SKILL.md +8 -8
  42. package/runtime/skills/okstra-inspect/SKILL.md +27 -43
  43. package/runtime/skills/okstra-rollup/SKILL.md +6 -6
  44. package/runtime/skills/okstra-run/SKILL.md +7 -16
  45. package/runtime/skills/okstra-schedule/SKILL.md +64 -419
  46. package/runtime/skills/okstra-setup/SKILL.md +25 -223
  47. package/runtime/skills/okstra-setup/references/project-config.md +188 -0
  48. package/runtime/templates/reports/schedule.template.md +2 -2
  49. package/runtime/templates/worker-prompt-preamble.md +1 -1
  50. package/runtime/validators/validate-run.py +7 -7
  51. package/src/cli-registry.mjs +14 -0
  52. package/src/commands/inspect/set-work-status.mjs +31 -0
  53. package/src/commands/lifecycle/check-project.mjs +68 -56
  54. package/src/commands/lifecycle/install.mjs +1 -0
  55. package/src/commands/lifecycle/preflight.mjs +82 -0
@@ -53,13 +53,9 @@ Show the final summary line back to the user (`version stamp: x.y.z`).
53
53
  If install fails, surface the stderr verbatim. Do NOT try to "fix" it by
54
54
  running the legacy `okstra-install.sh` — that path is dev-only.
55
55
 
56
- ## Step 2: Load runtime paths (no-op)
56
+ **Bash invocation rule (permission-friendly)**: after Step 1, every Bash command in this skill MUST begin with the literal token `okstra` and pass literal argument values. Do not introduce shell variables (`$PROJECT_ROOT`, `$PROJECT_ID`, ...), `$(...)` command substitution, leading `VAR=...` assignments, or wrap commands in `if`/`eval`/`||`/`&&` — any of those make the leading token non-literal, defeat the `Bash(okstra:*)` permission match, and force a confirmation prompt on every call. When a prior tool call emitted a path or value, paste the literal string into the next command. Every `okstra <subcmd>` call self-bootstraps its Python path — never run `okstra paths --shell` / `export PYTHONPATH=...`.
57
57
 
58
- After `okstra install`, every `okstra <subcmd>` call self-bootstraps its Python path, so this skill does NOT run `okstra paths --shell` / `eval ...` / `export PYTHONPATH=...`. The previously-set `$OKSTRA_WORKSPACE`, `$OKSTRA_AGENTS_DIR`, `$OKSTRA_PYTHONPATH`, `$OKSTRA_BIN`, `$OKSTRA_HOME` env vars are NOT set by this skill. If a later step needs one of those paths, call `okstra paths --json` as a **separate Bash tool call** (literal-token-first) and read the value from JSON output.
59
-
60
- **Bash invocation rule (permission-friendly)**: from this step on, every Bash command in this skill MUST begin with the literal token `okstra` and pass literal argument values. Do not introduce shell variables (`$PROJECT_ROOT`, `$PROJECT_ID`, ...), `$(...)` command substitution, leading `VAR=...` assignments, or wrap commands in `if`/`eval`/`||`/`&&` — any of those make the leading token non-literal, defeat the `Bash(okstra:*)` permission match, and force a confirmation prompt on every 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.
61
-
62
- ## Step 3: Resolve PROJECT_ROOT
58
+ ## Step 2: Resolve PROJECT_ROOT
63
59
 
64
60
  ```bash
65
61
  okstra check-project --json
@@ -74,12 +70,12 @@ Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, pro
74
70
  okstra check-project --cwd /abs/path/from/user --json
75
71
  ```
76
72
 
77
- - `ok: false`, `stage: "project_json_missing"` → proceed to Step 4 (this is the normal create path).
73
+ - `ok: false`, `stage: "project_json_missing"` → proceed to Step 3 (this is the normal create path).
78
74
  - `ok: false`, any other `stage` (`python`, `parse`, `project_json_invalid`) → show the JSON `reason` field to the user verbatim and follow the recovery it names (typically `okstra doctor` to diagnose, then `okstra ensure-installed` or re-running the Step 1 install). `reason` is the SSOT — do not hand-enumerate stage causes.
79
75
 
80
- ## Step 4: Inspect or create `project.json`
76
+ ## Step 3: Inspect or create `project.json`
81
77
 
82
- Use the `Read` tool on the literal absolute path `<projectRoot>/.okstra/project.json` (substitute the literal `projectRoot` value parsed in Step 3). If `Read` errors with "file does not exist", treat that as the "create" branch below; otherwise the file exists and you can inspect its contents inline.
78
+ Use the `Read` tool on the literal absolute path `<projectRoot>/.okstra/project.json` (substitute the literal `projectRoot` value parsed in Step 2). If `Read` errors with "file does not exist", treat that as the "create" branch below; otherwise the file exists and you can inspect its contents inline.
83
79
 
84
80
  If the file exists, surface its `projectId`/`projectRoot` and ask whether to
85
81
  keep or overwrite. Default is to keep — okstra refuses to change `projectId`
@@ -95,226 +91,32 @@ If the file does NOT exist, ask via `AskUserQuestion`:
95
91
  `error: --project-id is required (no existing project.json, not a TTY)`,
96
92
  so passing the user's empty answer through is a silent failure path.
97
93
 
98
- Then create the file — paste the literal `projectRoot` from Step 3 and the literal `projectId` from the user's answer (no shell variables):
94
+ Then create the file — paste the literal `projectRoot` from Step 2 and the literal `projectId` from the user's answer (no shell variables):
99
95
 
100
96
  ```bash
101
97
  okstra setup --yes --project-root /abs/path/to/projectRoot --project-id my-project-id
102
98
  ```
103
99
 
104
- > After this, **Steps 4.5–4.8 are all optional**. The built-in defaults
105
- > work for most projects — skip straight to Step 5 (`doctor`) unless the
106
- > user explicitly asks to customise worktree sync, declare QA commands,
107
- > or register a PR-body template. Step 4.7 (settings.local.json symlink)
108
- > is automatic; it is documented for diagnostic purposes only.
109
-
110
- ## Step 4.5 (optional): customise worktree sync dirs
111
-
112
- Each okstra run provisions a task-scoped git worktree under
113
- `~/.okstra/worktrees/.../`. A small set of project-root-relative
114
- directories is symlinked from the main checkout into that worktree so
115
- every task sees the shared state. The built-in default is
116
- `.project-docs`, `.scratch`, `graphify-out`, `.claude`.
117
- Syncing a directory does not make it okstra memory; okstra-owned context
118
- and writes still stay under `<PROJECT_ROOT>/.okstra/**`
119
- unless the task brief explicitly authorizes a non-okstra path.
120
-
121
- To override per-project, add a `worktreeSyncDirs` array to
122
- `project.json`. Empty array disables the feature; the field is
123
- preserved across the runtime's auto-upserts (see Step 4.6 for the
124
- canonical list of runtime-owned fields).
125
-
126
- ```json
127
- {
128
- "projectId": "...",
129
- "projectRoot": "...",
130
- "worktreeSyncDirs": [".project-docs", ".scratch", "graphify-out", ".claude", "my-custom-dir"]
131
- }
132
- ```
133
-
134
- Resolution precedence: `OKSTRA_WORKTREE_SYNC_DIRS` env var → this
135
- field → built-in default. Only edit when defaults don't cover the
136
- project's working files (e.g. additional cache or local-config dirs
137
- that must follow the executor into the worktree).
138
-
139
- ## Step 4.6 (optional but recommended): declare project QA commands
140
-
141
- `implementation`-phase verifiers run an independent QA gate over the
142
- executor's diff and need a project-wide baseline of check-only
143
- lint / format / typecheck / test commands. okstra does NOT auto-detect
144
- tooling from manifest files — declare the commands explicitly in
145
- `project.json` under `qaCommands`. Skipping this declaration is
146
- allowed but the verifier will then only run the plan's per-task
147
- `validation` set, with `qa-command not configured: <category>`
148
- recorded per missing category in the final report.
149
-
150
- Each category is an array of `{ "label", "cmd", "language"? }`
151
- objects. `language` is optional; when present the verifier MAY skip
152
- commands whose language is not represented in this run's diff.
153
-
154
- ```json
155
- {
156
- "projectId": "...",
157
- "projectRoot": "...",
158
- "qaCommands": {
159
- "lint": [{ "label": "cargo clippy", "cmd": "cargo clippy --all-targets -- -D warnings", "language": "rust" }],
160
- "format": [{ "label": "cargo fmt", "cmd": "cargo fmt --check", "language": "rust" }],
161
- "typecheck": [{ "label": "tsc", "cmd": "pnpm exec tsc --noEmit", "language": "ts" }],
162
- "test": [{ "label": "cargo test", "cmd": "cargo test --workspace --locked", "language": "rust" }]
163
- }
164
- }
165
- ```
166
-
167
- **`cmd` deny-list (mutation guard):** the verifier rejects any `cmd`
168
- containing tokens that imply mutation — declare commands in their
169
- check-only form only. Representative denied tokens: `--fix` / `--write`
170
- (formatters), `--updateSnapshot`, `npm install` (use `npm ci`),
171
- `pip install -U`, `cargo add`. The authoritative deny list is
172
- `scripts/okstra_ctl/qa_commands.py::find_denied_tokens`
173
- (`_DENIED_LITERAL_TOKENS` / `_DENIED_SUBSTRINGS` plus the dynamic
174
- `npm install` and `INSTA_UPDATE=` checks) — do not re-enumerate it here.
175
-
176
- Encountering a denied token aborts the verifier with status
177
- `contract-violated`; re-declare the command in check-only form to
178
- recover (e.g. swap `prettier --write` → `prettier --check`).
179
-
180
- The field is preserved across the runtime's auto-upserts of
181
- `project.json` — only `projectId`, `projectRoot`, `createdAt`,
182
- `updatedAt` are runtime-owned, so manual edits to `qaCommands`
183
- survive every subsequent `okstra setup` / `okstra run` invocation.
184
-
185
- ### Step 4.6.1 (optional): `qaEnv` — Tier 3 conformance environment
186
-
187
- `implementation` / `final-verification` verifiers run **stage
188
- conformance scripts** (Tier 3) that may need to reach a database or an
189
- HTTP endpoint to prove the diff satisfies upstream requirements. Declare
190
- the environment those scripts are allowed to touch under `qaEnv`. Every
191
- field is optional; declare only what your conformance scripts use.
192
-
193
- ```json
194
- "qaEnv": {
195
- "replicaDbDsn": "<replica/test DB DSN — never shared/staging/prod>",
196
- "appBaseUrl": "http://localhost:3000",
197
- "envFile": ".okstra/qa.env",
198
- "surfacePatterns": { "db": ["*.sql", "*repository*"], "http": ["*controller*"] }
199
- }
200
- ```
201
-
202
- - `replicaDbDsn` — DSN the conformance script connects to. MUST be a
203
- replica / disposable test DB, **never** a shared, staging, or
204
- production database (conformance scripts may write).
205
- - `appBaseUrl` — base URL for endpoint-level conformance checks
206
- (local app only).
207
- - `envFile` — path (under `.okstra/`) to an env file the verifier
208
- sources before running conformance scripts.
209
- - `surfacePatterns` — per-project **override** of the diff-surface
210
- cross-check map (`capability → glob list`). The validator maps each
211
- changed file to a capability surface (`db` / `http` / `io`) and fails
212
- the run when the diff touches a surface no stage `requires`. The
213
- built-in patterns (e.g. `*router*` for `http`, `*storage*` for `io`)
214
- are broad and match many front-end files, so front-end-heavy repos
215
- should override with narrower globs to avoid false BLOCKING verdicts
216
- (Phase 4b review note). An over-broad pattern over-blocks; an
217
- over-narrow one lets an undeclared surface through — tune to the
218
- repo's real db/http/io file naming.
219
-
220
- ## Step 4.7 (automatic): project-local Claude settings symlink
221
-
222
- `okstra setup` (and `okstra run` on its first invocation per project)
223
- provisions `<PROJECT_ROOT>/.claude/settings.local.json` as a symlink to
224
- `~/.okstra/templates/settings.local.json`. The template is installed
225
- by `okstra install` 0.14.0+ and contains the Bash permission rules
226
- required for the codex/antigravity worker wrappers:
227
-
228
- - `Bash($HOME/.okstra/bin/okstra-codex-exec.sh:*)`
229
- - `Bash($HOME/.okstra/bin/okstra-antigravity-exec.sh:*)`
230
-
231
- Claude Code automatically loads `.claude/settings.local.json` whenever
232
- it operates inside that project, so okstra workers dispatched from
233
- **any** Claude Code session (host or okstra.sh-spawned) are allowed to
234
- run their wrapper scripts without further configuration.
235
-
236
- This replaces the previous per-run `--settings` injection model
237
- (`<run-dir>/okstra-runtime-settings.json`) and the earlier guidance to
238
- modify the user's global `~/.claude/settings.json`.
239
-
240
- If a non-symlink `.claude/settings.local.json` already exists, the
241
- setup step backs it up to `.claude/settings.local.json.bak.<timestamp>`
242
- before installing the symlink — surface that to the user so they can
243
- merge any project-specific rules back into a downstream file (the
244
- symlinked template is okstra-owned and gets refreshed when okstra
245
- updates).
246
-
247
- To opt out (advanced): replace the symlink with a regular file. okstra
248
- will detect that it is no longer a symlink on its next setup call and
249
- back it up as `.bak.<timestamp>` rather than overwriting silently.
250
-
251
- ## Step 4.8 (optional, opt-in): register a project PR body template
252
-
253
- `release-handoff` fills the PR body from a template. By default it uses the
254
- installed template at
255
- `~/.okstra/templates/prd/pr-body.template.md`. Most projects
256
- want their own — e.g. the repo's `.github/PULL_REQUEST_TEMPLATE.md` — to
257
- keep PRs consistent with what the team already merges manually.
258
-
259
- **Pre-registration during setup is opt-in.** The same prompt is also
260
- offered on the first `release-handoff` run in this project, so it is
261
- safe to defer this step (`나중에`) — most users should skip unless they
262
- explicitly want to lock the template in now.
263
-
264
- Ask the user with `AskUserQuestion` (fixed options, NOT free text — file
265
- path entry happens in the follow-up plain text prompt per the
266
- okstra-run prompt convention):
267
-
268
- - **Question**: `"이 프로젝트에서 release-handoff 가 사용할 PR 본문 템플릿을 등록할까요?"`
269
- - **Options**:
270
- 1. `이번 프로젝트만 (project scope)` — write to `<PROJECT_ROOT>/.okstra/project.json` `prTemplatePath`.
271
- 2. `전역 (global scope)` — write to `~/.okstra/config.json` `prTemplatePath`.
272
- 3. `나중에` — skip.
273
-
274
- If the user picks scope 1 or 2, follow up with a **plain text prompt**:
275
- `"PR 본문 템플릿 파일 경로를 알려주세요. project 스코프는 project-root 기준 상대경로 또는 절대경로, global 스코프는 절대경로 또는 ~/ 시작 경로만 허용됩니다."` Consume the next user message, then run:
276
-
277
- ```bash
278
- okstra config set pr-template-path "<typed-path>" --scope <project|global>
279
- ```
280
-
281
- The command validates the value (global rejects relative paths) and
282
- writes atomically. Surface its stdout JSON so the user sees which file
283
- was updated.
284
-
285
- If the user chose `나중에`, tell them they can register later with one of:
286
-
287
- - `okstra config set pr-template-path <path> --scope project|global` from a
288
- terminal, or
289
- - the per-run 1회성 override prompt during the next release-handoff run
290
- (the run can also persist that override to project/global scope on the
291
- spot — see the okstra-run skill).
292
-
293
- ## Step 4.9 (optional): choose final report language
294
-
295
- 기본은 영어. 이 step 을 skip 하면 `reportLanguage` 필드를 두지 않고
296
- runtime 이 `auto` 로 처리한다 — 즉 task brief 의 주 서술 언어를 따라
297
- 간다 (한국어 brief → 한국어 report, 영어 brief → 영어 report).
298
-
299
- 사용자가 명시적으로 선택하면 `okstra config set` 으로 `project.json`
300
- 에 기록한다.
301
-
302
- AskUserQuestion (fixed options):
303
- - Question: `"Final report 를 어느 언어로 작성할까요?"`
304
- - Options:
305
- 1. `English (recommended)` → reportLanguage: "en"
306
- 2. `한국어` → reportLanguage: "ko"
307
- 3. `Auto (task brief 언어로 추론, 불분명하면 영어)` → reportLanguage: "auto"
308
- 4. `나중에` → skip (필드 미설정 → runtime 이 auto 로 처리)
309
-
310
- If the user picks 1/2/3, run:
100
+ ## Step 3.5 (optional): project customisation
311
101
 
312
- `okstra config set report-language <en|ko|auto> --scope project`
102
+ The built-in defaults work for most projects — skip straight to Step 4
103
+ (`doctor`) unless the user explicitly asks for one of these. When they do,
104
+ **read `references/project-config.md` next to this SKILL.md** and follow the
105
+ matching section:
313
106
 
314
- 전역 기본값을 두고 싶다면 README "global config" 안내를 따라
315
- `--scope global` 로 수동 설정한다. 이 step 에서는 project 스코프만 제공한다.
107
+ - **A. worktree sync dirs** (`worktreeSyncDirs`) extra directories symlinked
108
+ into task worktrees.
109
+ - **B. project QA commands** (`qaCommands`, recommended) + **B.1 `qaEnv`** —
110
+ check-only lint/format/typecheck/test baseline and the Tier 3 conformance
111
+ environment the `implementation` verifiers use.
112
+ - **C. `.claude/settings.local.json` symlink** — automatic; read only to
113
+ diagnose a symlink/backup warning.
114
+ - **D. PR body template** (release-handoff) — opt-in pre-registration; also
115
+ offered on the first release-handoff run.
116
+ - **E. final report language** (`reportLanguage`) — default is auto (follows
117
+ the brief's language).
316
118
 
317
- ## Step 5: Verify
119
+ ## Step 4: Verify
318
120
 
319
121
  ```bash
320
122
  okstra doctor --runtime claude-code
@@ -323,7 +125,7 @@ okstra doctor --runtime claude-code
323
125
  If all checks return `OK`, the setup is complete. If any check fails, surface
324
126
  the output and let the user decide whether to re-run install or skip.
325
127
 
326
- ## Step 6: Hand-off
128
+ ## Step 5: Hand-off
327
129
 
328
130
  Inform the user with a short summary:
329
131
 
@@ -337,7 +139,7 @@ Inform the user with a short summary:
337
139
  |---|---|---|
338
140
  | `command not found: npx` | Node missing | Install node 18+. |
339
141
  | `okstra ensure-installed` keeps reinstalling | `~/.okstra/version` write fails (permissions) | Check `~/.okstra` ownership and writability. |
340
- | `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`. |
142
+ | `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 3 prompt | Re-ask Step 3 and pass a non-empty id via `--project-id`. |
341
143
  | `projectId mismatch` / `projectId 불일치` | `project.json` already exists with a different id | Decide which id is canonical; manually delete `<PROJECT_ROOT>/.okstra/project.json` to re-register, or re-run with the existing id. |
342
144
  | `EACCES` writing under `.okstra/` | directory owned by another user (e.g. created by a previous root-shell run) | `chown -R "$USER" <PROJECT_ROOT>/.okstra` or delete and let setup recreate. |
343
145
  | `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. |
@@ -0,0 +1,188 @@
1
+ # Optional project configuration (okstra-setup)
2
+
3
+ Read this only when the user asks to customise one of the areas below after
4
+ `project.json` exists. The built-in defaults work for most projects — none of
5
+ these sections is required for a working setup.
6
+
7
+ The Bash invocation rule from SKILL.md applies to every command here.
8
+
9
+ ## A. Worktree sync dirs (`worktreeSyncDirs`)
10
+
11
+ Each okstra run provisions a task-scoped git worktree under
12
+ `~/.okstra/worktrees/.../`. A small set of project-root-relative directories is
13
+ symlinked from the main checkout into that worktree so every task sees the
14
+ shared state. The built-in default is `.project-docs`, `.scratch`,
15
+ `graphify-out`, `.claude`. Syncing a directory does not make it okstra memory;
16
+ okstra-owned context and writes still stay under `<PROJECT_ROOT>/.okstra/**`
17
+ unless the task brief explicitly authorizes a non-okstra path.
18
+
19
+ To override per-project, add a `worktreeSyncDirs` array to `project.json`.
20
+ Empty array disables the feature; the field is preserved across the runtime's
21
+ auto-upserts (only `projectId`, `projectRoot`, `createdAt`, `updatedAt` are
22
+ runtime-owned).
23
+
24
+ ```json
25
+ {
26
+ "projectId": "...",
27
+ "projectRoot": "...",
28
+ "worktreeSyncDirs": [".project-docs", ".scratch", "graphify-out", ".claude", "my-custom-dir"]
29
+ }
30
+ ```
31
+
32
+ Resolution precedence: `OKSTRA_WORKTREE_SYNC_DIRS` env var → this field →
33
+ built-in default. Only edit when defaults don't cover the project's working
34
+ files (e.g. additional cache or local-config dirs that must follow the
35
+ executor into the worktree).
36
+
37
+ ## B. Project QA commands (`qaCommands`) — recommended
38
+
39
+ `implementation`-phase verifiers run an independent QA gate over the
40
+ executor's diff and need a project-wide baseline of check-only lint / format /
41
+ typecheck / test commands. okstra does NOT auto-detect tooling — declare the
42
+ commands explicitly in `project.json` under `qaCommands`. Skipping this is
43
+ allowed but the verifier will then only run the plan's per-task `validation`
44
+ set, with `qa-command not configured: <category>` recorded per missing
45
+ category in the final report.
46
+
47
+ Each category is an array of `{ "label", "cmd", "language"? }` objects.
48
+ `language` is optional; when present the verifier MAY skip commands whose
49
+ language is not represented in this run's diff.
50
+
51
+ ```json
52
+ {
53
+ "projectId": "...",
54
+ "projectRoot": "...",
55
+ "qaCommands": {
56
+ "lint": [{ "label": "cargo clippy", "cmd": "cargo clippy --all-targets -- -D warnings", "language": "rust" }],
57
+ "format": [{ "label": "cargo fmt", "cmd": "cargo fmt --check", "language": "rust" }],
58
+ "typecheck": [{ "label": "tsc", "cmd": "pnpm exec tsc --noEmit", "language": "ts" }],
59
+ "test": [{ "label": "cargo test", "cmd": "cargo test --workspace --locked", "language": "rust" }]
60
+ }
61
+ }
62
+ ```
63
+
64
+ **`cmd` deny-list (mutation guard):** the verifier rejects any `cmd`
65
+ containing tokens that imply mutation — declare commands in their check-only
66
+ form only. Representative denied tokens: `--fix` / `--write` (formatters),
67
+ `--updateSnapshot`, `npm install` (use `npm ci`), `pip install -U`,
68
+ `cargo add`. The authoritative deny list is
69
+ `scripts/okstra_ctl/qa_commands.py::find_denied_tokens`
70
+ (`_DENIED_LITERAL_TOKENS` / `_DENIED_SUBSTRINGS` plus the dynamic
71
+ `npm install` and `INSTA_UPDATE=` checks) — do not re-enumerate it here.
72
+
73
+ Encountering a denied token aborts the verifier with status
74
+ `contract-violated`; re-declare the command in check-only form to recover
75
+ (e.g. swap `prettier --write` → `prettier --check`).
76
+
77
+ The field is preserved across the runtime's auto-upserts of `project.json`, so
78
+ manual edits to `qaCommands` survive every subsequent `okstra setup` /
79
+ `okstra run` invocation.
80
+
81
+ ### B.1 `qaEnv` — Tier 3 conformance environment
82
+
83
+ `implementation` / `final-verification` verifiers run **stage conformance
84
+ scripts** (Tier 3) that may need to reach a database or an HTTP endpoint to
85
+ prove the diff satisfies upstream requirements. Declare the environment those
86
+ scripts are allowed to touch under `qaEnv`. Every field is optional; declare
87
+ only what your conformance scripts use.
88
+
89
+ ```json
90
+ "qaEnv": {
91
+ "replicaDbDsn": "<replica/test DB DSN — never shared/staging/prod>",
92
+ "appBaseUrl": "http://localhost:3000",
93
+ "envFile": ".okstra/qa.env",
94
+ "surfacePatterns": { "db": ["*.sql", "*repository*"], "http": ["*controller*"] }
95
+ }
96
+ ```
97
+
98
+ - `replicaDbDsn` — DSN the conformance script connects to. MUST be a replica /
99
+ disposable test DB, **never** a shared, staging, or production database
100
+ (conformance scripts may write).
101
+ - `appBaseUrl` — base URL for endpoint-level conformance checks (local app
102
+ only).
103
+ - `envFile` — path (under `.okstra/`) to an env file the verifier sources
104
+ before running conformance scripts.
105
+ - `surfacePatterns` — per-project **override** of the diff-surface cross-check
106
+ map (`capability → glob list`). The validator maps each changed file to a
107
+ capability surface (`db` / `http` / `io`) and fails the run when the diff
108
+ touches a surface no stage `requires`. The built-in patterns (e.g. `*router*`
109
+ for `http`, `*storage*` for `io`) are broad and match many front-end files,
110
+ so front-end-heavy repos should override with narrower globs to avoid false
111
+ BLOCKING verdicts. An over-broad pattern over-blocks; an over-narrow one
112
+ lets an undeclared surface through — tune to the repo's real db/http/io file
113
+ naming.
114
+
115
+ ## C. Project-local Claude settings symlink (automatic — diagnostics only)
116
+
117
+ `okstra setup` (and `okstra run` on its first invocation per project)
118
+ provisions `<PROJECT_ROOT>/.claude/settings.local.json` as a symlink to
119
+ `~/.okstra/templates/settings.local.json`. The template contains the Bash
120
+ permission rules required for the codex/antigravity worker wrappers:
121
+
122
+ - `Bash($HOME/.okstra/bin/okstra-codex-exec.sh:*)`
123
+ - `Bash($HOME/.okstra/bin/okstra-antigravity-exec.sh:*)`
124
+
125
+ Claude Code automatically loads `.claude/settings.local.json` inside that
126
+ project, so okstra workers dispatched from **any** Claude Code session are
127
+ allowed to run their wrapper scripts without further configuration.
128
+
129
+ If a non-symlink `.claude/settings.local.json` already exists, setup backs it
130
+ up to `.claude/settings.local.json.bak.<timestamp>` before installing the
131
+ symlink — surface that to the user so they can merge project-specific rules
132
+ back (the symlinked template is okstra-owned and refreshed on okstra updates).
133
+ To opt out (advanced): replace the symlink with a regular file; okstra will
134
+ back it up as `.bak.<timestamp>` on its next setup call rather than
135
+ overwriting silently.
136
+
137
+ ## D. Project PR body template (release-handoff)
138
+
139
+ `release-handoff` fills the PR body from a template — by default
140
+ `~/.okstra/templates/prd/pr-body.template.md`. Most projects want their own
141
+ (e.g. `.github/PULL_REQUEST_TEMPLATE.md`). Pre-registration during setup is
142
+ opt-in; the same prompt is offered again on the first `release-handoff` run,
143
+ so deferring (`나중에`) is safe.
144
+
145
+ Ask with `AskUserQuestion` (fixed options — file path entry happens in the
146
+ follow-up plain text prompt):
147
+
148
+ - **Question**: `"이 프로젝트에서 release-handoff 가 사용할 PR 본문 템플릿을 등록할까요?"`
149
+ - **Options**:
150
+ 1. `이번 프로젝트만 (project scope)` — writes `prTemplatePath` to `<PROJECT_ROOT>/.okstra/project.json`.
151
+ 2. `전역 (global scope)` — writes `prTemplatePath` to `~/.okstra/config.json`.
152
+ 3. `나중에` — skip.
153
+
154
+ If scope 1 or 2, follow up with a plain text prompt:
155
+ `"PR 본문 템플릿 파일 경로를 알려주세요. project 스코프는 project-root 기준 상대경로 또는 절대경로, global 스코프는 절대경로 또는 ~/ 시작 경로만 허용됩니다."`
156
+ Consume the next user message, then run:
157
+
158
+ ```bash
159
+ okstra config set pr-template-path "<typed-path>" --scope <project|global>
160
+ ```
161
+
162
+ The command validates the value (global rejects relative paths) and writes
163
+ atomically. Surface its stdout JSON. If the user chose `나중에`, tell them
164
+ they can register later via the same `okstra config set` command or the
165
+ per-run override prompt during the next release-handoff run.
166
+
167
+ ## E. Final report language (`reportLanguage`)
168
+
169
+ 기본은 영어. Skip 하면 `reportLanguage` 필드를 두지 않고 runtime 이 `auto` 로
170
+ 처리한다 — task brief 의 주 서술 언어를 따라간다 (한국어 brief → 한국어
171
+ report, 영어 brief → 영어 report).
172
+
173
+ AskUserQuestion (fixed options):
174
+ - Question: `"Final report 를 어느 언어로 작성할까요?"`
175
+ - Options:
176
+ 1. `English (recommended)` → `en`
177
+ 2. `한국어` → `ko`
178
+ 3. `Auto (task brief 언어로 추론, 불분명하면 영어)` → `auto`
179
+ 4. `나중에` → skip (필드 미설정 → runtime 이 auto 로 처리)
180
+
181
+ If the user picks 1/2/3:
182
+
183
+ ```bash
184
+ okstra config set report-language <en|ko|auto> --scope project
185
+ ```
186
+
187
+ 전역 기본값은 README 의 "global config" 안내대로 `--scope global` 로 수동
188
+ 설정한다 — 이 flow 에서는 project 스코프만 제공한다.
@@ -81,9 +81,9 @@ _의존 정보 없음_
81
81
  <!-- OPTIONAL: include only when per-task day estimates are available.
82
82
  If included, MUST use this exact heading literal `## Gantt Chart`.
83
83
  Render as ASCII inside a fenced ``` block with NO language tag.
84
- Mermaid / PlantUML / Graphviz are forbidden — see SKILL §"ASCII Gantt format".
84
+ Mermaid / PlantUML / Graphviz are forbidden.
85
85
  The axis is RELATIVE DAY-COUNTS (Day 1 / J1, …) — never calendar dates.
86
- If day estimates are missing, omit this whole section and replace it with one blockquote: `> _Gantt Chart 생략: <reason>_` (SKILL §"MUST — emit a skip-reason note"). -->
86
+ If day estimates are missing, omit this whole section and replace it with one blockquote: `> _Gantt Chart 생략: <reason>_`. -->
87
87
 
88
88
  ```
89
89
  Day: 1 5 10 15 20 25 30
@@ -20,7 +20,7 @@ Work like a senior engineer who owns this result, not a commentator on it.
20
20
 
21
21
  You are required to read every primary input file enumerated by the dispatcher (the lead's prompt lists them under `[Required reading]`) from the very first character to the very last character before you produce any analysis output. Skimming, partial reads, jumping to a single section, or relying on prior knowledge of a similar file's structure is not acceptable. Source files listed as fallback/evidence paths are read on demand when you need to verify a citation, resolve ambiguity, or inspect material the packet says it omitted.
22
22
 
23
- ### Audience-scoped enumeration (BLOCKINGperformance optimization)
23
+ ### Audience-scoped enumeration (performance optimization mandatory)
24
24
 
25
25
  Different recipients need different files. Do NOT include `final-report-template.md` in analysis worker prompts: analysis workers produce findings (not the final report), and forcing them to read the template inflates token usage without changing finding quality.
26
26
 
@@ -1715,7 +1715,7 @@ _SINGLE_VOTE_BLOCKING_KINDS = {"a", "d"}
1715
1715
 
1716
1716
  def _classify_plan_item_gate(item: dict) -> str:
1717
1717
  """Recompute one plan item's gate class from its per-worker verdicts,
1718
- per `prompts/lead/convergence.md` "Round protocol". Returns one of
1718
+ per `prompts/lead/plan-body-verification.md` "Round protocol". Returns one of
1719
1719
  ``majority-disagree`` / ``has-dissent`` / ``full-consensus`` /
1720
1720
  ``all-non-result``. Collapses ``partial-consensus`` and
1721
1721
  ``dissent-isolated`` into ``has-dissent`` because they resolve to the
@@ -1797,12 +1797,12 @@ def _validate_plan_body_gate_recompute(data: dict, failures: list[str]) -> None:
1797
1797
  f"only support `{recomputed}` (a majority DISAGREE, a DISAGREE(f) on "
1798
1798
  "a P-Req item, or all-non-result dispatches were recorded). The gate "
1799
1799
  "value must honestly aggregate the worker votes — do not upgrade it "
1800
- "to unblock the run (convergence.md Round protocol)."
1800
+ "to unblock the run (plan-body-verification.md Round protocol)."
1801
1801
  )
1802
1802
 
1803
1803
 
1804
1804
  # Each plan-body deliverable array and the P-* verdict-item prefix it must be
1805
- # extracted into for §5.5.9 cross-verification (convergence.md Plan-item
1805
+ # extracted into for §5.5.9 cross-verification (plan-body-verification.md Plan-item
1806
1806
  # extraction). A weak item dropped from planItems escapes the gate entirely.
1807
1807
  _PLAN_ITEM_SOURCES = (
1808
1808
  ("optionCandidates", "P-Opt", "Option Candidates (§4.5.1)"),
@@ -1848,7 +1848,7 @@ def _validate_plan_item_extraction_completeness(data: dict, failures: list[str])
1848
1848
  f"planBodyVerification.planItems carries only {extracted} "
1849
1849
  f"`{prefix}-*` verdict item(s). Every plan-body item MUST be "
1850
1850
  "extracted for cross-verification — a dropped item escapes the "
1851
- "gate (convergence.md Plan-item extraction)."
1851
+ "gate (plan-body-verification.md Plan-item extraction)."
1852
1852
  )
1853
1853
 
1854
1854
 
@@ -1885,7 +1885,7 @@ def _validate_plan_item_subject_substance(data: dict, failures: list[str]) -> No
1885
1885
  f"placeholder subject `{subject}`. Give a plain-language label of "
1886
1886
  "what the item is (e.g. 'Option A: upload v2 를 신규 모듈로 분리') so "
1887
1887
  "the §5.5.9 reader knows what each verdict is about "
1888
- "(convergence.md Plan-item extraction)."
1888
+ "(plan-body-verification.md Plan-item extraction)."
1889
1889
  )
1890
1890
 
1891
1891
 
@@ -1894,7 +1894,7 @@ def _validate_plan_body_clarification_matching(data: dict, failures: list[str])
1894
1894
  must point (via `clarificationId`) at an existing `blocks: approval`
1895
1895
  clarification row. Closes the hole where a majority-disagree item blocks the
1896
1896
  gate but no §1 Clarification row is raised, so the user never sees why
1897
- approval is withheld (convergence.md self-review step 7).
1897
+ approval is withheld (implementation-planning.md self-review step 7).
1898
1898
  """
1899
1899
  ip = data.get("implementationPlanning")
1900
1900
  if not isinstance(ip, dict):
@@ -1920,7 +1920,7 @@ def _validate_plan_body_clarification_matching(data: dict, failures: list[str])
1920
1920
  f"final-report data.json: plan item `{item_id}` is majority-disagree "
1921
1921
  "but carries no `clarificationId`. A blocking disagreement MUST "
1922
1922
  "surface as a `## 1. Clarification Items` row (blocks=approval) so "
1923
- "the user sees the blocker (convergence.md self-review step 7)."
1923
+ "the user sees the blocker (implementation-planning.md self-review step 7)."
1924
1924
  )
1925
1925
  elif cid not in approval_ids:
1926
1926
  reason = (
@@ -45,6 +45,13 @@ export const COMMAND_REGISTRY = [
45
45
  category: "admin",
46
46
  summary: ["Verify the current project has been registered with setup"],
47
47
  },
48
+ {
49
+ name: "preflight",
50
+ module: "./commands/lifecycle/preflight.mjs",
51
+ export: "run",
52
+ category: "admin",
53
+ summary: ["One-call skill preflight: ensure-installed + check-project (JSON)"],
54
+ },
48
55
  {
49
56
  name: "config",
50
57
  module: "./commands/lifecycle/config.mjs",
@@ -117,6 +124,13 @@ export const COMMAND_REGISTRY = [
117
124
  category: "introspection",
118
125
  summary: ["Resolve a bare task-id to candidate task-keys from the catalog"],
119
126
  },
127
+ {
128
+ name: "set-work-status",
129
+ module: "./commands/inspect/set-work-status.mjs",
130
+ export: "run",
131
+ category: "introspection",
132
+ summary: ["Set a task's user-managed workStatus in task-manifest.json"],
133
+ },
120
134
  {
121
135
  name: "time-report",
122
136
  module: "./commands/inspect/time-report.mjs",
@@ -0,0 +1,31 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra set-work-status — set a task's user-managed workStatus
4
+
5
+ Usage:
6
+ okstra set-work-status <token> <status> [--note <text>] [--task-group <g>]
7
+ [--project-root <dir>] [--cwd <dir>] [--json]
8
+
9
+ <token> is a full task-key (<project-id>:<task-group>:<task-id>) or a bare
10
+ task-id. <status> is one of: todo | in-progress | blocked | done.
11
+
12
+ Updates workStatus + workStatusUpdatedAt in task-manifest.json (workStatusNote
13
+ only when --note is passed) using the same JSON serialization as the manifest
14
+ renderer. Output: JSON { ok, taskKey, previousWorkStatus, workStatus, ... };
15
+ ok:false stages: resolve | catalog | not-found | ambiguous (pick from
16
+ matches[]) | manifest-missing | manifest-invalid.
17
+ `;
18
+
19
+ export async function run(args) {
20
+ if (args.includes("--help") || args.includes("-h")) {
21
+ process.stdout.write(USAGE);
22
+ return 0;
23
+ }
24
+
25
+ const result = await runPythonModule({
26
+ module: "okstra_ctl.set_work_status",
27
+ args,
28
+ stdio: "inherit-stdout",
29
+ });
30
+ return result.code;
31
+ }