okstra 0.111.0 → 0.113.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 (64) 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/agents/workers/antigravity-worker.md +1 -1
  19. package/runtime/agents/workers/claude-worker.md +1 -1
  20. package/runtime/agents/workers/codex-worker.md +1 -1
  21. package/runtime/prompts/coding-preflight/overview.md +3 -1
  22. package/runtime/prompts/launch.template.md +1 -5
  23. package/runtime/prompts/lead/context-loader.md +2 -4
  24. package/runtime/prompts/lead/convergence.md +11 -240
  25. package/runtime/prompts/lead/okstra-lead-contract.md +70 -34
  26. package/runtime/prompts/lead/plan-body-verification.md +240 -0
  27. package/runtime/prompts/lead/report-writer.md +7 -7
  28. package/runtime/prompts/lead/team-contract.md +15 -17
  29. package/runtime/prompts/profiles/_common-contract.md +1 -37
  30. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  31. package/runtime/prompts/profiles/_implementation-self-check.md +1 -0
  32. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  33. package/runtime/prompts/profiles/final-verification.md +6 -5
  34. package/runtime/prompts/profiles/implementation-planning.md +6 -4
  35. package/runtime/prompts/profiles/implementation.md +1 -1
  36. package/runtime/prompts/profiles/improvement-discovery.md +1 -0
  37. package/runtime/prompts/profiles/requirements-discovery.md +1 -0
  38. package/runtime/python/okstra_ctl/path_hints.py +1 -0
  39. package/runtime/python/okstra_ctl/paths.py +3 -0
  40. package/runtime/python/okstra_ctl/render.py +8 -0
  41. package/runtime/python/okstra_ctl/set_work_status.py +147 -0
  42. package/runtime/python/okstra_ctl/team_reconcile.py +1 -1
  43. package/runtime/schemas/final-report-v1.0.schema.json +6 -4
  44. package/runtime/skills/okstra-brief/SKILL.md +32 -176
  45. package/runtime/skills/okstra-brief/references/reporter-confirmations.md +71 -0
  46. package/runtime/skills/okstra-brief/references/tracker-recursion.md +90 -0
  47. package/runtime/skills/okstra-container-build/SKILL.md +7 -20
  48. package/runtime/skills/okstra-graphify/SKILL.md +8 -8
  49. package/runtime/skills/okstra-inspect/SKILL.md +27 -43
  50. package/runtime/skills/okstra-rollup/SKILL.md +6 -6
  51. package/runtime/skills/okstra-run/SKILL.md +7 -16
  52. package/runtime/skills/okstra-schedule/SKILL.md +64 -419
  53. package/runtime/skills/okstra-setup/SKILL.md +25 -223
  54. package/runtime/skills/okstra-setup/references/project-config.md +188 -0
  55. package/runtime/templates/reports/final-report.template.md +24 -1
  56. package/runtime/templates/reports/improvement-discovery-input.template.md +3 -2
  57. package/runtime/templates/reports/schedule.template.md +2 -2
  58. package/runtime/templates/worker-prompt-preamble.md +5 -1
  59. package/runtime/validators/validate-run.py +8 -7
  60. package/src/cli-registry.mjs +14 -0
  61. package/src/commands/inspect/set-work-status.mjs +31 -0
  62. package/src/commands/lifecycle/check-project.mjs +68 -56
  63. package/src/commands/lifecycle/install.mjs +1 -0
  64. 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 스코프만 제공한다.
@@ -609,7 +609,7 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
609
609
  | # | Tier | Command (verbatim) | Status | Exit code | Output tail |
610
610
  |---|------|---------------------|--------|-----------|-------------|
611
611
  {% for row in finalVerification.readonlyCommandLog -%}
612
- | {{ row.number | mdcell }} | {{ row.tier | mdcell }} | `{{ row.command | mdcell }}` | `{{ row.status | mdcell }}` | {{ (row.exitCode if row.exitCode is not none else '—') | mdcell }} | {{ (row.rejectionReason if row.status == 'rejected' else row.outputTail) | mdcell }} |
612
+ | {{ row.number | mdcell }} | {{ row.tier | mdcell }} | `{{ row.command | mdcell }}` | `{{ row.status | mdcell }}` | {{ (row.exitCode if row.exitCode is not none else '—') | mdcell }} | {{ (row.statusReason if row.status in ['rejected', 'env-unavailable'] else row.outputTail) | mdcell }} |
613
613
  {% endfor %}
