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
@@ -25,7 +25,7 @@ okstra tasks are always operated using the `Claude lead` + required worker team
25
25
 
26
26
  **Model assignment has no default.** The model for every role comes from `resultContract.requiredWorkerRoles[*].modelExecutionValue` in `task-manifest.json` (and lead model metadata). There is no per-role hard-coded fallback — see "Model Assignment Rules" below.
27
27
 
28
- **Dispatch-prompt invariant (BLOCKING).** Lead's dispatch prompt body for Claude / Codex / Antigravity workers MUST be byte-identical except for the role label and any wrapper-specific path headers (e.g. `**Worktree:**`, `**Errors sidecar path:**`). Lead MUST NOT bias the brief by inserting per-worker emphasis sentences ("you focus on X") into the body. Bias-by-prompt reproduces the historical failure mode where Claude commented only on assumptions, Codex only on code paths, and Antigravity only on requirements — leaving convergence with nothing to converge on.
28
+ **Dispatch-prompt invariant.** Lead's dispatch prompt body for Claude / Codex / Antigravity workers MUST be byte-identical except for the role label and any wrapper-specific path headers (e.g. `**Worktree:**`, `**Errors sidecar path:**`). Lead MUST NOT bias the brief by inserting per-worker emphasis sentences ("you focus on X") into the body. Bias-by-prompt reproduces the historical failure mode where Claude commented only on assumptions, Codex only on code paths, and Antigravity only on requirements — leaving convergence with nothing to converge on.
29
29
 
30
30
  ### Model Assignment Rules
31
31
 
@@ -51,7 +51,7 @@ Only workers selected from `recommendedWorkers` in `task-manifest.json` and `res
51
51
  0. **Teammate dispatch (BLOCKING).** Claude Code v2.1.178 removed the `TeamCreate` / `TeamDelete` tools and the `Agent(...)` `team_name` parameter. With `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` (seeded by `okstra install`) the session owns one implicit team from start; Lead dispatches every worker with `Agent(name: "<role>-worker", run_in_background: true)` and **NO `team_name`**, and teammates auto-join that implicit team. Do NOT search for, probe, or call `TeamCreate` — its absence is the normal state on every current build, never an "Agent Teams unavailable" signal, and never a reason to degrade the run. Before any dispatch, record the `teamName` audit label plus `teamCreate: { attempted: false, status: "implicit", splitPane: <true if $TMUX set, else false> }` (or the `status: "skipped", reason: "concurrent-run"` variant when the launch prompt's concurrent-run gate applies). Split-pane teammates appear when `$TMUX` is set (`teammateMode: auto`); without tmux they run in-process — both are normal. See [okstra-lead-contract.md Phase 3](./okstra-lead-contract.md) for the full sequence.
52
52
  1. `Claude lead` is responsible for orchestration, convergence supervision, and final-report review/approval. It never overrides worker analysis results, and it never authors the final-report file when `Report writer worker` is in the roster.
53
53
  2. `Report writer worker` is NOT an analysis worker. It is excluded from Phase 4/5 (initial analysis) and Phase 5.5 (convergence re-verification). It is spawned only in Phase 6 and is the **author** of the final-report file at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md`.
54
- 3. When `Report writer worker` is in the roster, Lead MUST dispatch it in Phase 6. The only legal lead-authored fallback is when a dispatch was attempted and recorded a terminal status of `error` / `timeout` / `not-run` with a concrete logged reason. Speculative reasons such as "session resume constraint" or "team is no longer alive" are NOT valid — Lead can always dispatch a fresh subagent (omit `team_name` if the team is gone).
54
+ 3. When `Report writer worker` is in the roster, Lead MUST dispatch it in Phase 6. The only legal lead-authored fallback is when a dispatch was attempted and recorded a terminal status of `error` / `timeout` / `not-run` with a concrete logged reason. Speculative reasons such as "session resume constraint" or "team is no longer alive" are NOT valid — Lead can always dispatch a fresh subagent (the implicit team is always present).
55
55
  4. The assigned model for each role is maintained based on `resultContract.requiredWorkerRoles` in task-manifest.json and the lead model metadata.
56
56
  5. Required roles must not be replaced by unnamed generic parallel workers.
57
57
  6. Before dispatching any required worker, persist the exact worker prompt to the assigned current-run prompt history path under `runs/<task-type>/prompts/`.
@@ -82,7 +82,7 @@ If the task brief contains an `## Available MCP Servers` section, inject only th
82
82
 
83
83
  Before dispatching any required worker, lead persists the exact worker prompt to the assigned current-run prompt history path under `runs/<task-type>/prompts/`. Do not use `/tmp/*prompt*.txt` as the canonical artifact path.
84
84
 
85
- Send byte-identical dispatch prompts to every analysis worker (Claude / Codex / Antigravity), modulo the role label and the wrapper-specific path headers enumerated in the "Dispatch-prompt invariant" rule of the Role Definitions section. The prior "role-specific emphasis" guidance is retired — emphasis in the body biases each worker toward its lens and silently kills convergence (see Role Definitions for the failure mode). Specialization lives in Section 6 of the worker output, not in the dispatch prompt body.
85
+ Send byte-identical dispatch prompts to every analysis worker (Claude / Codex / Antigravity), modulo the role label and the wrapper-specific path headers enumerated in the "Dispatch-prompt invariant" rule of the Role Definitions section. Per-worker emphasis in the body biases each worker toward its lens and silently kills convergence (see Role Definitions for the failure mode). Specialization lives in Section 6 of the worker output, not in the dispatch prompt body.
86
86
 
87
87
  ### Required Reading + Error Reporting via Worker Preamble (SSOT)
88
88
 
