okstra 0.110.0 → 0.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.kr.md +3 -2
  2. package/README.md +3 -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 +10 -10
  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 +11 -12
  14. package/docs/kr/cli.md +6 -4
  15. package/docs/project-structure-overview.md +14 -12
  16. package/docs/task-process/README.md +4 -4
  17. package/docs/task-process/common-flow.md +8 -5
  18. package/docs/task-process/implementation.md +4 -5
  19. package/docs/task-process/release-handoff.md +3 -3
  20. package/package.json +1 -1
  21. package/runtime/BUILD.json +2 -2
  22. package/runtime/agents/workers/antigravity-worker.md +1 -0
  23. package/runtime/agents/workers/codex-worker.md +1 -0
  24. package/runtime/prompts/coding-preflight/overview.md +3 -1
  25. package/runtime/prompts/launch.template.md +1 -5
  26. package/runtime/prompts/lead/context-loader.md +2 -4
  27. package/runtime/prompts/lead/convergence.md +11 -240
  28. package/runtime/prompts/lead/okstra-lead-contract.md +70 -34
  29. package/runtime/prompts/lead/plan-body-verification.md +240 -0
  30. package/runtime/prompts/lead/report-writer.md +7 -7
  31. package/runtime/prompts/lead/team-contract.md +15 -17
  32. package/runtime/prompts/profiles/_common-contract.md +2 -38
  33. package/runtime/prompts/profiles/_implementation-diff-review.md +43 -0
  34. package/runtime/prompts/profiles/_implementation-executor.md +9 -11
  35. package/runtime/prompts/profiles/_implementation-self-check.md +11 -5
  36. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  37. package/runtime/prompts/profiles/final-verification.md +1 -1
  38. package/runtime/prompts/profiles/implementation-planning.md +2 -2
  39. package/runtime/prompts/profiles/implementation.md +1 -1
  40. package/runtime/python/okstra_ctl/codex_dispatch.py +3 -0
  41. package/runtime/python/okstra_ctl/consumers.py +4 -0
  42. package/runtime/python/okstra_ctl/handoff.py +5 -23
  43. package/runtime/python/okstra_ctl/implementation_stage.py +11 -11
  44. package/runtime/python/okstra_ctl/path_hints.py +1 -0
  45. package/runtime/python/okstra_ctl/paths.py +3 -0
  46. package/runtime/python/okstra_ctl/render.py +8 -0
  47. package/runtime/python/okstra_ctl/report_views.py +76 -26
  48. package/runtime/python/okstra_ctl/set_work_status.py +147 -0
  49. package/runtime/python/okstra_ctl/stage_targets.py +152 -0
  50. package/runtime/python/okstra_ctl/team_reconcile.py +1 -1
  51. package/runtime/python/okstra_ctl/wizard.py +47 -0
  52. package/runtime/python/okstra_project/__init__.py +2 -0
  53. package/runtime/python/okstra_project/state.py +44 -2
  54. package/runtime/skills/okstra-brief/SKILL.md +32 -176
  55. package/runtime/skills/okstra-brief/references/reporter-confirmations.md +71 -0
  56. package/runtime/skills/okstra-brief/references/tracker-recursion.md +90 -0
  57. package/runtime/skills/okstra-container-build/SKILL.md +7 -20
  58. package/runtime/skills/okstra-graphify/SKILL.md +8 -8
  59. package/runtime/skills/okstra-inspect/SKILL.md +27 -43
  60. package/runtime/skills/okstra-rollup/SKILL.md +6 -6
  61. package/runtime/skills/okstra-run/SKILL.md +27 -32
  62. package/runtime/skills/okstra-schedule/SKILL.md +64 -419
  63. package/runtime/skills/okstra-setup/SKILL.md +25 -223
  64. package/runtime/skills/okstra-setup/references/project-config.md +188 -0
  65. package/runtime/templates/reports/schedule.template.md +2 -2
  66. package/runtime/templates/worker-prompt-preamble.md +1 -1
  67. package/runtime/validators/validate-run.py +7 -7
  68. package/src/cli-registry.mjs +14 -0
  69. package/src/commands/execute/wizard.mjs +3 -1
  70. package/src/commands/inspect/set-work-status.mjs +31 -0
  71. package/src/commands/inspect/task-show.mjs +11 -31
  72. package/src/commands/lifecycle/check-project.mjs +68 -56
  73. package/src/commands/lifecycle/install.mjs +1 -0
  74. 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:
@@ -104,7 +68,7 @@ profile document.
104
68
  The labels `Ticket:` / `Kind:` / `Blocks:` / `Status:` stay English in every locale so the approval-gate parser (`clarification_items.parse_meta_cell`) reads them regardless of report language.
105
69
  Profile-specific addenda may tighten cell content but MUST NOT add, remove, rename, or reorder columns, nor change the meta-cell field order. The `ID` is `C-NNN` (3-digit zero-padded), the `Status` ∈ `{open, answered, resolved, obsolete}`, and the `Kind` / `Blocks` legal values are listed below.
106
70
  - section 1 is a **single unified table** per `final-report-template.md`. Every clarification item — whether the user must attach a file, choose between options, or supply a single number/path — is one row of that table. Do not split it into sub-sections (`1.1 추가 자료 요청` / `1.2 사용자 확인 질문` / `5.5.9 Open Questions` are removed and the validator fails reports that reintroduce them), do not create a parallel table elsewhere in the report, and do not duplicate the same item into an approval block or any other section.
107
- - each row's `Kind` column picks one of `{material, decision, data-point}`: `material` for files / snapshots / logs / screenshots the user must attach (the `User input` cell will hold a path or URL); `decision` for choices and yes/no confirmations only the user can make; `data-point` for a single number, ID, date, or short string the user can answer inline. Items that mix "yes/no + file path if yes" are one row of `Kind=material` with the combined expectation written into `Expected form`.
71
+ - each row's `Kind` column picks one of `{material, decision, data-point}`: `material` for files / snapshots / logs / screenshots the user must attach (the `User input` cell will hold a path or URL); `decision` for choices and yes/no confirmations only the user can make; `data-point` for a single number, ID, date, or short string the user can answer inline. A `decision` alternative must be a terminal choice the user can pick as-is; if acting on an alternative still requires the user to supply a concrete value (a path, string, number, or file), that value is its own `data-point` / `material` row — never phrase a data-entry action (e.g. "경로 구체화", "값 입력") as a selectable `decision` option, because the rendered `<select>` cannot capture the value the option demands. Items that mix "yes/no + file path if yes" are one row of `Kind=material` with the combined expectation written into `Expected form`.
108
72
  - each row's `Blocks` column picks one of `{approval, next-phase, none}`. `approval` is reserved for items that gate an approval action, especially the `implementation-planning` `approved:` frontmatter flip; outside `implementation-planning`, unresolved brief reporter-confirmation rows use `next-phase` instead. `next-phase` blocks the next run from starting cleanly. `none` is informational/audit-only.
109
73
  - write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the answer shape (예/아니오, 보기 중 하나, 숫자/날짜, 파일 경로, 짧은 서술 등); supply concrete option choices when applicable.
110
74
  - if a phase requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: <answer> — <rationale>; Alternatives: <options>`. When there is more than one alternative, enumerate them as `(a) … (b) …` so each renders as its own selectable option; a single alternative may be written plainly. Do **not** append a pick-one answer-space summary such as `(A / B 중 택1)` or `(… 중 택N)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