614
614
 
615
615
  ### 5.8.6 Conditional Acceptance Conditions
@@ -640,6 +640,29 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
640
640
 
641
641
  {{ finalVerification.routingRecommendation }}
642
642
 
643
+ ### 5.8.9 Could-Not-Verify Roll-up
644
+
645
+ Single scan of every agreed check that this run did NOT confirm — requirement gaps, commands not run (rejected / not-configured / env-unavailable), and blocked manual tests — aggregated from the sections above.
646
+
647
+ {% set nv = namespace(any=false) -%}
648
+ {% for row in finalVerification.validationEvidence if row.status == 'gap' -%}
649
+ {% set nv.any = true -%}
650
+ - `{{ row.id | mdcell }}` requirement gap — {{ row.requirement | mdcell }}
651
+ {% endfor -%}
652
+ {% for row in finalVerification.readonlyCommandLog if row.status in ['rejected', 'not-configured', 'env-unavailable'] -%}
653
+ {% set nv.any = true -%}
654
+ - `{{ row.command | mdcell }}` — {{ row.status | mdcell }}: {{ row.statusReason | mdcell }}
655
+ {% endfor -%}
656
+ {% if finalVerification.manualUserTest.applicable -%}
657
+ {% for row in finalVerification.manualUserTest.results if row.result == 'blocked' -%}
658
+ {% set nv.any = true -%}
659
+ - manual: {{ row.target | mdcell }} — {{ row.performed | mdcell }} (blocked): {{ row.observed | mdcell }}
660
+ {% endfor -%}
661
+ {% endif -%}
662
+ {% if not nv.any -%}
663
+ - All agreed checks were executed or covered — nothing left unverified.
664
+ {%- endif %}
665
+
643
666
  {% endif %}
644
667
  {% if header.taskType == 'improvement-discovery' %}
645
668
  ## 5.9 Improvement Candidates
@@ -51,8 +51,9 @@ taskType: "{{FM_TASK_TYPE}}"
51
51
  ## Phase 1.5 — Lead Reflect-Back Grilling
52
52
 
53
53
  This section is filled in by the lead during Phase 1.5 before worker dispatch.
54
- Workers MUST read the resolved values from `runs/improvement-discovery/<seq>/state/phase-1.5-grilling.md`
55
- rather than the unresolved brief.
54
+ Workers MUST read the resolved values from the log the lead points to via the
55
+ `**Phase 1.5 Grilling Log:** <abs-path>` dispatch-prompt anchor
56
+ (`runs/improvement-discovery/<seq>/state/phase-1.5-grilling.md`) rather than the unresolved brief.
56
57
 
57
58
  - Reflect-back summary:
58
59
  - Open questions (Q1..QN):
@@ -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
 
@@ -103,6 +103,10 @@ For the **final-verification phase** specifically, the dispatched prompt MUST al
103
103
  - `**Verification base ref:** <base-ref>` — the implementation base for the diff.
104
104
  - `**Verification diff stat:** <git diff --stat output>` — the captured diff-stat that bounds the verification scope.
105
105
 
106
+ For the **improvement-discovery phase** specifically, the dispatched prompt MUST also include the resolved-scope log the lead wrote during Phase 1.5, so workers key off an explicit absolute path instead of re-deriving the run directory:
107
+
108
+ - `**Phase 1.5 Grilling Log:** <absolute-path>` — the lead's Phase 1.5 reflect-back log (`<RUN_DIR>/state/phase-1.5-grilling.md`, written before dispatch). Its `Resolved scope` / `Resolved lenses` blocks are the authoritative scope and lens definition for this run. Read the file at this absolute path; do NOT re-derive it from `<RUN_DIR>`, and do NOT re-interpret the brief's raw `scan-scope` / `priority-lenses`. The presence of this anchor is itself the signal that this is an improvement-discovery run — no separate task-type detection is required.
109
+
106
110
  When a worker reads any project-relative path from the prompt, it MUST resolve it against `Project Root` (e.g. `<Project Root>/<Result Path>`) — never use bare relative paths that depend on cwd.
107
111
 
108
112
  ---
@@ -1220,6 +1220,7 @@ FINAL_VERIFICATION_REQUIRED_SECTIONS = (
1220
1220
  "Conditional Acceptance Conditions",
1221
1221
  "Manual User Test Results",
1222
1222
  "Routing Recommendation",
1223
+ "Could-Not-Verify Roll-up",
1223
1224
  )