@@ -93,7 +93,7 @@ What the lead MUST still do per dispatch:
93
93
  - Inject the absolute `**Errors log path:**` and `**Errors sidecar path:**` headers (#7 and #8 above) — workers cannot synthesize these paths.
94
94
  - Omit the preamble pointer for reverify dispatches (Phase 5.5 lightweight mode) — see [convergence](./convergence.md) "Reverify prompt: required-reading suppression".
95
95
 
96
- Audience-scoped file enumeration (BLOCKINGperformance optimization):
96
+ Audience-scoped file enumeration (performance optimization mandatory):
97
97
 
98
98
  | Recipient | Files the lead lists under `## Inputs` |
99
99
  |---|---|
@@ -151,7 +151,7 @@ For the Claude Code `team` backend, use the self-scheduled polling protocol belo
151
151
  - pending set empty → proceed to the next phase. The background task already self-terminated, so there is NO schedule to disarm — this self-terminating property is why a background poll is preferred over a cron schedule.
152
152
  - the poll exited `POLL_TIMEOUT` for a worker past its deadline → record terminal status `timeout` for that worker, remove it from the pending set, then redispatch-once (per "Lead Redispatch Policy on Result-Missing" below) or proceed.
153
153
  - any error / abort path → no zombie schedule exists, because the background task is self-terminating.
154
- 6. **Per-worker soft timeout (BLOCKING).** Use 2× the task-type expected per-worker duration in the table below as `<per-worker-deadline-seconds>`. This supersedes the unimplemented "수정 B" in `agents/TODO.md` (Leader-side worker soft timeout): the background poll's `deadline` IS that safety net.
154
+ 6. **Per-worker soft timeout.** Use 2× the task-type expected per-worker duration in the table below as `<per-worker-deadline-seconds>`. The background poll's `deadline` IS the lead-side worker soft-timeout safety net.
155
155
 
156
156
  | Task type | Expected per-worker | Deadline (2×) |
157
157
  |---|---|---|
@@ -161,7 +161,11 @@ For the Claude Code `team` backend, use the self-scheduled polling protocol belo
161
161
  | implementation | 20 min | 40 min |
162
162
  | final-verification | 10 min | 20 min |
163
163
 
164
- Relationship to the Codex / Antigravity wrapper polling contract: that contract (in the errors-sidecar section above) governs how a *wrapper subagent* waits on its own external CLI via `BashOutput`. This section governs how *Lead* waits on the worker subagents themselves. The two compose — Lead's background poll watches the result files; each wrapper independently watches its CLI — and neither imposes a timeout on the other (see "No external timeout on wrapper subagents").
164
+ Relationship to the Codex / Antigravity wrapper polling contract: that contract (in the errors-sidecar section below) governs how a *wrapper subagent* waits on its own external CLI via `BashOutput`. This section governs how *Lead* waits on the worker subagents themselves. The two compose — Lead's background poll watches the result files; each wrapper independently watches its CLI — and neither imposes a timeout on the other (see "No external timeout on wrapper subagents").
165
+
166
+ ### Claude-worker heartbeat staleness (lead-side liveness check)
167
+
168
+ The claude-worker appends a `- PROGRESS: <stage> <ISO-UTC>` line to its audit sidecar (`runs/<task-type>/worker-results/claude-worker-audit-<task-type>-<seq>.md`) at least every 5 minutes (`agents/workers/claude-worker.md` "Heartbeat"). While the result file is still pending, if that sidecar is absent or its mtime is >5 minutes stale, treat the dispatch as `timeout` and redispatch once with a byte-identical prompt (one-retry budget shared with "Lead Redispatch Policy on Result-Missing" below); after a second silent hang, record terminal status `timeout` with the missing-sidecar reason in team-state. The heartbeat is an auxiliary liveness signal layered on top of the completion protocol above — the authoritative completion signal remains the result file — and a missing sidecar after the result file appears is itself a contract violation per the heartbeat rule.
165
169
 
166
170
  ## Lead Redispatch Policy on Result-Missing
167
171
 
@@ -315,20 +319,14 @@ Workers MUST use only `errorType: "tool-failure"` in the **sidecar file**.
315
319
  **Path delivery contract (BLOCKING).** Workers do NOT synthesize the
316
320
  run-level errors log path or their sidecar path from the
317
321
  `runs/<task-type>/...` template syntax. Both absolute paths are delivered
318
- by Lead via two dispatch-prompt header lines:
319
-
320
- - `**Errors log path:** <absolute path>` — run-level JSONL (`okstra error-log append-observed --out ...`)
321
- - `**Errors sidecar path:** <absolute path>` — per-worker JSON (`{ "schemaVersion": 1, "errors": [...] }`)
322
-
323
- Lead obtains both paths from the launch prompt's `## Run Logs (error-log
322
+ by Lead via anchor headers #7 / #8 (Worker Prompt Composition above); Lead
323
+ obtains the values from the launch prompt's `## Run Logs (error-log
324
324
  wiring)` section (resolved by the okstra runtime via `paths.py`). If Lead
325
325
  omits either header, the worker MUST return `<WORKER>_ERRORS_PATH_MISSING`
326
- without proceeding — this is the contractual replacement for the previous
327
- "derive from template placeholders" behavior, which silently produced
328
- empty run-level error logs in production.
326
+ without proceeding.
329
327
 
330
328
  - `cli-failure` events are recorded by the wrapper subagent itself (Codex / Antigravity), but **directly to the run-level error log** via `okstra error-log append-observed --error-type cli-failure ...` — NOT via the sidecar. The sidecar is an in-process tool-failure channel only.
331
- - **Wrapper invocation arity.** Both `okstra-codex-exec.sh` and `okstra-antigravity-exec.sh` accept four required positional arguments plus an optional fifth `<role>`: `<project-root> <model> <prompt-path> <worktree-path> [<role>]`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise. For codex it becomes `--add-dir <worktree>` (sandbox write access); for antigravity it is appended to `--include-directories`. Omitting it during implementation causes the codex sandbox to reject every Edit/Write targeting the worktree with EPERM. Workers extract the path from the `**Worktree:**` / `EXECUTOR_WORKTREE_PATH` / `cwd for every mutating command:` line in the lead prompt. The optional fifth `<role>` is folded into both the caller (worker) pane title `<cli>-<role>-<pid>` and the sibling trace-pane title `<cli>-<role>-<pid>-tail` (e.g. `codex-executor-93421` ↔ `codex-executor-93421-tail`). `<pid>` is the wrapper's own PID and disambiguates concurrent dispatches of the same role. The role value comes from the dispatch prompt's `**Pane role:**` line: `executor` on an `implementation` Executor dispatch, `verifier` on an `implementation` / `final-verification` verifier dispatch, so the trace pane names the actual job rather than a generic `worker`. When no `**Pane role:**` line is present (analysis phases), pass the literal `worker` (the wrapper also defaults to `worker` if the argument is omitted).
329
+ - **Wrapper invocation arity.** Both `okstra-codex-exec.sh` and `okstra-antigravity-exec.sh` accept four required positional arguments plus an optional fifth `<role>`: `<project-root> <model> <prompt-path> <worktree-path> [<role>]`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise. For codex it becomes `--add-dir <worktree>` (sandbox write access); for antigravity it is appended to `--include-directories`. Omitting it during implementation causes the codex sandbox to reject every Edit/Write targeting the worktree with EPERM. Workers extract the path from the `**Worktree:**` / `EXECUTOR_WORKTREE_PATH` / `cwd for every mutating command:` line in the lead prompt. The optional fifth `<role>` is folded into both the caller (worker) pane title `<cli>-<role>-<pid>` and the sibling trace-pane title `<cli>-<role>-<pid>-tail` (e.g. `codex-executor-93421` ↔ `codex-executor-93421-tail`). `<pid>` is the wrapper's own PID and disambiguates concurrent dispatches of the same role. The role value comes from the dispatch prompt's `**Pane role:**` line (injected by Lead per [okstra-lead-contract](./okstra-lead-contract.md) Phase 4 "Agent `name` on dispatch"); when the line is absent, pass the literal `worker` (the wrapper also defaults to `worker` if the argument is omitted).
332
330
  - **Background dispatch + polling contract (Codex / Antigravity wrappers).** Both wrapper subagents MUST dispatch `okstra-codex-exec.sh` / `okstra-antigravity-exec.sh` via `Bash(run_in_background: true)` and poll with `BashOutput(bash_id)` until the shell reports `status == "completed"`, capped at 30 minutes (1800s) of wall-clock elapsed time. `BashOutput` itself is the wait primitive — call it back-to-back; do NOT insert a standalone `sleep` between polls. The Claude Code harness blocks `sleep` calls of 5 seconds or longer as a circumvention vector and explicitly forbids chaining shorter sleeps inside until-loops to work around the block. Workers that hit the contract bug must NOT self-recover with `until ...; do sleep 2; done` wrappers — that path violates the harness anti-circumvention rule, even though it superficially "works". The current rule applies in **every phase** (analysis runs typically complete in 1–2 `BashOutput` calls, so there is no regression for short jobs). Recording responsibilities:
333
331
  - Successful completion: return the wrapper's accumulated stdout from the final `BashOutput`. No log entry.
334
332
  - Non-zero `exit_code` reported by `BashOutput`: record a `cli-failure` to the run-level error log with the real `exit_code` and observed `duration-ms`.
@@ -367,7 +365,7 @@ Every worker result file under `worker-results/` must begin with a standardized
367
365
  **Model:** <Role>, <AI model>
368
366
  ```
369
367
 
370
- Task-type and date are **not** repeated in this human header — they already live in the YAML frontmatter (`taskType`, `date`), which is the copy Obsidian indexes. Restating them here added a third, un-indexed, machine-unparsed copy with no value; the frontmatter is the single source for both. The `Target:` line is optional — include it when the run is scoped to a specific path or module, omit it for whole-project runs; when present, place it between the title and the `Model:` line.
368
+ Task-type and date are **not** repeated in this human header — the YAML frontmatter (`taskType`, `date`) is the single source for both (and the copy Obsidian indexes). The `Target:` line is optional — include it when the run is scoped to a specific path or module, omit it for whole-project runs; when present, place it between the title and the `Model:` line.
371
369
 
372
370
  Example (Codex / Report-writer headers are identical modulo the role name and model id):
373
371
 
@@ -30,43 +30,7 @@ profile document.
30
30
  - This rule does NOT relax any phase-specific Forbidden actions list; safety rules in the per-profile document remain in force regardless of the user's authority.
31
31
  - Anti-escalation rule (shared):
32
32
  - treating "다음 단계 진행해" or equivalent user phrases as authorisation to start a *different* lifecycle phase is forbidden. The next phase begins only in a separate okstra run launched with the new `--task-type`. Per-profile documents may further restrict this within their own scope.