@@ -0,0 +1,43 @@
1
+ <!--
2
+ Post-write counterpart to _coding-conventions-preflight.md. The preflight gate
3
+ runs BEFORE the first Edit/Write and loads the conventions; THIS gate runs AFTER
4
+ the edits are written and BEFORE the executor's final commit, sweeping the actual
5
+ diff against those same conventions with an exhaustive file×rule matrix. It is a
6
+ prevention pass owned by the executor — it fixes findings in place, so the same
7
+ defects do not reach the verifier's Static design gate or post-merge PR review.
8
+
9
+ Same delivery paths as the other executor gates (see _implementation-executor.md
10
+ "Pre-implementation context exploration"):
11
+ - Claude executor reads this file directly after its last Edit / Write.
12
+ - codex / antigravity executor cannot read this path (outside the CLI sandbox),
13
+ so the lead appends this file's body into the persisted executor prompt at
14
+ dispatch time (see codex_dispatch.py `_implementation_executor_tail`).
15
+ The `Pre-commit diff review sweep` heading below is the literal string the CLI
16
+ wrapper's "Executor post-write gate forwarding check" greps for in the persisted
17
+ prompt (agents/workers/_cli-wrapper-template.md → Prompt Composition).
18
+ -->
19
+
20
+ # Pre-commit diff review sweep (BLOCKING — before the executor's final commit)
21
+
22
+ Lint/test green is necessary but NOT sufficient — self-mocked tests, interaction-only assertions, untruthful names, and unreadable functions all pass a green pipeline. This sweep is what keeps them out of the diff. Run it once, after the last `Edit` / `Write` of the stage, before the final commit. Fix every finding in place (you are the executor — you may edit); this is a prevention pass, not a report you hand off.
23
+
24
+ Do not scan holistically and stop when it "looks fine". Work the matrix exhaustively — the failure mode this gate exists to prevent is a real defect surviving because you eyeballed the diff instead of enumerating it.
25
+
26
+ ## Method — enumerate the diff, then walk every (file × applicable-rule) cell
27
+
28
+ 1. **List every changed file:** `git diff --name-only <stage-base>..HEAD`. That list is your worklist — cover it to the end; no sampling.
29
+ 2. **Classify each file and select its rule set** from the conventions the preflight already loaded (do NOT re-derive the rules here — read them from the routed pack):
30
+ - any source file → `clean-code.md`: truthful + standalone names, plain-English summary test, single-purpose ≤50-line functions, DRY (incl. scattered domain literals + documented forks), no magic numbers, shallow nesting, comments explain why.
31
+ - test file (`*.spec.*` / `*.test.*` / `test_*.py` / `*_test.go` …) → `clean-code.md` "Testing discipline": no self-mocking of the SUT, behavioral (outcome) assertions not interaction-only, no tautological delegation assertion.
32
+ - port / adapter / domain file, when the hexagonal overlay is loaded → `architectures/hexagonal.md`: no business logic in a port body, adapter methods are I/O only (no post-fetch filtering on domain state, no `findValid*`/`findActive*` names hiding a rule), domain objects declared under the domain boundary.
33
+ A file can hold several roles — apply every rule set that fits it.
34
+ 3. **Decide clean-or-finding for each cell.** Read the full file when a rule needs context (never judge a port/adapter/domain or a naming rule from the hunk alone).
35
+ 4. **Fix each finding in place** before the commit. When a readability finding is real, the fix is a named helper or named intermediate value — sketch the cleaner shape (a few lines) in your audit note, then apply it. When the fix is genuinely out of this stage's scope, record it as an `Out-of-plan` note instead of silently leaving it.
36
+
37
+ ## Coverage footer (BLOCKING deliverable)
38
+
39
+ End your audit-sidecar entry for this sweep with a one-line `Coverage:` footer naming each file you examined and the rule set(s) you applied to it — e.g. `Coverage: users.service.ts [names, plain-english], users.repository.adapter.ts [hexagonal, names], users.service.spec.ts [testing-discipline]`. The footer is how the completion self-check and any reviewer confirm nothing in the diff was skipped. A sweep with no footer is an unfinished sweep.
40
+
41
+ ## Graceful degradation
42
+
43
+ When the routed coding-preflight pack is unreadable (codex / antigravity runtime, or the files are absent), do NOT skip the sweep — apply the language-agnostic principles the preflight already listed (no self-mocking, behavioral assertions, truthful/standalone names, single-purpose ≤50-line functions) plus the project's `CLAUDE.md` / `CONTRIBUTING` / lint config, and record `diff-review: resource-unavailable → applied <agnostic principles + project rules>` with the Coverage footer. Never claim a resource read that did not happen.
@@ -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.
@@ -22,16 +22,19 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
22
22
  - **Coding-conventions preflight (BLOCKING — runs before the first `Edit` / `Write`, and binds the TDD loop below).** The gate body is a single source at `prompts/profiles/_coding-conventions-preflight.md` (sibling of this sidecar). Do NOT re-type that content from memory — deliver it by file so it cannot drift or be dropped:
23
23
  - **Claude executor:** Read `_coding-conventions-preflight.md` end-to-end before the first `Edit` / `Write`, then state in ONE line which conventions apply (e.g. `Applying TS + hexagonal overlay; domain at src/domains/*/domain/`).
24
24
  - **CLI executor (BLOCKING when the executor provider is `codex` or `antigravity`):** the executor CLI process does NOT share the lead's context, and it cannot Read this sidecar's directory — that path sits outside the CLI sandbox and the CLI only sees its stdin prompt, so a file reference never reaches it. The lead MUST physically append the **body** of `_coding-conventions-preflight.md` into the persisted executor prompt at dispatch time: Read the file from the same absolute directory you read this sidecar from, then `Write`/`cat` its body into the persisted prompt. Never hand-retype it. Enforcement: the CLI wrapper agents refuse an implementation-Executor dispatch whose persisted prompt lacks the literal heading `Coding-conventions preflight`, returning `<SENTINEL_PREFIX>_PREFLIGHT_MISSING` (see `agents/workers/_cli-wrapper-template.md` → Prompt Composition).
25
- - **Completion self-check (BLOCKING — runs BEFORE you claim the stage done).** The gate body is a single source at `prompts/profiles/_implementation-self-check.md` (sibling of this sidecar): the four-item completion gate (functions ≤50 lines, conventions applied, truthful names & why-comments, real build/test run). Do NOT re-type it from memory — deliver it by file so it cannot drift.
26
- - **Claude executor:** Read `_implementation-self-check.md` end-to-end before appending the `status:"done"` row, then write one confirming line per item to your audit sidecar.
27
- - **CLI executor (codex / antigravity):** the CLI process cannot Read this path (outside its sandbox). The lead appends this file's body into the persisted executor prompt at dispatch time, immediately after the preflight body (see `codex_dispatch.py` `_implementation_executor_tail`). The head-less executor honours both gates from the single prompt.
25
+ - **Pre-commit diff review sweep (BLOCKING — runs AFTER the last `Edit` / `Write`, BEFORE the final commit).** The gate body is a single source at `prompts/profiles/_implementation-diff-review.md` (sibling of this sidecar): an exhaustive file×rule sweep of the actual diff against the conventions the preflight loaded, with fix-in-place and a `Coverage:` footer. Do NOT re-type it from memory — deliver it by file so it cannot drift.
26
+ - **Claude executor:** Read `_implementation-diff-review.md` end-to-end after the stage's last edit, run the sweep over `git diff <stage-base>..HEAD`, fix findings in place, and write its `Coverage:` footer to your audit sidecar before committing.
27
+ - **CLI executor (codex / antigravity):** the CLI process cannot Read this path (outside its sandbox). The lead appends this file's body into the persisted executor prompt at dispatch time, between the preflight body and the self-check body (see `codex_dispatch.py` `_implementation_executor_tail`). The head-less executor honours all three gates from the single prompt.
28
+ - **Completion self-check (BLOCKING — runs BEFORE you claim the stage done).** The gate body is a single source at `prompts/profiles/_implementation-self-check.md` (sibling of this sidecar): the completion gate (diff-review Coverage footer present, functions ≤50 lines, conventions applied, truthful names & why-comments, real build/test run, cleanup). Do NOT re-type it from memory — deliver it by file so it cannot drift.
29
+ - **Claude executor:** Read `_implementation-self-check.md` end-to-end before appending the `status:"done"` row, then write the confirming evidence per item to your audit sidecar.
30
+ - **CLI executor (codex / antigravity):** the CLI process cannot Read this path (outside its sandbox). The lead appends this file's body into the persisted executor prompt at dispatch time, immediately after the diff-review body (see `codex_dispatch.py` `_implementation_executor_tail`). The head-less executor honours all three gates from the single prompt.
28
31
  - **Stage discipline transcription (when a preceding stage is `done`):** the lead MUST transcribe the `Stage discipline` rule (from this run's rendered profile — the INCLUDEd `_stage-discipline.md` body) verbatim into the dispatched CLI executor prompt, so a codex/antigravity executor honors the prior-stage behavior-freeze. Declaration-level — no wrapper sentinel.
29
32
  - **Non-interactive auto-execution (BLOCKING when the executor provider is `codex` or `antigravity`).** A CLI executor runs head-less (`codex exec` / antigravity equivalent) — there is no human at the keyboard. Skills loaded during the run (tdd, coding-preflight, and others) contain "get user approval", "state your plan to the user and wait", or "ask before proceeding" gates written for interactive sessions; in this run those gates are **already satisfied** by the upstream `implementation-planning` approval (the plan this stage executes was human-approved). The executor MUST NOT stop to request approval, MUST NOT end its turn after only producing a plan, and MUST carry the stage through end-to-end — RED → GREEN → refactor → per-cycle commit → `### Stage Carry Evidence`. The ONLY skill step to skip is the interactive user-approval prompt itself; every other skill rule (TDD discipline, conventions, real-IO isolation) still binds. The lead MUST transcribe this bullet verbatim into the dispatched CLI executor prompt (same reason as the preflight transcription rule above — the CLI process does not share lead context). Stopping early for approval in a head-less run is the observed empty-exit failure (exit 0, no diff): treat it as `contract-violated`.
30
33
  - **Mandatory TDD loop**: BEFORE the first `Edit` or `Write` call, the executor MUST apply a red-green-refactor loop for every code change in this run. This is required; skipping it is a `contract-violated` outcome. This governs HOW each step is executed (failing test first → minimal implementation → refactor); it does not override the approved plan's WHAT/file scope.
31
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.
32
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>`).
33
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.
34
- - **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.
35
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).
36
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`.
37
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.
@@ -43,12 +46,7 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
43
46
  - **drift rule** (this section): if a file *named in the plan* has materially drifted, refuse to edit and route back to planning. This protects trust in the approved scope.
44
47
  - **out-of-plan rule** (Allowed actions section below): if a step *requires touching a file NOT in the plan list*, that is permitted with `Out-of-plan edits` justification. This handles honest scope discovery during execution.
45
48
  - confirm the test/build commands referenced in the plan still exist and run from a clean state
46
- - **Pre-commit review-rule sweep (BLOCKING before the executor's final commit):** inspect the run diff (`git diff <stage-base>..HEAD`) against the loaded coding conventions and project review rule packs. Fix in-place before handing to verifiers when the issue is inside this stage's scope. Minimum sweep:
47
- - no new byte-identical or semantically equivalent helper stack appears in two touched services; extract a shared helper/domain module unless the approved plan explicitly justified duplication,
48
- - no test asserts equality to a direct re-invocation of the collaborator/helper being delegated to; keep literal-value or observable-state assertions,
49
- - no public method/repository function introduced by this run is left with zero in-scope callers unless it is part of a declared interface contract,
50
- - no exported/public name hides side effects or omits the entity it acts on,
51
- - no newly introduced function requires a reader to mentally name several phases that should be helper calls.
49
+ - **Pre-commit diff review sweep (BLOCKING before the executor's final commit):** run the sweep whose body is delivered via `_implementation-diff-review.md` (see the gate-delivery bullet above) over the run diff (`git diff <stage-base>..HEAD`), and fix findings in place before handing to verifiers when the issue is inside this stage's scope. That sidecar is the single source for the sweep's file×rule matrix (DRY / self-mock / behavioral-test / hexagonal / truthful-name / plain-English rules) and its `Coverage:` footer — do not maintain a second copy of the rule list here.
52
50
 
53
51
  ## Stage execution contract (this run owns one stage)
54
52
 
@@ -9,12 +9,18 @@ appends this file's body into the persisted executor prompt at dispatch time
9
9
 
10
10
  # Implementation self-check (BLOCKING — before you claim done)
11
11
 
12
- Before declaring the change complete, write one confirming line per item to your
13
- audit sidecar. If any item fails, fix it or surface the violation do not claim
14
- done on a failing item.
12
+ Before declaring the change complete, confirm each item below against the ACTUAL
13
+ diff not from memory. This is a re-derivation, not a rubber stamp: enumerate the
14
+ changed files (`git diff --name-only <stage-base>..HEAD`) and, for the per-file
15
+ items, write the confirming evidence per file, not one global "✓". If any item
16
+ fails, fix it or surface the violation — do not claim done on a failing item.
15
17
 
16
- - functions: every new/edited function ≤50 effective lines, single purpose
18
+ - diff-review done: the `Pre-commit diff review sweep` ran over the full diff and its `Coverage:` footer is in your audit sidecar (every changed file named with the rule set applied). No footer → the sweep is unfinished; do not claim done.
19
+ - functions: every new/edited function ≤50 effective lines, single purpose — cite the longest function's actual line count, not "all under 50"
17
20
  - conventions: applied the routed pack + project patterns (name which ones)
18
21
  - names & comments: names say what, comments say why; no obvious-restatement comments; no truthful-name violations
19
- - verification: ran the actual build/test (paste the command + its result)
22
+ - verification: ran the actual build/test paste the exact command line and its real exit code / output tail, never a paraphrased "tests pass"
20
23
  - cleanup: no dead or commented-out code left behind
24
+
25
+ Close with a `Self-check coverage:` line naming the files you verified the
26
+ per-file items against, so the Coverage footer above and this gate reconcile.
@@ -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`).
@@ -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`+); 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.
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.)
@@ -41,7 +41,7 @@
41
41
  - 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
42
  - Cross-verification mode:
43
43
  - 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").
44
+ - §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
45
  {{INCLUDE:_coverage-critic.md}}
46
46
  - Non-goals:
47
47
  - code-level micro-optimization unless it changes the implementation approach
@@ -107,7 +107,7 @@
107
107
  - 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
108
  - **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
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").
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/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
111
  - **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
112
  1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
113
113
  2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
@@ -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).
@@ -818,6 +818,9 @@ def _implementation_executor_tail(
818
818
  parts.append(preflight_path.read_text(encoding="utf-8"))
819
819
  else:
820
820
  parts.append("# Coding-conventions preflight\n\nApply project-local conventions before editing.")
821
+ diff_review_path = profiles / "_implementation-diff-review.md"
822
+ if diff_review_path.is_file():
823
+ parts.append(diff_review_path.read_text(encoding="utf-8"))
821
824
  selfcheck_path = profiles / "_implementation-self-check.md"
822
825
  if selfcheck_path.is_file():
823
826
  parts.append(selfcheck_path.read_text(encoding="utf-8"))
@@ -68,6 +68,10 @@ def read_stage_consumer_state(
68
68
  if recover_from_carry:
69
69
  backfill_done_from_carry(plan_run_root)
70
70
  rows = read_consumers(plan_run_root)
71
+ return stage_consumer_state_from_rows(rows)
72
+
73
+
74
+ def stage_consumer_state_from_rows(rows: List[Dict[str, Any]]) -> StageConsumerState:
71
75
  done_rows = [r for r in rows if r.get("status") == "done"]
72
76
  done_by_stage = latest_done_by_stage(rows)
73
77
  started = {
@@ -12,7 +12,7 @@ import subprocess
12
12
  from pathlib import Path
13
13
  from typing import Any, Callable, Dict, List, Optional, Tuple
14
14
 
15
- from . import consumers, worktree_registry
15
+ from . import consumers, stage_targets, worktree_registry
16
16
  from .final_report_paths import final_report_markdown_path
17
17
  from .worktree import (compute_branch_name, compute_worktree_path,
18
18
  main_worktree_path, is_dirty_excluding_okstra,
@@ -44,28 +44,10 @@ def compute_eligibility(stage_map: List[Dict[str, Any]],
44
44
  rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
45
45
  """stage 별 PR 가능 여부와 차단 사유. 의존 폐포는 선택 집합의 속성이라
46
46
  여기서는 판정하지 않는다 (assemble 의 check_dependency_closure 가 담당)."""
47
- done = consumers.latest_done_by_stage(rows)
48
- verified = consumers.verified_accepted_stages(rows)
49
- in_pr = consumers.pr_covered_stages(rows)
50
- out = []
51
- for s in stage_map:
52
- n = s["stage_number"]
53
- reasons = []
54
- if n in in_pr:
55
- reasons.append("already-in-pr")
56
- else:
57
- if n not in done:
58
- reasons.append("not-done")
59
- if n not in verified:
60
- reasons.append("not-verified-accepted")
61
- out.append({
62
- "stage": n,
63
- "depends_on": list(s["depends_on"]),
64
- "eligible": not reasons,
65
- "reasons": reasons,
66
- "head_commit": (done.get(n) or {}).get("head_commit", ""),
67
- })
68
- return out
47
+ return stage_targets.stage_lifecycle_snapshot_from_rows(
48
+ stage_map,
49
+ rows,
50
+ ).handoff_eligibility()
69
51
 
70
52
 
71
53
  def latest_whole_task_fv_accepted(project_root, project_id: str,
@@ -66,32 +66,32 @@ def select_and_provision_implementation_stage(
66
66
  """
67
67
  del task_key # The registry uses the split identity fields.
68
68
 
69
- from .consumers import backfill_done_from_carry, read_stage_consumer_state
69
+ from .consumers import backfill_done_from_carry
70
70
  from . import worktree as _worktree
71
71
  from . import worktree_registry as _reg
72
72
 
73
73
  plan_run_root = Path(inp.approved_plan_path).resolve().parents[1]
74
74
  backfill_done_from_carry(plan_run_root)
75
75
  auto_reconcile_best_effort(inp, plan_run_root)
76
- consumers = read_stage_consumer_state(plan_run_root)
77
76
  reserved_stages = _reg.list_active_stage_numbers(
78
77
  inp.project_id,
79
78
  inp.task_group,
80
79
  inp.task_id,
81
80
  )
81
+ snapshot = stage_targets.read_stage_lifecycle_snapshot(
82
+ ctx_stage_map,
83
+ plan_run_root,
84
+ reserved_stages=reserved_stages,
85
+ )
82
86
 
83
87
  try:
84
- selected = stage_targets.resolve_implementation_stage(
85
- ctx_stage_map,
86
- consumers.done_stages,
87
- inp.stage,
88
- started_stages=consumers.started_stages,
89
- reserved_stages=reserved_stages,
90
- )
88
+ selected = snapshot.resolve_implementation_stage(inp.stage)
91
89
  except stage_targets.StageTargetError as exc:
92
90
  raise _as_implementation_stage_error(exc) from exc
93
91
 
94
- concurrent_stages = sorted(reserved_stages - consumers.done_stages - {selected})
92
+ concurrent_stages = snapshot.concurrent_stage_numbers(
93
+ selected_stage=selected,
94
+ )
95
95
 
96
96
  if executor_worktree_status.startswith("skipped"):
97
97
  head = _git_out(inp.project_root, "rev-parse", "HEAD")
@@ -133,7 +133,7 @@ def select_and_provision_implementation_stage(
133
133
  try:
134
134
  stage_base = stage_targets.resolve_stage_base_commit(
135
135
  selected_stage,
136
- consumers.done_rows,
136
+ snapshot.done_rows,
137
137
  anchor_base_commit=anchor,
138
138
  candidate_base=head_sha,
139
139
  project_root=Path(task_worktree_path),
@@ -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
  ),