1224
1225
 
1225
1226
  # Allowed Verdict Token vocabulary, by task-type. `release-handoff` is
@@ -1715,7 +1716,7 @@ _SINGLE_VOTE_BLOCKING_KINDS = {"a", "d"}
1715
1716
 
1716
1717
  def _classify_plan_item_gate(item: dict) -> str:
1717
1718
  """Recompute one plan item's gate class from its per-worker verdicts,
1718
- per `prompts/lead/convergence.md` "Round protocol". Returns one of
1719
+ per `prompts/lead/plan-body-verification.md` "Round protocol". Returns one of
1719
1720
  ``majority-disagree`` / ``has-dissent`` / ``full-consensus`` /
1720
1721
  ``all-non-result``. Collapses ``partial-consensus`` and
1721
1722
  ``dissent-isolated`` into ``has-dissent`` because they resolve to the
@@ -1797,12 +1798,12 @@ def _validate_plan_body_gate_recompute(data: dict, failures: list[str]) -> None:
1797
1798
  f"only support `{recomputed}` (a majority DISAGREE, a DISAGREE(f) on "
1798
1799
  "a P-Req item, or all-non-result dispatches were recorded). The gate "
1799
1800
  "value must honestly aggregate the worker votes — do not upgrade it "
1800
- "to unblock the run (convergence.md Round protocol)."
1801
+ "to unblock the run (plan-body-verification.md Round protocol)."
1801
1802
  )
1802
1803
 
1803
1804
 
1804
1805
  # 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
1806
+ # extracted into for §5.5.9 cross-verification (plan-body-verification.md Plan-item
1806
1807
  # extraction). A weak item dropped from planItems escapes the gate entirely.
1807
1808
  _PLAN_ITEM_SOURCES = (
1808
1809
  ("optionCandidates", "P-Opt", "Option Candidates (§4.5.1)"),
@@ -1848,7 +1849,7 @@ def _validate_plan_item_extraction_completeness(data: dict, failures: list[str])
1848
1849
  f"planBodyVerification.planItems carries only {extracted} "
1849
1850
  f"`{prefix}-*` verdict item(s). Every plan-body item MUST be "
1850
1851
  "extracted for cross-verification — a dropped item escapes the "
1851
- "gate (convergence.md Plan-item extraction)."
1852
+ "gate (plan-body-verification.md Plan-item extraction)."
1852
1853
  )
1853
1854
 
1854
1855
 
@@ -1885,7 +1886,7 @@ def _validate_plan_item_subject_substance(data: dict, failures: list[str]) -> No
1885
1886
  f"placeholder subject `{subject}`. Give a plain-language label of "
1886
1887
  "what the item is (e.g. 'Option A: upload v2 를 신규 모듈로 분리') so "
1887
1888
  "the §5.5.9 reader knows what each verdict is about "
1888
- "(convergence.md Plan-item extraction)."
1889
+ "(plan-body-verification.md Plan-item extraction)."
1889
1890
  )
1890
1891
 
1891
1892
 
@@ -1894,7 +1895,7 @@ def _validate_plan_body_clarification_matching(data: dict, failures: list[str])
1894
1895
  must point (via `clarificationId`) at an existing `blocks: approval`
1895
1896
  clarification row. Closes the hole where a majority-disagree item blocks the
1896
1897
  gate but no §1 Clarification row is raised, so the user never sees why
1897
- approval is withheld (convergence.md self-review step 7).
1898
+ approval is withheld (implementation-planning.md self-review step 7).
1898
1899
  """
1899
1900
  ip = data.get("implementationPlanning")
1900
1901
  if not isinstance(ip, dict):
@@ -1920,7 +1921,7 @@ def _validate_plan_body_clarification_matching(data: dict, failures: list[str])
1920
1921
  f"final-report data.json: plan item `{item_id}` is majority-disagree "
1921
1922
  "but carries no `clarificationId`. A blocking disagreement MUST "
1922
1923
  "surface as a `## 1. Clarification Items` row (blocks=approval) so "
1923
- "the user sees the blocker (convergence.md self-review step 7)."
1924
+ "the user sees the blocker (implementation-planning.md self-review step 7)."
1924
1925
  )
1925
1926
  elif cid not in approval_ids:
1926
1927
  reason = (