33
- - Run-start pane recording (shared — runs ONCE at run start, before the FIRST worker dispatch):
34
- - The codex/antigravity wrappers now self-anchor their trace pane by walking their own ancestor PIDs against tmux `pane_pid`s (see `lib/okstra/tmux-pane.sh`), so they no longer depend on this file. The lead still records its own pane id here so the cleanup script below can know which pane to never kill and can detect tmux absence itself — the lead does NOT read it back to gate those steps. A bare `tmux display-message -p '#{pane_id}'` is NOT reliable for this — Claude Code's Bash tool strips `$TMUX`/`$TMUX_PANE`, so that command returns the most-recently-active *client's* pane (often a different session, or a foreign pane when the lead is launched outside tmux entirely). The lead therefore records via the same ancestry resolver.
35
- - The lead MUST run once, at run start: `mkdir -p "<RUN_DIR>/state" && { . "$HOME/.okstra/bin/lib/okstra/tmux-pane.sh" 2>/dev/null && okstra_resolve_caller_pane; } > "<RUN_DIR>/state/lead-pane.id" 2>/dev/null || true` (substitute the run's absolute `RUN_DIR`). When the lead is not inside a tmux pane (e.g. Claude launched from the GUI app) no ancestor matches a pane and the file is empty. The cleanup steps below do NOT read this file to decide whether to run — they always run, and the script itself reads `lead-pane.id` (with its own ancestry fallback) only to know which pane to never kill and to detect tmux absence. The lead never interprets this file to gate a pane step.
36
- - This recording is **silent internal setup**: the lead MUST emit NOTHING to the user about it — no pane id, no `%NNN`, no `lead-pane.id` path, no announcement of the phase-start-cleanup / wrap-up-disposition steps. Just record the file and proceed.
37
- - Phase-start cleanup — prior-batch panes + completed teammates (shared — runs BEFORE dispatching each new worker batch, at the SAME boundaries: just before each `PROGRESS: phase-5.5-convergence round=<N>` marker and just before `PROGRESS: phase-6-synthesis dispatching report-writer-worker`):
38
- - **(a) tmux panes.** okstra creates two kinds of tmux pane per run: **worker-agent panes** the harness gives to dispatched subagents (titled `claude-worker` / `codex-worker` / `antigravity-worker` / `report-writer-worker`), and **trace panes** the codex/antigravity wrappers spawn (`<cli>-<role>-<pid>-tail`). Both accumulate because each new batch spawns fresh panes and the prior ones are never reclaimed. The lead MUST ALWAYS run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"` immediately before dispatching the next batch — NEVER gating this call on its own reading of `lead-pane.id`. The script self-resolves the lead pane and is a safe no-op outside tmux (it returns an empty pane list), so an always-run call cannot do harm; it closes every prior-batch okstra pane (worker-agent + trace) for this run while NEVER killing the lead's own pane. The lead MUST NOT fabricate a synthetic pane list.
39
- - **(b) completed teammates (split-pane runs only).** Each convergence round / critic / gap-verification / report-writer dispatch spawns teammates under fresh Agent `name`s (`<role>-reverify-r<N>`, `<provider>-worker-critic`, `report-writer-worker`, …); completed teammates do NOT leave the FleetView roster on their own — they linger as idle pills until run-end teardown, so a long run's roster grows unreadable. Before dispatching the next batch, the lead MUST send `SendMessage(to: <name>, message: { type: "shutdown_request" })` to every teammate it spawned earlier in THIS run **whose completion is already confirmed** (its Result Path exists / it is in the self-scheduled-polling done set). The `message` MUST be the object literal shown, NEVER a JSON string stuffed into a text field. This is the same graceful-shutdown primitive used at `Run-end teammate teardown`, applied per batch instead of once at the end.
40
- - **Only confirmed-complete teammates, never the lead.** NEVER send a shutdown_request to a teammate whose result file has not yet appeared. In particular the critic runs CONCURRENTLY with the first reverify round (see `convergence` "Coverage critic pass"), so it MUST NOT be shut down until its own result is collected. NEVER target the lead's own session.
41
- - Only individual completed teammates are dismissed here; the rest of the roster stays for the remaining batches. There is no `TeamDelete` tool (removed in CC v2.1.178) — the implicit team itself ends with the session; teardown only dismisses teammates.
42
- - Silent-skip in `tmux-pane` backend runs (external-lead workers are not session teammates); otherwise the on-disk existence check at run-end teardown is the authority on whether a roster exists.
43
- - **(c) record the current session into team-state (축1 — re-issued session generation capture).** batch cleanup 직후(그리고 아래 `PROGRESS: phase-batch-cleanup` 방출 전), lead 는 재발급된 세션 세대가 Phase 7 토큰·PROGRESS 수집에서 누락되지 않도록 현재 live 세션을 team-state 에 기록한다: `okstra token-usage "<TEAM_STATE_PATH>" --record-observed-session --project-root "<PROJECT_ROOT>"` 를 1회 호출한다(`<TEAM_STATE_PATH>` 와 `<PROJECT_ROOT>` 는 shell 변수·`$(...)` 없이 리터럴 절대경로로 치환 — `Bash(okstra:*)` 매칭 유지). `--project-root` 는 **필수**다: 생략하면 collector 가 team-state 의 상위 디렉터리(run/state 경로)를 project root 로 잘못 추론해 엉뚱한 `~/.claude/projects/<encoded-cwd>/` 를 뒤지고 조용히 no-op 하므로 축1 이 꺼진다. 실패는 무시 가능한 best-effort — worker 세션이거나 이미 기록된 세대이면 no-op 다. `python3 "$HOME/..."` 직접 호출은 금지(`$HOME` 확장이 리터럴-토큰 permission 매칭을 깨고 매 호출 확인 프롬프트를 유발) — 반드시 `okstra token-usage` 서브커맨드를 쓴다.
44
- - Both (a), (b), and (c) run **automatically without any interactive prompt**. After running them, emit the checkpoint line `PROGRESS: phase-batch-cleanup panes=<n> teammates=<m>` (counts only — NEVER expose `%NNN`, `lead-pane.id`, or raw Agent names) and proceed; this is the BLOCKING checkpoint the Phase 7 validator requires before each convergence round and before the report-writer dispatch (`prompts/lead/okstra-lead-contract.md` "Progress reporting (BLOCKING)"). **Effect caveat:** Claude Code exposes no tool to surgically delete a roster entry mid-run, so a dismissed teammate may still show as an idle pill; the shutdown still reclaims its session and prevents the roster from growing with live members. On the FIRST batch of a run nothing has been dispatched yet — the pane/teammate steps (a)/(b) no-op; step (c) still records the current session.
45
- - Phase wrap-up — okstra pane disposition (shared, runs AFTER Phase 7 persistence/token collection and BEFORE teammate teardown):
46
- - At run end the only residual okstra panes are the LAST phase's (e.g. the `report-writer-worker` agent pane and any codex/antigravity trace pane). `okstra-trace-cleanup.sh --list --run-dir "<RUN_DIR>"` returns one tab-separated `<pane_id>\t<pane_title>` line per residual okstra pane (worker-agent + trace) for this run.
47
- - After the final-report file has been written and the routing recommendation has been issued, the lead MUST ALWAYS run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --list --run-dir "<RUN_DIR>"` exactly once — NEVER gating this call on its own reading of `lead-pane.id`. The output lists every residual okstra pane (worker-agent + trace) for this run, never the lead's own pane; outside tmux it is a safe no-op (empty output).
48
- - If the list is empty and no matching on-disk team config exists (see Run-end teammate teardown below), skip the question — there is nothing to ask about (the phase-start resets above usually already cleared prior phases).
49
- - Otherwise the lead MUST present the user with a strict binary choice **before** declaring the phase complete. This single choice controls BOTH residual pane cleanup and worker teammate dismissal; do not ask a second teammate-cleanup question. Use one prompt of this shape (Korean preferred, English acceptable if the rest of the run is in English):
50
- > 현재 phase 종료 시점입니다. 다음 okstra pane 및 worker teammate 가 남아 있습니다 — 닫고 정리할까요?
51
- > <인용된 `--list` 출력>
52
- > (예) 모두 닫고 팀원 정리 / (아니오) 그대로 두기
53
- - On `예` / `y` / `close` → run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"`, then run the Run-end teammate teardown sequence below without asking a second question. Report the pane kill count and teammate cleanup outcome back in one sentence.
54
- - On `아니오` / `n` / `keep` → leave panes and teammates intact; remind the user that tagged trace panes will be cleaned up automatically when Claude `/exit` fires the `SessionEnd` hook (`--reap`), while FleetView/Teams teammates remain visible until session end or manual Teams UI cleanup.
55
- - The question MUST be a clean yes/no — do NOT offer "close some / keep some" partial answers, do NOT propose alternatives like "close only codex panes". The whole-set decision keeps the wrap-up predictable: lead pane excluded, every okstra worker/tail pane and worker teammate cleaned together on approval.
56
- - This step is mandatory for every phase (`requirements-discovery`, `error-analysis`, `implementation-planning`, `implementation`, `final-verification`, `release-handoff`). Whether there is anything to ask about is decided by the `--list` output, NOT by the lead's reading of `lead-pane.id`: an empty list → skip the question (above); the lead MUST NOT fabricate a synthetic pane list.
57
- - Run-end teammate teardown (shared — MUST run AFTER the pane disposition question and BEFORE `PROGRESS: complete`):
58
- - CC v2.1.178 removed `TeamCreate` / `TeamDelete`: this run rode the session's **implicit team** (Phase 3 recorded `teamCreate.status: "implicit"` plus the `teamName` audit label). There is no tool to delete a team — the implicit team ends when the session ends. What teardown CAN do is gracefully dismiss the worker teammates the lead spawned, so they stop showing as live members in the FleetView roster.
59
- - **Whether to run this step is decided by on-disk fact, resolved at teardown time by the CURRENT live session — NOT from `team-state.lead.sessionId`.** The harness names the team directory `session-<leadSessionId-prefix>` (the `teamName` label never becomes a directory name), but `team-state.lead.sessionId` is only a phase-3 snapshot: Claude Code re-issues the session id across resume / compaction mid-run, so the live roster usually sits under a DIFFERENT `session-*` dir than the snapshot names. Resolving from the snapshot is what made split-pane teardown silently miss its own live teammates. The resolver in step 1 below keys off the live session (falling back to the snapshot dir only when the live one is absent) and prints the members to dismiss.
60
- - **Resolver printed one or more `dismissible-member: <name>` lines** → teammates exist; run the dismissal sequence below.
61
- - **Resolver printed `no live roster for this session`, or split-pane was off** → nothing to dismiss → silent-skip.
62
- - The pane disposition prompt above is the only user prompt for this cleanup decision. Do NOT ask a second teammate-only question.
63
- - On `아니오` / `n` / `keep` → dismiss nothing. Tell the user the teammates remain visible in FleetView/Teams until the session ends, and can be removed earlier from the Teams UI.
64
- - On `예` / `y` / `close` → emit `PROGRESS: phase-7-teardown dismissing teammates`, then run the sequence below. Token-usage collection MUST already be complete — it reads `~/.claude/projects/` jsonls, which dismissal never touches, but the read MUST precede teardown.
65
- 1. Run `$HOME/.okstra/bin/okstra-team-reconcile.sh --project-root "<PROJECT_ROOT>" --fallback-team "session-<lead.sessionId-prefix>"` exactly once. It resolves the live session's `~/.claude/teams/session-<live>/config.json` (falling back to the snapshot `session-<lead.sessionId-prefix>` dir only if the live one is absent), flips dead-pane stale-active members to inactive (no-op when tmux is unavailable or nothing is stale), and prints one `dismissible-member: <name>` line per non-lead member. Do NOT loop it, and do NOT hand-resolve a config path from `team-state.lead.sessionId`.
66
- 2. Send each printed `dismissible-member: <name>` **whose completion is confirmed** (its Result Path exists) a structured `SendMessage(to: <name>, message: { type: "shutdown_request" })` — the `message` MUST be the object literal shown, NEVER a JSON string stuffed into a text field. NEVER target the lead's own session, and never use `TaskStop` (teammates are not background tasks — `TaskStop` 404s on a member address).
67
- - Claude Code exposes no tool to delete the team or surgically remove a roster entry, so a dismissed teammate may still show as an idle pill until the session ends. Do not pretend the team was deleted. If the user wants the roster gone entirely, surface the manual action: `Teams/FleetView 에서 team <teamName> 삭제`, and if tmux panes were kept earlier also show `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"`.
68
- - **Enforcement / safety net.** This teardown fires after the Phase 7 validator's window, so `validate_session_conformance.py` cannot check it (it is not a required checkpoint). The net is the `SessionEnd` hook `$HOME/.okstra/bin/okstra-team-reconcile.sh --session-end` (seeded into `settings.template.json`): when the Claude session ends it reconciles that session's roster regardless of whether this step ran, so a dead-pane member never lingers as a live pill past session end.
69
- - Report the outcome in one short line (e.g. `worker teammate 정리 완료`, or `stale 멤버 1명 reconcile 후 정리`) and proceed to the final `PROGRESS: complete ...` line.
33
+ - Run-scoped pane & teammate lifecycle (shared — run-start pane recording, per-batch cleanup, wrap-up pane disposition, run-end teammate teardown): these are lead-only operating duties and live in `prompts/lead/okstra-lead-contract.md` "Run-scoped pane & teammate lifecycle". Profiles do not restate them.
70
34
  - Brief handoff contract (shared — applies whenever the run consumes a task brief produced by `okstra-brief`):
71
35
  - the brief is a **pre-discovery artifact**: it converts a domain-reporter's words (non-expert *or* developer) into expert-consumable form so this and later phases can run with zero fill-in questions to the operator. The brief is **not** authoritative on solution decisions; it is authoritative on the reporter's intent.
72
36
  - **Reporter confirmation precondition (BLOCKING)**: the brief's frontmatter carries `reporter-confirmations: <complete | partial | pending | skipped>` set by `okstra-brief` Step 6.5. Every phase that consumes the brief MUST read this field before doing analysis. The handling matrix is:
@@ -11,7 +11,7 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
11
11
 
12
12
  ## Executor role binding (carried over from the thin core)
13
13
 
14
- - **Executor dispatch labelling.** When Lead dispatches the Executor it MUST set the Agent `name` to `<provider>-executor` (e.g. `codex-executor`, `claude-executor`) — NOT the generic `<provider>-worker` — so the FleetView teammate pill names the job; see `prompts/lead/okstra-lead-contract.md` Phase 4 "Agent `name` on dispatch". For a `codex` / `antigravity` Executor, Lead MUST additionally inject a `**Pane role:** executor` line into the dispatched prompt body so the CLI wrapper titles its tmux trace pane `<cli>-executor-<pid>` (the wrapper reads that line per `agents/workers/_cli-wrapper-template.md`). Token attribution is unaffected — the collector matches any `agentName` beginning `<provider>-`.
14
+ - **Executor dispatch labelling.** Agent `name` = `<provider>-executor` (e.g. `codex-executor`); for a `codex` / `antigravity` Executor, Lead additionally injects `**Pane role:** executor` into the dispatched prompt body. Naming/mapping rules and the token-attribution guarantee are owned by `prompts/lead/okstra-lead-contract.md` Phase 4 "Agent `name` on dispatch".
15
15
  - The `Executor` (bound in `implementation.md` thin core) is the **only worker permitted to use Edit / Write / state-mutating Bash commands** on project files. All other workers run read-only. When the executor provider is `codex` or `antigravity`, the actual file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --sandbox workspace-write`, antigravity's equivalent) — not through Claude-side Edit/Write tools — but the safety rules in this sidecar still apply identically.
16
16
  - When the thin core's Task worktree block resolves status to `created` or `reused`, the Executor MUST run every Edit / Write / build / test / commit command with the worktree path as cwd. Treat it as `project_root` for the duration of this run. Do NOT mutate the caller's original checkout. Do NOT `cd` out of the worktree to reach files. If a file outside the worktree is genuinely needed, treat it as a planning gap: record it in `Out-of-plan edits` and continue.
17
17
  - **How to set cwd per Bash call**: the Claude Bash tool inherits its cwd from the lead session, which is NOT the worktree. To put cwd-sensitive toolchains (`cargo`, `npm`, `pnpm`, `bun`, `pytest`, `make`, `go`) into the worktree, prefix the command with `cd {{EXECUTOR_WORKTREE_PATH}} && ` inside the same Bash invocation — e.g. `cd {{EXECUTOR_WORKTREE_PATH}} && cargo test -p foo`. **Never wrap in `bash -lc "..."` or `bash -c "..."`** — the wrapper hides the leading `cd` token from Claude Code's permission auto-allow layer (causing prompts on every call) without any safety benefit. For tools that accept an explicit working-directory flag (`git -C <path>`, `cargo --manifest-path`, `pytest --rootdir`), prefer that form over the `cd && ` chain. Edit / Write / Read tool calls already use absolute paths and need no cwd handling. The codex / antigravity executor CLI wrappers (`okstra-codex-exec.sh -C`, `okstra-antigravity-exec.sh --include-directories`) already inject worktree cwd at the CLI layer, so this rule applies primarily to the Claude executor.
@@ -34,7 +34,7 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
34
34
  - Order of operations per plan step: (1) write/extend the test that captures the step's acceptance criterion and confirm it fails for the right reason, (2) implement the minimum change to make it pass, (3) commit the test and its implementation together in a single commit (`feat|fix(<scope>): ...`) — do NOT commit the failing test separately, (4) refactor without changing behaviour and commit separately if any cleanup is made (`refactor(<scope>): ...`). The failing-then-passing transition is preserved as `TDD evidence` in the final report (failing output captured before the merged commit, passing output after), not as two separate commits.
35
35
  - Doc-only / config-only / pure-rename steps that have no observable runtime behaviour are exempt from the failing-test requirement, but the executor MUST cite the exemption per step in the final report (`TDD exemption: <reason>`).
36
36
  - When the touched area has no existing test harness, the executor MUST stand up the minimum harness needed to host one regression test for this run rather than skipping TDD entirely. Record the harness-bootstrap step as an `Out-of-plan edit` if it is not in the plan.
37
- - **DB / IO / SQL changes require real execution — mock-only is NOT validation evidence:** when this run's diff touches DB/IO/SQL (ORM / query-builder code — sequelize / typeorm / prisma / knex / raw SQL — `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string), a mocked unit test cannot observe the SQL the query builder actually emits a mocked suite once passed while `count({ col: 'FontFamily.fontFamily' })` threw `Unknown column` on the real DB. The executor MUST run the change against a real (or faithful-replica) datastore — the `db-test` validation step (plan `validation` db step, else `project.json.qaCommands.db-test`), targeting a **local / replica** DB — and cite its exact command + exit code in the final report's `Validation evidence`. If no real DB / `db-test` command is reachable, do NOT claim the change verified: label the DB portion `정적 분석상 …, 미검증(실행 안 함)` in the report, surface it in the routing recommendation, and never downplay the real run as "too heavy". `git push` stays forbidden (universal list); the unverified DB state is carried forward so `final-verification` cannot accept it and `release-handoff` cannot push.
37
+ - **DB / IO / SQL changes require real execution — mock-only is NOT validation evidence:** when this run's diff touches DB/IO/SQL (ORM / query-builder code — sequelize / typeorm / prisma / knex / raw SQL — `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string), a mocked unit test cannot observe the SQL the query builder actually emits (observed failure class: `_implementation-verifier.md` §"DB / IO / SQL change real-execution gate"). The executor MUST run the change against a real (or faithful-replica) datastore — the `db-test` validation step (plan `validation` db step, else `project.json.qaCommands.db-test`), targeting a **local / replica** DB — and cite its exact command + exit code in the final report's `Validation evidence`. If no real DB / `db-test` command is reachable, do NOT claim the change verified: label the DB portion `정적 분석상 …, 미검증(실행 안 함)` in the report, surface it in the routing recommendation, and never downplay the real run as "too heavy". `git push` stays forbidden (universal list); the unverified DB state is carried forward so `final-verification` cannot accept it and `release-handoff` cannot push.
38
38
  - **Real-IO test isolation (BLOCKING).** A test that exercises a **real** datastore, HTTP endpoint, external service, message queue, or filesystem — a live DB connection / DSN, a real `fetch` / `axios` / `http` request, an actual S3 / queue client, anything the project's normal CI test suite cannot run because that backend is absent — MUST be written under the task's qa scripts directory `<task_root>/qa/scripts/` (`<TASK_QA_PATH>/scripts`; the `qa/` root itself holds only data sidecars — the Tier 3 conformance manifest and `result-*.json`). It MUST NOT be written into the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*`, or anywhere the project's lint/test globs collect. Two reasons: (a) the project's CI / normal suite has no real DB or network, so a real-IO test placed in source silently breaks the pipeline; (b) it is an okstra verification artifact, and the artifact-home rule confines okstra outputs to `.okstra/`. **The dividing line is the IO, not the intent:** a unit test that stubs/spies only *injected collaborators* (mock — no real socket, no real DB handle) is a TDD red-green artifact and stays in source; the moment a test opens a real connection or makes a real network call it belongs in qa. A stage's real-IO requirement check is a Tier 3 conformance script under `<task_root>/qa/scripts/` (declared via the implementation-planning conformance entry) — never smuggle real IO into a `*.spec.*` in source to make it run "as a unit test". The `db-test` real-execution gate above is satisfied by the conformance/db-test path against the replica, NOT by adding a live-DB `*.spec.*` to the project suite. **Author qa specs with the project's own test framework — never hand-roll `describe`/`it`/`expect`.** When the project ships a test runner as a devDependency (jest / vitest / pytest …), the qa spec uses it, invoked with the project config plus a discovery override pointing at the qa scripts dir (jest: `npx jest --config <project jest config> --roots <task_root>/qa/scripts --runInBand <spec-name>`) — the project config keeps module aliases resolving while the default sweep never collects the file; never widen the project's own test config to include qa paths. For TypeScript qa specs also write `<task_root>/qa/scripts/tsconfig.json` (`extends` the project tsconfig, adds the runner's `types` entry, `"include": ["**/*.ts"]`) so editors resolve path aliases and test globals — it is a qa artifact like the rest (untracked). **These qa artifacts stay untracked — never commit them.** `.okstra/**` is gitignored (the artifact-home rule); conformance scripts and their results are *executed* and recorded in the carry sidecar / verifier result, never written into git history. A committed `.okstra/qa` file is a stage-branch defect that leaks okstra internals into the eventual PR (see the `git add` rules below).
39
39
  - re-read the approved plan end-to-end and parse the `## 5.5 Stage Map`. Read the **Stage** injected in the launch prompt (`Stage for this implementation run`): the single stage number this run owns. The runtime already selected and reserved this stage (one run = one stage) — do NOT recompute the start stage from `consumers.jsonl`.
40
40
  - load every `runs/<plan-key>/carry/stage-<i>.json` for `i ∈ depends-on(this stage)` and inject them into the executor's working context as "runtime carry-in". For a `depends-on (none)` stage, no sidecar load — task-brief only.
@@ -20,6 +20,7 @@ fails, fix it or surface the violation — do not claim done on a failing item.
20
20
  - conventions: applied the routed pack + project patterns (name which ones)
21
21
  - names & comments: names say what, comments say why; no obvious-restatement comments; no truthful-name violations
22
22
  - verification: ran the actual build/test — paste the exact command line and its real exit code / output tail, never a paraphrased "tests pass"
23
+ - acceptance: each implemented plan step's `Acceptance:` condition is observably met — cite the RED→GREEN test (or the command output) proving it per step, not one global "acceptance met"; and each `Test case (success|boundary|failure)` the plan declared for this stage has a matching test in the diff (name the test), so declared edge/failure cases are not silently uncovered
23
24
  - cleanup: no dead or commented-out code left behind
24
25
 
25
26
  Close with a `Self-check coverage:` line naming the files you verified the
@@ -9,7 +9,7 @@ at Phase 5, BEFORE constructing the verifier worker dispatch prompts.
9
9
 
10
10
  ## Verifier roles (resolved at run-prep time)
11
11
 
12
- - **Verifier dispatch labelling.** When Lead dispatches a verifier (here, and identically in `final-verification`) it MUST set the Agent `name` to `<provider>-verifier` (e.g. `claude-verifier`, `codex-verifier`) — NOT the generic `<provider>-worker` — see `prompts/lead/okstra-lead-contract.md` Phase 4 "Agent `name` on dispatch". For a `codex` / `antigravity` verifier, Lead MUST additionally inject a `**Pane role:** verifier` line into the dispatched prompt body so the CLI wrapper titles its tmux trace pane `<cli>-verifier-<pid>` (per `agents/workers/_cli-wrapper-template.md`). Token attribution is unaffected: the collector matches any `agentName` beginning `<provider>-`.
12
+ - **Verifier dispatch labelling.** Agent `name` = `<provider>-verifier` (here, and identically in `final-verification`); for a `codex` / `antigravity` verifier, Lead additionally injects `**Pane role:** verifier` into the dispatched prompt body. Naming/mapping rules and the token-attribution guarantee are owned by `prompts/lead/okstra-lead-contract.md` Phase 4 "Agent `name` on dispatch".
13
13
  - The verifier slots are `Claude verifier` and `Codex verifier`, plus `Antigravity verifier` **only when `antigravity` is in the resolved `--workers` roster**. Every verifier in the resolved roster is dispatched regardless of which provider holds the executor role; the executor's own provider is run *separately* as a verifier (a fresh CLI session with no shared context) so that no verdict is produced from the same session that wrote the diff. Verifiers MUST NOT call Edit, Write, or any Bash command that mutates files outside the run's artifact directories. If a verifier wants a fix, it records the recommendation in its worker result; it does not apply the fix itself.
14
14
  - Session isolation — not model-variant divergence — is the primary self-review safeguard: each verifier is a separate CLI invocation with its own context window, so reusing the same model variant for executor and same-provider verifier is acceptable. Different model variants (e.g. executor=opus / Claude verifier=sonnet) remain recommended when available.
15
15
  - Phase-specific model defaults override the shared defaults: `Claude verifier`=`opus`, `Codex verifier`=`gpt-5.5`, `Antigravity verifier`=`auto` (only when present in the roster). The `Executor`'s model is taken from the provider-specific worker model corresponding to `--executor`: claude→`--claude-model` (default `opus`), codex→`--codex-model` (default `gpt-5.5`), antigravity→`--antigravity-model` (default `auto`).
@@ -85,7 +85,7 @@ The final report keeps both — executor's `Validation evidence` AND each verifi
85
85
 
86
86
  ### Static design & test-quality review (gate — runs after the command re-run, before the verdict)
87
87
 
88
- Re-running commands proves the diff *builds and passes*; it does NOT prove the diff is *well-designed*. Lint/test green is necessary but not sufficient — self-mocked tests, interaction-only assertions, and untruthful names all survive a green pipeline. This gate is the filter for exactly those defects, so the executor's design errors are caught here instead of in post-merge PR review. It is a real gate, not a checklist: it enumerates the full diff and a blocking hit forces `FAIL`.
88
+ Re-running commands proves the diff *builds and passes*; it does NOT prove the diff is *well-designed*. Lint/test green is necessary but not sufficient — self-mocked tests, interaction-only assertions, and untruthful names all survive a green pipeline. This gate is the filter for exactly those defects, so the executor's design errors are caught here instead of in post-merge PR review. It is a real gate, not a checklist: it enumerates the full diff and a blocking hit forces `FAIL`. This blocking-check taxonomy is the **gate counterpart** of the executor's `Pre-commit diff review sweep` (`_implementation-diff-review.md`) prevention pass — the same defect families, different action (executor fixes in place; verifier fails the verdict). Both lists are inlined on purpose: each sidecar is delivered stand-alone into a flat CLI prompt (lazy-read, not INCLUDE-expanded), so neither can factor the taxonomy into a shared fragment.
89
89
 
90
90
  - **Scope (no silent sampling).** Enumerate every changed source/test file via `git diff --name-only <base>...HEAD` and review each one. Skipping a changed file silently is a `contract-violated` outcome. If a file's language has no reference and is not covered by the agnostic checks below, record `design-review skipped: <file> (language=<x> no reference)` — never pass it silently.
91
91
  - **Load the same conventions the executor used via the routed pack.** Use this worker prompt's `**Coding preflight pack:**` anchor header as the absolute path to the installed routed pack. Read `overview.md` first, then `clean-code.md`, then apply the router's three ordered stages: language, framework, architecture. In each stage, iterate every rule, treat a rule as matched when any listed condition is true, and accumulate every matching resource — including `frameworks/node-server.md` for server-side Node work and `architectures/hexagonal.md` for ports-and-adapters / NestJS-hex layouts. Degrade to the agnostic checks below when the resolved pack is unreadable, and record either `coding-conventions: resources=<...>` or `coding-conventions: resource-unavailable → applied <project rules + agnostic principles>`. The verifier does NOT inline language rules — it loads the same situation-specific resources as the executor preflight.
@@ -14,7 +14,7 @@
14
14
  - delivered artifacts match recorded expected values in `reference-expectations` (config files, deployment manifests, other recorded expected states); when reference-expectations are absent, record it as missing information rather than assuming a match
15
15
  - test & validation suite pass status — independently re-run the read-only two-tier command set (Tier 1 = brief/approved-plan `validation`, Tier 2 = `project.json` `qaCommands`) and confirm each passes on the verified head, citing exact command + exit code
16
16
  - test correctness — delivered tests actually assert the intended behaviour: no gutted/weakened assertions, no tautological or always-passing tests, no tests exercising only mocks; new behaviour has matching coverage
17
- - DB / IO / SQL real-execution evidence — trigger: the diff touches DB/IO/SQL (ORM / query-builder, `*.repository.*`, model / `migrations/**` / `*.sql`, or changed query strings). Then Validation Evidence MUST cite a real (or faithful-replica) DB execution — the `db-test` command + exit code — not a mock-only suite. Rationale: a mock-only suite cannot observe the SQL actually emitted (`count({ col: 'FontFamily.fontFamily' })` passed mocks yet threw `Unknown column` on the real DB). A DB-touching change whose only evidence is mocked, or for which no `db-test` ran, is an **Acceptance Blocker** (`major`+); since `accepted` requires zero blockers the verdict becomes `conditional-accept` / `blocked`. This gate stops an unverified DB change from reaching `release-handoff` and being pushed.
17
+ - DB / IO / SQL real-execution evidence — trigger: the diff touches DB/IO/SQL (ORM / query-builder, `*.repository.*`, model / `migrations/**` / `*.sql`, or changed query strings). Then Validation Evidence MUST cite a real (or faithful-replica) DB execution — the `db-test` command + exit code — not a mock-only suite. Rationale: a mock-only suite cannot observe the SQL actually emitted (observed failure class: `prompts/profiles/_implementation-verifier.md` §"DB / IO / SQL change real-execution gate"). A DB-touching change whose only evidence is mocked, or for which no `db-test` ran, is an **Acceptance Blocker** (`major`+; per the Verdict vocabulary below, any blocker moves the verdict off `accepted`). This gate stops an unverified DB change from reaching `release-handoff` and being pushed.
18
18
  - no new defects introduced — the diff does not break previously-working behaviour and adds no new bug (logic/off-by-one, null/empty handling, resource leaks, broken error paths)
19
19
  - scope conformance — the delivered diff stays within the approved plan's scope; flag out-of-scope edits, unrelated file changes, leftover debug/commented-out code, and unintended deletions
20
20
  - project review-rule packs (when present) — search `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`; read the matching `SKILL.md` + referenced `references/*.md` and apply their rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none found`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
@@ -31,14 +31,15 @@
31
31
  - the lead still captures `git status --short` from the injected worktree to confirm the analysis ran against the delivered work-tree state; an unexpected divergence (dirty tree outside `.okstra/`, missing worktree) is a `tool-failure`, not a silent proceed.
32
32
  - Required deliverable shape (final report, in addition to the standard sections):
33
33
  - **Source Implementation Report(s)**: the `VERIFICATION_TARGET` snapshot verbatim — verification scope, worktree path, base ref, the list of stages under verification, and one row per stage citing its originating implementation final-report (`report_path` from `consumers.jsonl`; render `(report_path unrecorded)` when absent). The lead injects this same snapshot into every analyser prompt (`**Verification scope:** / **Worktree:** / **Verification base ref:** / **Verification diff stat:**`); a worker that cannot confirm its analysis ran against that worktree's delivered diff (against `base ref`) MUST record a `tool-failure`.
34
- - **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is `accepted`.
34
+ - **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is `accepted`. **Any Acceptance Blocker therefore forces the verdict off `accepted` (to `conditional-accept` or `blocked`); the gates below cite this rule instead of restating the arithmetic.**
35
35
  - **Acceptance Blockers block** (under section 4): one row per blocker with `id`, `severity` (`critical` / `major` / `minor`), evidence (file path, log excerpt, or test output), and the recommended follow-up phase (`error-analysis` or `implementation-planning`). Empty block is acceptable and preferred — render the single line `- No acceptance blockers found.`
36
36
  - **Residual Risk block** (under section 4): risks that are not blockers but should be tracked, each with mitigation owner and a trigger that would escalate them to a blocker.
37
37
  - **Validation Evidence**: for every requirement in the originating plan or task brief, cite the artifact (commit SHA, test output, log line, MCP SELECT result) that demonstrates coverage. Paraphrased "verified" claims without an artifact are rejected.
38
- - **Read-only command log**: any pre-existing test/validation command executed during this run MUST be listed with its exact command line and exit code. No mutating commands may appear here.
38
+ - **Read-only command log**: any pre-existing test/validation command touched during this run MUST be listed with its exact command line and one honest status — `executed` (ran; carries its exit code) / `env-unavailable` (should run but cannot in this environment — missing replica DB, container, or service; carries the reason, never a faked pass) / `not-configured` (no such qa-command tier) / `rejected` (a mutating/denied token skipped, carries the denied token). A check that could not run locally is recorded as `env-unavailable` with the reason — never silently dropped and never reported as `executed` with an invented exit code. Mutating-command prohibition is the shared read-only boundary (see Non-goals); it is not restated per row.
39
39
  - **Two-tier command lookup (shared with `implementation`):** when this phase performs its own independent re-validation, the command source is exactly the same two tiers `implementation` verifiers use — Tier 1 is the originating task brief / approved plan's `validation` set, Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` under `qaCommands`. Auto-detecting tools from manifest files is forbidden; missing tiers are recorded as `qa-command not configured: <category>` rather than guessed. The `cmd` deny-list (`--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, `npm install` without `ci`, etc.) is enforced identically. NOTE: runtime fail-fast validation (`okstra_ctl.qa_commands.validate_qa_commands`) only fires at `--task-type implementation` run-prep, so this phase MUST self-check each `qaCommands` entry against the deny-list before executing it — if a denied token is present, skip the command and record it as a `Read-only command log` line `qa-command rejected (denied token: <token>): <label>`.
40
- - **Tier 3 — stage conformance scripts (whole-task union):** because this phase verifies the **integrated, merged** state, it re-runs conformance against that state rather than per-stage. Read the task-level manifest `<task_root>/qa/conformance-manifest.json` (the directory is the `TASK_QA_PATH` token) and, in **whole-task scope**, run the `runCommand` of **every** `entries[]` item against the merged worktree, refreshing each `<task_root>/qa/result-<stageKey>.json` (`{ "stageKey", "overall": "PASS"|"FAIL"|"MISSING", "ranAt", "requirements" }`). In **single-stage scope**, run only the entry whose `stageKey` matches the verified stage. An entry carrying an `exemption` or user `waiver` is NOT executed — record the skip and reason; a `waiver` becomes a `conditional-accept` condition surfaced in the section 7 Verdict (conformance left unverified by user acknowledgement). Each `runCommand` runs in the worktree cwd with `qaEnv` env (replica DB DSN / app base URL / env file) — **replica / test environment only**, never shared / staging / prod, and the same source/lockfile mutation deny-list applies (a conformance script MAY mutate only its `qaEnv` replica datastore). Interpret each result from the exit code + stdout `QA-RESULT: PASS|FAIL` (last wins) and `REQ <id>: PASS|FAIL: <reason>` lines; no `QA-RESULT` marker → `MISSING`. Any entry whose result is not `PASS` (including `MISSING` or a never-run/missing sidecar) is an **Acceptance Blocker** (`major`+), so the verdict becomes `conditional-accept` / `blocked`. This is the same gate the `validate-run.py` Tier 3 check enforces on the result sidecars.
40
+ - **Tier 3 — stage conformance scripts (whole-task union):** because this phase verifies the **integrated, merged** state, it re-runs conformance against that state rather than per-stage. Read the task-level manifest `<task_root>/qa/conformance-manifest.json` (the directory is the `TASK_QA_PATH` token) and, in **whole-task scope**, run the `runCommand` of **every** `entries[]` item against the merged worktree, refreshing each `<task_root>/qa/result-<stageKey>.json` (`{ "stageKey", "overall": "PASS"|"FAIL"|"MISSING", "ranAt", "requirements" }`). In **single-stage scope**, run only the entry whose `stageKey` matches the verified stage. An entry carrying an `exemption` or user `waiver` is NOT executed — record the skip and reason; a `waiver` becomes a `conditional-accept` condition surfaced in the section 7 Verdict (conformance left unverified by user acknowledgement). Each `runCommand` runs in the worktree cwd with `qaEnv` env (replica DB DSN / app base URL / env file) — **replica / test environment only**, never shared / staging / prod, and the same source/lockfile mutation deny-list applies (a conformance script MAY mutate only its `qaEnv` replica datastore). Interpret each result from the exit code + stdout `QA-RESULT: PASS|FAIL` (last wins) and `REQ <id>: PASS|FAIL: <reason>` lines; no `QA-RESULT` marker → `MISSING`. Any entry whose result is not `PASS` (including `MISSING` or a never-run/missing sidecar) is an **Acceptance Blocker** (`major`+). This is the same gate the `validate-run.py` Tier 3 check enforces on the result sidecars.
41
41
  - **Manual user test results**: take each item from the source implementation report's §5.7.9 Manual User Test (Draft), execute the ones reproducible in this environment (e.g. `/okstra-container-build`, which runs `okstra container up`, then the documented steps), and record `result` (`pass` / `fail` / `blocked`) + observed value in §5.8.7 (data field `finalVerification.manualUserTest`). Steps that need human-only interaction this run cannot perform are recorded as `blocked` with the reason (handed to the user), never silently skipped. A failed manual test is an Acceptance Blocker. If the draft was an exemption (`applicable=false`), reaffirm the reason in one line (`applicable=false` + `exemptionReaffirm`).
42
+ - **Could-not-verify roll-up (§5.8.9)**: the template mechanically aggregates every not-confirmed check into one scannable list — `gap` requirement-coverage rows, `not-configured` / `env-unavailable` / `rejected` command rows, and `blocked` manual tests. You do not hand-author it, but you MUST give those rows their honest status so nothing unverified hides across sections: a check silently recorded as `executed`/`covered` will not surface in the roll-up. This is okstra's answer to "say what could not be verified this run."
42
43
  - **Routing recommendation**: the next safe phase — one of `release-handoff`, `done`, `error-analysis`, `implementation-planning` — tied to the verdict and blocker list. `release-handoff` is allowed ONLY when the Verdict Token is `accepted`. `release-handoff` is additionally allowed ONLY when the verification scope (the `Verification scope:` line of the injected `VERIFICATION_TARGET` block, recorded as the report's `verificationScope` field) is `whole-task`; a `single-stage` accepted run routes to `release-handoff(stage-group)` (or `implementation` / `done`); plain `release-handoff` remains whole-task-only. Enforcement: `validators/validate-run.py` rejects a `single-stage` report whose routing cites plain `release-handoff`.
43
44
  - **Verified-row recording** (single-stage scope only): when the Verdict Token is `accepted`, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json, so a non-accepted or whole-task report is rejected at the tool layer.
44
45
  - Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
@@ -50,7 +51,7 @@
50
51
  4. **Verifier dissent preserved** — if workers reach different verdicts, the disagreement is visible in section 1.2; synthesis hides nothing.
51
52
  5. **No source-mutation audit** — scan the run's session transcripts for Edit / Write or state-mutating Bash commands that touch paths OUTSIDE `<PROJECT_ROOT>/.okstra/**` and outside the assigned run-artifact paths. Writes to worker prompts, audit sidecars, team-state, the final-report `data.json`, and rendered reports under the run directory are allowed okstra artifacts. Any source/schema/deployment mutation means the run has crossed into implementation and MUST be re-routed; do NOT silently strip the evidence.
52
53
  - Cross-verification mode:
53
- - **Acceptance critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker **acceptance devil's-advocate** pass is dispatched concurrently with the first convergence reverify round to surface candidate acceptance blockers the verifiers may have missed; candidates are verified only after convergence completes. Each candidate is verified **confirm-or-downgrade**: confirmed → an `Acceptance Blockers` row (which, since `accepted` requires zero blockers, moves the verdict to `conditional-accept` / `blocked`); unconfirmed → a `Residual Risk` row (never dropped). See `prompts/lead/convergence.md` "Acceptance critic pass (final-verification)".
54
+ - **Acceptance critic (opt-in)**: when `convergence.critic.enabled=true` (chosen via the okstra-run picker or `--critic`), a reused-worker **acceptance devil's-advocate** pass is dispatched concurrently with the first convergence reverify round to surface candidate acceptance blockers the verifiers may have missed; candidates are verified only after convergence completes. Each candidate is verified **confirm-or-downgrade**: confirmed → an `Acceptance Blockers` row; unconfirmed → a `Residual Risk` row (never dropped). See `prompts/lead/convergence.md` "Acceptance critic pass (final-verification)".
54
55
  - Non-goals:
55
56
  - proposing unrelated refactors beyond the delivered scope
56
57
  - **source code edits, follow-up bug fixes, or scope expansion** — this run renders a verdict only; defects detected here become inputs to a new `error-analysis` or `implementation-planning` run
@@ -20,6 +20,7 @@
20
20
  - **codebase-first ambiguity resolution**: any ambiguity that can be answered by `Read` / `Grep` MUST be resolved that way and recorded with file:line evidence. Only ambiguities that genuinely require a human decision are escalated as `Clarification Items` rows. Writing a clarification row for something the code already answers is a defect of this phase.
21
21
  - flag any requirement that is ambiguous, contradictory, or missing success criteria — register each one as a row in the report's `## 1. Clarification Items` table with `Blocks=approval` instead of guessing
22
22
  - read `<PROJECT_ROOT>/.okstra/glossary.md` and `<PROJECT_ROOT>/.okstra/decisions/` titles if present. Absent okstra memory files are the normal state — do not error. Treat the brief's `terminology:*` resolutions from `requirements-discovery` (if any) as authoritative; if missing, resolve any remaining fuzzy term as a `Blocks=approval` clarification row.
23
+ - **spec-settled short-circuit**: when the brief already carries a decision-complete design the reporter has confirmed (a `requirements-discovery` outcome whose approach sits behind a `[CONFIRMED …]` marker, or a verbatim reporter-approved plan in `Source Material`), do NOT re-litigate the settled decision. Present that design as the `Recommended Option`, and record the alternatives it already weighed as the remaining Option Candidate(s) tagged `(considered & rejected upstream — <why>)` rather than manufacturing fresh competing options to fill the slot. The Stage Map, validation, gates, and the ≥2-candidate shape all still apply — this short-circuits re-deliberation, not the plan's structure.
23
24
  - Primary focus areas:
24
25
  - requirement gaps
25
26
  - affected components and boundaries
@@ -41,7 +42,7 @@
41
42
  - The YAML frontmatter `approved: true|false` field is the only authorised approval gate. report-writer always emits `approved: false`. The user clears it either by (a) editing the frontmatter line to `approved: true` directly, or (b) invoking the next phase with `--approve` so the CLI flips the frontmatter on the user's behalf. `okstra_ctl.run._validate_approved_plan` reads this field and refuses entry until it is `true`.
42
43
  - Cross-verification mode:
43
44
  - Phase 5.5 finding convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker finding (requirement gap / risk / option) by re-inspecting its cited evidence; the burden of proof sits on the claim. See `prompts/lead/convergence.md` §"Adversarial Verification Mode".
44
- - §5.5.9 plan-body verification runs with an **adversarial posture** (`prompts/lead/convergence.md` §"Adversarial plan-body posture"): verifiers open and confirm every cited path / command and put the burden of proof on the plan. The gate threshold is majority-based for kinds `b`/`c`/`e`, but a single `DISAGREE` blocks on its own for the concrete, safety-critical kinds `a` (path/symbol mismatch) / `d` (rollback order) — and `f` on `P-Req-*` items. A majority also needs ≥2 participating votes, so a lone dissent whose peer returned a non-result does not block on a majority-gated kind (see `convergence.md` §"Adversarial plan-body posture").
45
+ - §5.5.9 plan-body verification runs with an **adversarial posture** (`prompts/lead/plan-body-verification.md` §"Adversarial plan-body posture"): verifiers open and confirm every cited path / command and put the burden of proof on the plan. The gate threshold is majority-based for kinds `b`/`c`/`e`, but a single `DISAGREE` blocks on its own for the concrete, safety-critical kinds `a` (path/symbol mismatch) / `d` (rollback order) — and `f` on `P-Req-*` items. A majority also needs ≥2 participating votes, so a lone dissent whose peer returned a non-result does not block on a majority-gated kind (see that contract's §"Adversarial plan-body posture").
45
46
  {{INCLUDE:_coverage-critic.md}}
46
47
  - Non-goals:
47
48
  - code-level micro-optimization unless it changes the implementation approach
@@ -65,6 +66,7 @@
65
66
  - estimated blast radius (units, configs, deployment manifests, data migrations)
66
67
  - trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
67
68
  - recommended option with rationale tied to the design principles above
69
+ - **Working Assumptions (non-blocking)**: inside the `Recommended Option` section, an `Assumptions:` labelled list recording each assumption the plan proceeds on **without** user confirmation but that does NOT block approval — a sensible default the reviewer can still veto (e.g. "assuming the existing retry policy stays; not re-tuning it here"). This is distinct from `## 1. Clarification Items`, which gates approval: anything that must be answered before coding is a clarification row, never an assumption. Omit the list only when there are genuinely none. (No new scanned heading — it lives under the existing `Recommended Option`. The `Authority & permissions` class from the shared contract is explicitly NOT recorded here — that class is suppressed, not surfaced.)
68
70
  - **Stage Map (mandatory — always emitted, even when N=1):** a table of all stages with `stage | title | depends-on | step-count | exit-contract-summary`. `depends-on` is `(none)` or a comma-separated stage number list. Stages with `depends-on (none)` can be implemented in parallel by two simultaneous `implementation` runs.
69
71
  - **Keep the table at exactly 5 columns** — do NOT add a column. `validators/validate-implementation-plan-stages.py` parses `stage | title | depends-on | step-count | exit-contract-summary` and silently skips any row that is not exactly 5 cells, so a 6th column would drop every stage and bypass S2–S11.
70
72
  - **Multi-project plans only** (the plan's work spans more than one project — see the Project-boundary partition rule below): prefix each stage's `title` cell with a `[<project>]` tag (e.g. `[okstra] Add X`) so the project each stage belongs to is readable at a glance, and add exactly one line directly under the Stage Map table — `Cross-project parallelism: <which per-project stages run in parallel, which are sequenced, and the cross-project dependency that forces each sequencing>`. Single-project plans omit both the tag and the line.
@@ -106,8 +108,8 @@
106
108
  - the YAML frontmatter MUST include the line `approved: false` (report-writer always emits the unflipped value). The user authorises the next `implementation` run by flipping it to `approved: true` (manual edit or `--approve` CLI). Do NOT recreate any `User Approval Request` body block — the validator fails reports that contain one (see `validators/validate-run.py` deprecated patterns).
107
109
  - the YAML frontmatter MUST include the line `implementation-option:` directly under `approved:` (report-writer always emits it with an **empty value**). The user selects which Option Candidate the next `implementation` run executes by filling this line with that option's name (manual edit or `--implementation-option <name>` CLI). When left empty, the `implementation` run falls back to the `Recommended Option`.
108
110
  - **the frontmatter `approved: false` line is rendered unconditionally; if the plan-body verification gate (§5.5.9) returns `blocked-by-disagreement` or `aborted-non-result`, the writer MUST keep `approved: false` and the validator refuses any report that ships with `approved: true` under such a gate result.**
109
- - every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under the implementation plan body — the unified table is the single home)
110
- - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/convergence.md` "Plan-body verification mode". The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture").
111
+ - every ambiguity flagged during pre-planning that the user must resolve before approval registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (the unified table is the single home for these — the "no separate `Open Questions` block" rule is in the shared `_common-contract.md` clarification policy)
112
+ - **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture").
111
113
  - **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the recommended option introduces against the three criteria:
112
114
  1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
113
115
  2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
@@ -127,7 +129,7 @@
127
129
  4. **Ambiguity check** — any requirement that could be read two ways must be made explicit or moved to the `## 1. Clarification Items` table as a `Blocks=approval` row.
128
130
  5. **Scope check** — if the recommended plan now spans multiple independent subsystems, recommend splitting into separate planning runs rather than shipping an oversized plan.
129
131
  6. **Review-rule preflight check** — if a project review rule pack exists, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
130
- 7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** For every §5.5.9 `planItems[]` entry whose verdicts make it `majority-disagree`, set that item's `clarificationId` to a `C-<N>` row that MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. Do NOT create a parallel `Open Questions` block under the implementation plan body — the unified table is the single home. **Enforced:** `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item has no `clarificationId`, or its `clarificationId` is dangling / points at a non-`approval` row. For `partial-consensus` and `dissent-isolated` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
132
+ 7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** For every §5.5.9 `planItems[]` entry whose verdicts make it `majority-disagree`, set that item's `clarificationId` to a `C-<N>` row that MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. **Enforced:** `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item has no `clarificationId`, or its `clarificationId` is dangling / points at a non-`approval` row. For `partial-consensus` and `dissent-isolated` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
131
133
  8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 8. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` — this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap). **Project-boundary:** confirm no stage mixes edits from two projects (different repo/`PROJECT_ROOT` or different top-level deployable module); if any stage does, split it per project. For multi-project plans, confirm each stage's `title` carries its `[<project>]` tag and the `Cross-project parallelism:` line under the table records the parallel-vs-sequenced determination (with the forcing dependency) for every project pair; for cross-repo work, confirm it is split into separate per-repo runs (required — one run structurally cannot touch another repo) rather than crammed into one task's stages.
132
134
  9. **Cross-project dependency check** — 타 repo / 다른 top-level 배포 모듈 / published 패키지에 대한 의존을 빠뜨리지 않았는지 확인한다. `dependencyMigrationRisk` 에 `kind: cross-project` 행이 있으면 매칭되는 `direction: upstream-precondition` `XP-NNN` 행이 `crossProjectDependencies` 에 있고, 그 `requiredWork` 가 추상 표현("상대 작업 완료")이 아니라 상대가 실제로 만들어야 할 구체 작업인지 reviewer 로서 다시 읽는다(validator S 가 존재만 보므로 구체성은 self-review 가 책임짐). cross-repo 작업을 한 task 의 stage 로 욱여넣지 않고 별도 run + XP 행으로 분리했는지, cross-project substance 가 `§3 Recommended Next Steps` 에 중복되지 않고 `§5.4 Cross-Project Dependencies` 에만 있는지 확인한다.
133
135
  10. **Decision-draft materialization check** — `decisionDrafts` 가 비어있지 않으면, 매칭 materialization step(`.okstra/decisions/<NNNN>-<slug>.md` 생성)이 어느 stage 의 stepwise 에 존재하는지, draft 개수와 materialization step 이 1:1 로 대응하는지 reviewer 로서 확인한다. validator 는 step 의 *존재*만 보므로 `<NNNN>-<slug>` 정확성·개수 대응은 self-review 가 책임진다.
@@ -24,7 +24,7 @@
24
24
  - Stage worktree (provisioned by `okstra-ctl` at this implementation run's prep time):
25
25
  - Status: `{{EXECUTOR_WORKTREE_STATUS}}` (one of: `created` | `reused` | `skipped-in-worktree` | `skipped-not-git`)
26
26
  - Working tree path: `{{EXECUTOR_WORKTREE_PATH}}` — when status is `created` or `reused`, this is this run's isolated stage worktree rooted at `~/.okstra/worktrees/<project>/<task-group>/<task-id>/stage-<N>/`. When skipped, this is the caller's `project_root`.
27
- - Branch: `{{EXECUTOR_WORKTREE_BRANCH}}` — empty when status is `skipped-*`. Branch name = `<work-category-prefix>-<task-id-segment>-s<N>`, globally unique via `~/.okstra/worktrees/registry.json`.
27
+ - Branch: `{{EXECUTOR_WORKTREE_BRANCH}}` — empty when status is `skipped-*`. Branch name = `<work-category-namespace>/<task-id-segment>-s<N>` (e.g. `feature/dev-9436-s2`), globally unique via `~/.okstra/worktrees/registry.json`.
28
28
  - Base ref: `{{EXECUTOR_WORKTREE_BASE_REF}}` — canonical `<base>` for every `git diff` / `git log` in this run. Independent stages start from the common anchor (the task-key worktree HEAD, fixed once at first stage entry); dependent stages start from the predecessor done commit or the verified merged task worktree head.
29
29
  - Provisioning note: `{{EXECUTOR_WORKTREE_NOTE}}`
30
30
  - Treat the working-tree path as `project_root` for the duration of this run. Do NOT mutate the caller's original checkout. cwd-sensitive Bash commands MUST be prefixed `cd {{EXECUTOR_WORKTREE_PATH}} && ` in the same Bash invocation (never `bash -lc "..."` wrappers — see executor sidecar for full rules).
@@ -33,6 +33,7 @@
33
33
  - For each open question Lead asks ONE `AskUserQuestion` with a `(Recommended)` answer drawn from a codebase-first inspection. Budget: at most 12 questions in this phase.
34
34
  - Stop conditions (OR): all questions resolved / budget exhausted / user signals proceed.
35
35
  - Lead persists the round at `<RUN_DIR>/state/phase-1.5-grilling.md` with one section per question (question / recommended / user answer) and a closing `Resolved scope` / `Resolved lenses` block. Worker prompts use this resolved block as the authoritative scope and lens definition.
36
+ - After writing the log and before Phase 4 dispatch, the lead injects its **absolute path** into every analyser prompt as the `**Phase 1.5 Grilling Log:** <absolute-path>` anchor header (see `templates/worker-prompt-preamble.md` §"Anchor headers"). This is the improvement-discovery counterpart to the `**Worktree:**` / `**Verification …:**` anchors that implementation / final-verification inject: workers read the log from this explicit path rather than re-deriving `<RUN_DIR>`. The path is byte-identical across all analysers, so it does not break the dispatch-prompt invariant.
36
37
  - Decision-tree walk (bounded):
37
38
  - When candidates branch on a structural question (e.g. "is module X meant to own this responsibility?"), resolve via `Read` / `Grep` first. Only escalate to the user inside the Phase 1.5 budget.
38
39
  - Expected output emphasis:
@@ -17,6 +17,7 @@
17
17
  - Primary focus areas:
18
18
  - classify the work as bugfix, feature, improvement, refactor, or ops
19
19
  - determine whether `error-analysis` or `implementation-planning` is the next safe step. Direct `implementation` handoff is never a valid routing target — implementation requires an approved `implementation-planning` report
20
+ - capture the reporter's **rejection criteria** — the delivered outcome that would make this work wrong or unacceptable — as a routing input. Consume it from the brief's `Desired Outcome` / `Out of Scope` / `Source Material` when present; when it is absent AND it would change the classification (e.g. bugfix vs feature) or the next-phase choice, raise it as one `decision` clarification row with `Evidence checked: none — reporter intent`. Never infer it — this is a reporter-intent signal, the mirror of improvement-discovery's `Anti-goals`
20
21
  - identify missing materials that block reliable routing
21
22
  - define task continuity expectations for long-running work under the same task key
22
23
  - capture approval or confirmation points before the next phase starts
@@ -447,6 +447,7 @@ def _runtime_fields(paths: Mapping[str, Any]) -> dict[str, str]:
447
447
  "OKSTRA_CONTEXT_LOADER_PATH": str(lead / "context-loader.md"),
448
448
  "OKSTRA_TEAM_CONTRACT_PATH": str(lead / "team-contract.md"),
449
449
  "OKSTRA_CONVERGENCE_PATH": str(lead / "convergence.md"),
450
+ "OKSTRA_PLAN_BODY_VERIFICATION_PATH": str(lead / "plan-body-verification.md"),
450
451
  "OKSTRA_REPORT_WRITER_PATH": str(lead / "report-writer.md"),
451
452
  "OKSTRA_CODING_PREFLIGHT_DIR": str(runtime_home / "prompts" / "coding-preflight"),
452
453
  }
@@ -269,6 +269,9 @@ def compute_run_paths(
269
269
  "OKSTRA_CONVERGENCE_PATH": str(
270
270
  runtime_home / "prompts" / "lead" / "convergence.md"
271
271
  ),
272
+ "OKSTRA_PLAN_BODY_VERIFICATION_PATH": str(
273
+ runtime_home / "prompts" / "lead" / "plan-body-verification.md"
274
+ ),
272
275
  "OKSTRA_REPORT_WRITER_PATH": str(
273
276
  runtime_home / "prompts" / "lead" / "report-writer.md"
274
277
  ),
@@ -1010,6 +1010,10 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
1010
1010
  "leadRuntime": _lead_runtime(ctx),
1011
1011
  "leadAdapter": _lead_adapter(ctx),
1012
1012
  "workCategory": work_category,
1013
+ # user-managed status (set-work-status CLI) — carried across re-renders
1014
+ "workStatus": existing.get("workStatus", ""),
1015
+ "workStatusUpdatedAt": existing.get("workStatusUpdatedAt", ""),
1016
+ "workStatusNote": existing.get("workStatusNote", ""),
1013
1017
  "taskBriefPath": ctx.get("BRIEF_RELATIVE_PATH", ""),
1014
1018
  "recommendedWorkers": reviewers,
1015
1019
  "relatedTasks": related_tasks,
@@ -1740,6 +1744,9 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1740
1744
  convergence_path = ctx.get("OKSTRA_CONVERGENCE_PATH") or str(
1741
1745
  home_prompts / "lead" / "convergence.md"
1742
1746
  )
1747
+ plan_body_verification_path = ctx.get("OKSTRA_PLAN_BODY_VERIFICATION_PATH") or str(
1748
+ home_prompts / "lead" / "plan-body-verification.md"
1749
+ )
1743
1750
  report_writer_path = ctx.get("OKSTRA_REPORT_WRITER_PATH") or str(
1744
1751
  home_prompts / "lead" / "report-writer.md"
1745
1752
  )
@@ -1753,6 +1760,7 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
1753
1760
  f"- Context loader: `{context_loader_path}`\n"
1754
1761
  f"- Team contract: `{team_contract_path}`\n"
1755
1762
  f"- Convergence contract: `{convergence_path}`\n"
1763
+ f"- Plan-body verification contract (implementation-planning Phase 6 sub-step only): `{plan_body_verification_path}`\n"
1756
1764
  f"- Report writer contract: `{report_writer_path}`\n"
1757
1765
  f"- Coding preflight pack: `{coding_preflight_dir}`"
1758
1766
  )