okstra 0.186.7 → 0.187.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 (541) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.d.mts +88 -1
  3. package/dist/cli-registry.mjs +68 -111
  4. package/dist/cli-registry.mjs.map +1 -1
  5. package/dist/commands/execute/render-bundle.mjs +0 -1
  6. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  7. package/dist/commands/execute/run.mjs +8 -3
  8. package/dist/commands/execute/run.mjs.map +1 -1
  9. package/dist/commands/lifecycle/check-project.mjs +1 -14
  10. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  11. package/dist/commands/lifecycle/config.mjs +38 -40
  12. package/dist/commands/lifecycle/config.mjs.map +1 -1
  13. package/dist/commands/lifecycle/doctor.d.mts +22 -7
  14. package/dist/commands/lifecycle/doctor.mjs +77 -49
  15. package/dist/commands/lifecycle/doctor.mjs.map +1 -1
  16. package/dist/commands/lifecycle/install.d.mts +12 -10
  17. package/dist/commands/lifecycle/install.mjs +104 -39
  18. package/dist/commands/lifecycle/install.mjs.map +1 -1
  19. package/dist/commands/lifecycle/paths.mjs +8 -3
  20. package/dist/commands/lifecycle/paths.mjs.map +1 -1
  21. package/dist/commands/lifecycle/preflight.mjs +2 -1
  22. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  23. package/dist/commands/lifecycle/setup.mjs +22 -36
  24. package/dist/commands/lifecycle/setup.mjs.map +1 -1
  25. package/dist/commands/lifecycle/uninstall.d.mts +4 -2
  26. package/dist/commands/lifecycle/uninstall.mjs +59 -15
  27. package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
  28. package/dist/commands/memory/memory.mjs +7 -1
  29. package/dist/commands/memory/memory.mjs.map +1 -1
  30. package/dist/lib/helper-scripts.d.mts +1 -1
  31. package/dist/lib/helper-scripts.mjs +10 -18
  32. package/dist/lib/helper-scripts.mjs.map +1 -1
  33. package/dist/lib/host-config.d.mts +72 -0
  34. package/dist/lib/host-config.mjs +404 -0
  35. package/dist/lib/host-config.mjs.map +1 -0
  36. package/dist/lib/host-registry-client.d.mts +2 -2
  37. package/dist/lib/host-registry-client.mjs +0 -3
  38. package/dist/lib/host-registry-client.mjs.map +1 -1
  39. package/dist/lib/install-assets.d.mts +1 -0
  40. package/dist/lib/install-assets.mjs +4 -0
  41. package/dist/lib/install-assets.mjs.map +1 -1
  42. package/dist/lib/proc.d.mts +2 -0
  43. package/dist/lib/proc.mjs +12 -0
  44. package/dist/lib/proc.mjs.map +1 -1
  45. package/dist/lib/python-command.d.mts +2 -0
  46. package/dist/lib/python-command.mjs +52 -0
  47. package/dist/lib/python-command.mjs.map +1 -0
  48. package/dist/lib/python-helper.d.mts +2 -5
  49. package/dist/lib/python-helper.mjs +3 -52
  50. package/dist/lib/python-helper.mjs.map +1 -1
  51. package/dist/lib/runtime-payload.d.mts +23 -0
  52. package/dist/lib/runtime-payload.mjs +57 -0
  53. package/dist/lib/runtime-payload.mjs.map +1 -0
  54. package/dist/lib/types.d.mts +24 -13
  55. package/docs/architecture/storage-model.md +21 -5
  56. package/docs/architecture.md +37 -36
  57. package/docs/cli.md +55 -76
  58. package/docs/coding-rules.md +295 -0
  59. package/docs/container.md +10 -36
  60. package/docs/contributor-change-matrix.md +1 -1
  61. package/docs/for-ai/skills/okstra-code-review.md +0 -1
  62. package/docs/for-ai/skills/okstra-container-build.md +8 -41
  63. package/docs/for-ai/skills/okstra-inspect.md +4 -4
  64. package/docs/for-ai/skills/okstra-manager.md +2 -2
  65. package/docs/for-ai/skills/okstra-rollup.md +0 -1
  66. package/docs/for-ai/skills/okstra-run.md +3 -3
  67. package/docs/for-ai/skills/okstra-user-response.md +0 -1
  68. package/docs/performance-improvement-plan-v2.md +1 -1
  69. package/docs/project-structure-overview.md +62 -62
  70. package/docs/task-process/README.md +3 -3
  71. package/docs/task-process/common-flow.md +1 -1
  72. package/docs/task-process/error-analysis.md +3 -3
  73. package/docs/task-process/implementation-planning.md +3 -1
  74. package/docs/task-process/release-handoff.md +1 -1
  75. package/package.json +3 -2
  76. package/runtime/BUILD.json +2 -2
  77. package/runtime/agents/workers/claude-worker.md +6 -11
  78. package/runtime/agents/workers/report-writer-worker.md +1 -1
  79. package/runtime/bin/lib/okstra/cli.sh +4 -0
  80. package/runtime/bin/lib/okstra/globals.sh +2 -0
  81. package/runtime/bin/lib/okstra/interactive.sh +26 -173
  82. package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
  83. package/runtime/bin/lib/okstra/usage.sh +3 -3
  84. package/runtime/bin/okstra-compact-reminder.sh +1 -5
  85. package/runtime/bin/okstra-error-log.py +19 -2
  86. package/runtime/bin/okstra-import-check.py +26 -0
  87. package/runtime/bin/okstra-inject-report-index.py +5 -4
  88. package/runtime/bin/okstra-provider-exec.py +22 -17
  89. package/runtime/bin/okstra-render-final-report.py +16 -6
  90. package/runtime/bin/okstra-render-report-views.py +25 -18
  91. package/runtime/bin/okstra-report-translate.py +30 -18
  92. package/runtime/bin/okstra-spawn-followups.py +4 -0
  93. package/runtime/bin/okstra-token-usage.py +3 -0
  94. package/runtime/bin/okstra.sh +4 -2
  95. package/runtime/bin/okstra_bootstrap.py +58 -0
  96. package/runtime/prompts/coding-preflight/overview.md +1 -1
  97. package/runtime/prompts/duties/planning-worker.md +1 -1
  98. package/runtime/prompts/launch.template.md +35 -9
  99. package/runtime/prompts/lead/convergence.md +96 -87
  100. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  101. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  102. package/runtime/prompts/lead/report-writer.md +34 -4
  103. package/runtime/prompts/lead/team-contract.md +4 -1
  104. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  105. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  106. package/runtime/prompts/profiles/_common-contract.md +3 -3
  107. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  108. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  109. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  110. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  111. package/runtime/prompts/profiles/error-analysis.md +4 -3
  112. package/runtime/prompts/profiles/final-verification.md +2 -2
  113. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  114. package/runtime/prompts/profiles/implementation.md +3 -2
  115. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  116. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  117. package/runtime/python/okstra_ctl/__init__.py +6 -27
  118. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  119. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  120. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  121. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +40 -22
  122. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -21
  123. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  124. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  125. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  126. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  127. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  128. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +12 -1
  129. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  130. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +215 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +408 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  139. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  140. package/runtime/python/okstra_ctl/approval_decisions.py +260 -8
  141. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  142. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  143. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  144. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  145. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  146. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  147. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  148. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  149. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  150. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  151. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  152. package/runtime/python/okstra_ctl/cmux.py +38 -31
  153. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  154. package/runtime/python/okstra_ctl/conformance.py +23 -0
  155. package/runtime/python/okstra_ctl/container.py +38 -421
  156. package/runtime/python/okstra_ctl/context_cost.py +16 -2
  157. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  158. package/runtime/python/okstra_ctl/convergence.py +101 -15
  159. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  160. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  161. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  162. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  163. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  164. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  165. package/runtime/python/okstra_ctl/dispatch_core.py +102 -25
  166. package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
  167. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  168. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  169. package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -1
  170. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  171. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  172. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  173. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  174. package/runtime/python/okstra_ctl/error_report.py +14 -0
  175. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  176. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  177. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  178. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  179. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  180. package/runtime/python/okstra_ctl/final_report_schema.py +90 -16
  181. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  182. package/runtime/python/okstra_ctl/handoff.py +24 -1
  183. package/runtime/python/okstra_ctl/ids.py +6 -16
  184. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  185. package/runtime/python/okstra_ctl/implementation_options.py +25 -11
  186. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  187. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  188. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  189. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  190. package/runtime/python/okstra_ctl/index.py +2 -2
  191. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  192. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  193. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  194. package/runtime/python/okstra_ctl/listing.py +0 -91
  195. package/runtime/python/okstra_ctl/locks.py +5 -19
  196. package/runtime/python/okstra_ctl/log_report.py +14 -0
  197. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  198. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  199. package/runtime/python/okstra_ctl/material.py +2 -2
  200. package/runtime/python/okstra_ctl/migrate.py +23 -1
  201. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  202. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  203. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  204. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  205. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  206. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  207. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  208. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  209. package/runtime/python/okstra_ctl/models.py +8 -8
  210. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  211. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  212. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  213. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  214. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  215. package/runtime/python/okstra_ctl/paths.py +76 -8
  216. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  217. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  218. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  219. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  220. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  221. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  222. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  223. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  224. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  225. package/runtime/python/okstra_ctl/recap.py +22 -1
  226. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  227. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  228. package/runtime/python/okstra_ctl/render.py +98 -32
  229. package/runtime/python/okstra_ctl/render_final_report.py +3 -7
  230. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  231. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  232. package/runtime/python/okstra_ctl/report_finalize.py +206 -15
  233. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  234. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  235. package/runtime/python/okstra_ctl/report_language.py +8 -4
  236. package/runtime/python/okstra_ctl/report_narrative.py +90 -3
  237. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  238. package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
  239. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  240. package/runtime/python/okstra_ctl/report_views.py +4 -22
  241. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  242. package/runtime/python/okstra_ctl/rollup.py +13 -0
  243. package/runtime/python/okstra_ctl/run.py +639 -138
  244. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  245. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  246. package/runtime/python/okstra_ctl/seeding.py +21 -11
  247. package/runtime/python/okstra_ctl/sequence.py +1 -1
  248. package/runtime/python/okstra_ctl/session.py +64 -14
  249. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  250. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  251. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  252. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  253. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  254. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  255. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  256. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  257. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  258. package/runtime/python/okstra_ctl/task_target.py +4 -16
  259. package/runtime/python/okstra_ctl/team.py +49 -2
  260. package/runtime/python/okstra_ctl/time_report.py +17 -2
  261. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  262. package/runtime/python/okstra_ctl/user_response.py +55 -218
  263. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  264. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  265. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  266. package/runtime/python/okstra_ctl/wizard.py +457 -92
  267. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  268. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  269. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  270. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  271. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  272. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  273. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  274. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  275. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  276. package/runtime/python/okstra_ctl/worker_runner.py +16 -9
  277. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  278. package/runtime/python/okstra_ctl/workflow.py +18 -1
  279. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  280. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  281. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  282. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  283. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  284. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  285. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  286. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  287. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  288. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  289. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  290. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  291. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  292. package/runtime/python/okstra_project/__init__.py +0 -4
  293. package/runtime/python/okstra_project/dirs.py +2 -2
  294. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  295. package/runtime/python/okstra_project/slug.py +24 -0
  296. package/runtime/python/okstra_project/state.py +22 -230
  297. package/runtime/python/okstra_token_usage/claude.py +9 -1
  298. package/runtime/python/okstra_token_usage/cli.py +3 -1
  299. package/runtime/python/okstra_token_usage/collect.py +71 -10
  300. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  301. package/runtime/python/okstra_token_usage/grok.py +1 -5
  302. package/runtime/python/okstra_token_usage/paths.py +26 -0
  303. package/runtime/python/okstra_token_usage/pricing.py +19 -7
  304. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  305. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  306. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  307. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  308. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  309. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  310. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  311. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  312. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  313. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  314. package/runtime/skills/okstra-run/SKILL.md +7 -5
  315. package/runtime/templates/report-writer-prompt-preamble.md +1 -1
  316. package/runtime/templates/reports/brief.template.md +6 -2
  317. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  318. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  319. package/runtime/templates/reports/implementation-input.template.md +7 -1
  320. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  321. package/runtime/templates/reports/quick-input.template.md +2 -0
  322. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  323. package/runtime/templates/reports/report.js +20 -3
  324. package/runtime/templates/reports/schedule.template.md +2 -0
  325. package/runtime/templates/reports/settings.template.json +0 -10
  326. package/runtime/templates/reports/task-brief.template.md +3 -0
  327. package/runtime/templates/reports/user-response.template.md +7 -3
  328. package/runtime/validators/checks/fixtures-01.py +57 -0
  329. package/runtime/validators/checks/fixtures-02.py +565 -0
  330. package/runtime/validators/checks/runners-01.py +108 -0
  331. package/runtime/validators/checks/validate-assets-01.py +60 -0
  332. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  333. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  334. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  335. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  336. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  337. package/runtime/validators/forbidden_actions.py +73 -3
  338. package/runtime/validators/lib/common.sh +5 -0
  339. package/runtime/validators/lib/fixtures.sh +7 -591
  340. package/runtime/validators/lib/paths.sh +13 -0
  341. package/runtime/validators/lib/runners.sh +6 -104
  342. package/runtime/validators/lib/summary.sh +1 -1
  343. package/runtime/validators/lib/validate-assets.sh +6 -56
  344. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  345. package/runtime/validators/lib/validate-tasks.sh +9 -294
  346. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  347. package/runtime/validators/validate-run.py +1369 -2654
  348. package/runtime/validators/validate-workflow.sh +56 -16
  349. package/runtime/validators/validate_improvement_report.py +2 -1
  350. package/runtime/validators/validate_session_conformance.py +295 -49
  351. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  352. package/dist/commands/execute/agent-prompt.mjs +0 -24
  353. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  354. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  355. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  356. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  357. package/dist/commands/execute/codex-run.d.mts +0 -3
  358. package/dist/commands/execute/codex-run.mjs +0 -62
  359. package/dist/commands/execute/codex-run.mjs.map +0 -1
  360. package/dist/commands/execute/convergence.d.mts +0 -1
  361. package/dist/commands/execute/convergence.mjs +0 -38
  362. package/dist/commands/execute/convergence.mjs.map +0 -1
  363. package/dist/commands/execute/error-log.d.mts +0 -1
  364. package/dist/commands/execute/error-log.mjs +0 -18
  365. package/dist/commands/execute/error-log.mjs.map +0 -1
  366. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  367. package/dist/commands/execute/git-reconcile.mjs +0 -30
  368. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  369. package/dist/commands/execute/handoff.d.mts +0 -1
  370. package/dist/commands/execute/handoff.mjs +0 -31
  371. package/dist/commands/execute/handoff.mjs.map +0 -1
  372. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  373. package/dist/commands/execute/incremental-carry.mjs +0 -20
  374. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  375. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  376. package/dist/commands/execute/incremental-scope.mjs +0 -29
  377. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  378. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  379. package/dist/commands/execute/integrate-stages.mjs +0 -24
  380. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  381. package/dist/commands/execute/pane-title.d.mts +0 -1
  382. package/dist/commands/execute/pane-title.mjs +0 -20
  383. package/dist/commands/execute/pane-title.mjs.map +0 -1
  384. package/dist/commands/execute/plan-items.d.mts +0 -1
  385. package/dist/commands/execute/plan-items.mjs +0 -9
  386. package/dist/commands/execute/plan-items.mjs.map +0 -1
  387. package/dist/commands/execute/plan-validate.d.mts +0 -1
  388. package/dist/commands/execute/plan-validate.mjs +0 -68
  389. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  390. package/dist/commands/execute/plan-verify.d.mts +0 -1
  391. package/dist/commands/execute/plan-verify.mjs +0 -43
  392. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  393. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  394. package/dist/commands/execute/spawn-followups.mjs +0 -22
  395. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  396. package/dist/commands/execute/team.d.mts +0 -3
  397. package/dist/commands/execute/team.mjs +0 -66
  398. package/dist/commands/execute/team.mjs.map +0 -1
  399. package/dist/commands/execute/token-usage.d.mts +0 -1
  400. package/dist/commands/execute/token-usage.mjs +0 -19
  401. package/dist/commands/execute/token-usage.mjs.map +0 -1
  402. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  403. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  404. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  405. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  406. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  407. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  408. package/dist/commands/execute/worker-state.d.mts +0 -1
  409. package/dist/commands/execute/worker-state.mjs +0 -28
  410. package/dist/commands/execute/worker-state.mjs.map +0 -1
  411. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  412. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  413. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  414. package/dist/commands/execute/worktree-status.d.mts +0 -1
  415. package/dist/commands/execute/worktree-status.mjs +0 -121
  416. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  417. package/dist/commands/inspect/code-review.d.mts +0 -1
  418. package/dist/commands/inspect/code-review.mjs +0 -32
  419. package/dist/commands/inspect/code-review.mjs.map +0 -1
  420. package/dist/commands/inspect/container.d.mts +0 -1
  421. package/dist/commands/inspect/container.mjs +0 -25
  422. package/dist/commands/inspect/container.mjs.map +0 -1
  423. package/dist/commands/inspect/context-cost.d.mts +0 -1
  424. package/dist/commands/inspect/context-cost.mjs +0 -25
  425. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  426. package/dist/commands/inspect/design-prep.d.mts +0 -1
  427. package/dist/commands/inspect/design-prep.mjs +0 -22
  428. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  429. package/dist/commands/inspect/error-report.d.mts +0 -1
  430. package/dist/commands/inspect/error-report.mjs +0 -25
  431. package/dist/commands/inspect/error-report.mjs.map +0 -1
  432. package/dist/commands/inspect/error-zip.d.mts +0 -1
  433. package/dist/commands/inspect/error-zip.mjs +0 -24
  434. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  435. package/dist/commands/inspect/log-report.d.mts +0 -1
  436. package/dist/commands/inspect/log-report.mjs +0 -26
  437. package/dist/commands/inspect/log-report.mjs.map +0 -1
  438. package/dist/commands/inspect/model-io.d.mts +0 -1
  439. package/dist/commands/inspect/model-io.mjs +0 -25
  440. package/dist/commands/inspect/model-io.mjs.map +0 -1
  441. package/dist/commands/inspect/profile-show.d.mts +0 -1
  442. package/dist/commands/inspect/profile-show.mjs +0 -28
  443. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  444. package/dist/commands/inspect/recap.d.mts +0 -1
  445. package/dist/commands/inspect/recap.mjs +0 -30
  446. package/dist/commands/inspect/recap.mjs.map +0 -1
  447. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  448. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  449. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  450. package/dist/commands/inspect/rollup.d.mts +0 -1
  451. package/dist/commands/inspect/rollup.mjs +0 -25
  452. package/dist/commands/inspect/rollup.mjs.map +0 -1
  453. package/dist/commands/inspect/run-audit.d.mts +0 -1
  454. package/dist/commands/inspect/run-audit.mjs +0 -25
  455. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  456. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  457. package/dist/commands/inspect/set-work-status.mjs +0 -29
  458. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  459. package/dist/commands/inspect/stage-map.d.mts +0 -1
  460. package/dist/commands/inspect/stage-map.mjs +0 -131
  461. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  462. package/dist/commands/inspect/task-list.d.mts +0 -1
  463. package/dist/commands/inspect/task-list.mjs +0 -149
  464. package/dist/commands/inspect/task-list.mjs.map +0 -1
  465. package/dist/commands/inspect/task-show.d.mts +0 -1
  466. package/dist/commands/inspect/task-show.mjs +0 -108
  467. package/dist/commands/inspect/task-show.mjs.map +0 -1
  468. package/dist/commands/inspect/time-report.d.mts +0 -1
  469. package/dist/commands/inspect/time-report.mjs +0 -24
  470. package/dist/commands/inspect/time-report.mjs.map +0 -1
  471. package/dist/commands/inspect/usage-report.d.mts +0 -1
  472. package/dist/commands/inspect/usage-report.mjs +0 -23
  473. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  474. package/dist/commands/inspect/user-response.d.mts +0 -1
  475. package/dist/commands/inspect/user-response.mjs +0 -35
  476. package/dist/commands/inspect/user-response.mjs.map +0 -1
  477. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  478. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  479. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  480. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  481. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  482. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  483. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  484. package/dist/commands/lifecycle/migrate.mjs +0 -30
  485. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  486. package/dist/commands/lifecycle/model.d.mts +0 -1
  487. package/dist/commands/lifecycle/model.mjs +0 -22
  488. package/dist/commands/lifecycle/model.mjs.map +0 -1
  489. package/dist/commands/manager.d.mts +0 -3
  490. package/dist/commands/manager.mjs +0 -50
  491. package/dist/commands/manager.mjs.map +0 -1
  492. package/dist/commands/report/agent-activity.d.mts +0 -1
  493. package/dist/commands/report/agent-activity.mjs +0 -20
  494. package/dist/commands/report/agent-activity.mjs.map +0 -1
  495. package/dist/commands/report/approval-decision.d.mts +0 -1
  496. package/dist/commands/report/approval-decision.mjs +0 -21
  497. package/dist/commands/report/approval-decision.mjs.map +0 -1
  498. package/dist/commands/report/design-snapshot.d.mts +0 -1
  499. package/dist/commands/report/design-snapshot.mjs +0 -19
  500. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  501. package/dist/commands/report/finalize.d.mts +0 -4
  502. package/dist/commands/report/finalize.mjs +0 -64
  503. package/dist/commands/report/finalize.mjs.map +0 -1
  504. package/dist/commands/report/inject-report-index.d.mts +0 -1
  505. package/dist/commands/report/inject-report-index.mjs +0 -21
  506. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  507. package/dist/commands/report/render-final-report.d.mts +0 -1
  508. package/dist/commands/report/render-final-report.mjs +0 -23
  509. package/dist/commands/report/render-final-report.mjs.map +0 -1
  510. package/dist/commands/report/render-views.d.mts +0 -1
  511. package/dist/commands/report/render-views.mjs +0 -27
  512. package/dist/commands/report/render-views.mjs.map +0 -1
  513. package/dist/commands/report/translate.d.mts +0 -1
  514. package/dist/commands/report/translate.mjs +0 -33
  515. package/dist/commands/report/translate.mjs.map +0 -1
  516. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  517. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  518. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  519. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  520. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  521. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  522. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  523. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  524. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  525. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  526. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  527. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  528. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  529. package/runtime/bin/okstra-central.sh +0 -152
  530. package/runtime/bin/okstra-incremental-carry.py +0 -10
  531. package/runtime/bin/okstra-incremental-scope.py +0 -10
  532. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  533. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  534. package/runtime/python/okstra_ctl/batch.py +0 -60
  535. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  536. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  537. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  538. package/runtime/python/okstra_ctl/resolver.py +0 -54
  539. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  540. package/runtime/python/okstra_ctl/tmux.py +0 -134
  541. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -8,10 +8,10 @@ roles:
8
8
  max: 5
9
9
  duty: planning-worker
10
10
  - role: critic
11
- min: 1
11
+ min: 0
12
12
  recommended: 1
13
13
  max: 1
14
- duty: acceptance-critic
14
+ duty: scope-critic
15
15
  - role: report-writer
16
16
  min: 1
17
17
  recommended: 1
@@ -44,7 +44,8 @@ roles:
44
44
  - **Ticket Tagging.** Tag every section 1–5 item with its related ticket. Use `Issue / Ticket`, fall back to Task ID, then `unknown`; comma-separate multiple tickets.
45
45
  - route by the planning input contract before analysis. A run carrying `selected-direction.json` uses the selected-direction procedure. Only a legacy rerun without that snapshot uses candidate comparison.
46
46
  - **Selected-direction planning procedure** — perform these steps in order and no others:
47
- 1. Read `selected-direction.json` and the original requirements ledger end-to-end.
47
+ 1. Read `selected-direction.json` and the original requirements ledger end-to-end. The ledger is not a separate file: it is the brief's `Expected Behavior` / `Preserved Behavior` / `Expected Outcome` sections (`EB-` / `PB-` / `EO-` ids), carried verbatim in the analysis packet's `## Task-Specific Brief Extract`.
48
+ 1a. Fill `selectedDirectionRef` from that snapshot: copy `sourceReport`, `sourceDataSha256`, and `optionId` verbatim from the file; write `snapshotPath` as the snapshot's path relative to the **task root** (`instruction-set/selected-direction.json`, never project-relative and never absolute) and `snapshotSha256` as the SHA-256 of its exact bytes. Those last two are the only ref fields the snapshot does not carry.
48
49
  2. Concretize the selected direction into files, interfaces, stages, validation, and rollback without changing its mechanism or boundaries.
49
50
  3. Link every planned file and stage bidirectionally to the original requirements.
50
51
  4. If current evidence requires changing the selected direction, emit `direction-invalidated` and stop planning.
@@ -67,8 +68,9 @@ roles:
67
68
  - A stage whose `status` is `done` is already implemented and will not be executed again. Carry its plan body forward as written; do not rewrite its steps, and do not fold its work into a new stage.
68
69
  - Every stage number in the ledger is taken. A new stage takes the next number after the highest one listed; numbers are never reused or reordered. **Not yet machine-enforced** — the validator for this rule lands with the plan-amendment feature.
69
70
  - The ledger answers two questions from two sources, and the block names both. `sourcePlan` is the plan the completed stages were actually built against; `latestPlan` is the plan the `stages` list came from and is therefore the numbering authority. When they differ, the completed work followed the former and the highest taken number comes from the latter.
70
- - A `planDivergence` entry means the two plans disagree about a stage that is already `done` — the same number naming different work, or a completed stage the latest plan no longer declares. Do not pick one of the two yourself; register it as a `Blocks=approval` clarification row and assign no new stage number until it is resolved.
71
+ - A `planDivergence` entry means one of two things: the two plans disagree about a stage that is already `done` — the same number naming different work, or a completed stage the latest plan no longer declares — or the plan the completed stages were built against could not be read at all, so that comparison never ran. Both block the same way: do not pick one of the two plans yourself; register it as a `Blocks=approval` clarification row and assign no new stage number until it is resolved. Completed stages built against an *earlier* plan are not a divergence — that is the normal shape of an amended plan and the ledger folds it silently.
71
72
  - The block is absent ONLY on a task's first planning run. Its absence then means there is no prior plan, not that no stage is done. When the ledger could not be read, the packet says so under the same heading with a reason instead of going silent — in that state, assign no new stage number and report the reason as a blocker.
73
+ - **Prior Planning Run (read before re-deriving anything):** on a re-run the packet carries a `## Prior Planning Run` block with the previous planning run's clarification rows, its Stage Map one line per stage, the findings its convergence left `contested` / `worker-unique`, and the path to the report itself. It is context, not an approved plan — the previous run did not reach approval. Use it so a stage you are not changing keeps the wording it already had and a settled question is not re-opened under a new `P-*` number; where the brief, the directive, or a clarification answer disagrees with it, they win. The block is absent on a first planning run and on a clarification re-run, which receives the same rows through `## Clarification Carry-In Extract` instead. **Enforced:** `okstra_ctl.prior_planning.build_prior_planning_summary` assembles it and `okstra_ctl.analysis_packet._prior_planning_block` renders it; nothing checks that a worker read it.
72
74
  - Primary focus areas:
73
75
  - requirement gaps
74
76
  - affected components and boundaries
@@ -88,14 +90,15 @@ roles:
88
90
  - dependency and risk visibility
89
91
  - recommended execution order
90
92
  - one `endStateCoverage` row per brief end-state id, whose `coveredBy` names the `R-NNN` row that carries it. The two tables are a chain, not duplicates: `endStateCoverage` proves no reporter requirement was dropped, `requirementCoverage` proves each one reaches a stage.
93
+ - a `blocked` `endStateCoverage` row MUST carry `blockedBy` — `kind` one of `clarification` / `finding` / `execution` / `upstream` / `not-observed`, plus a `ref` for the first two. `blocked` on its own carries all five situations at once, and only `clarification` is something a person can answer; without the field nothing downstream can tell "the reporter must decide" from "the server would not boot". A `clarification` ref must be a `C-NNN` row of this report or one carried in — the same row the approval-decision ledger holds, not an id you only named in prose. A `finding` ref is required but not resolved: it legitimately points at the approved plan or an upstream report (`VC-003`, `CA-001`). **Enforced:** `validators/validate-run.py` `_validate_end_state_blocked_by`.
91
94
  - Implementation Design Preparation (`implementation-design-prep-v1`, BLOCKING):
92
- - **Detector SSOT:** the planner MUST run the V1 detector defined by `scripts/okstra_ctl/design_surfaces.py` (`detect_design_surfaces()` over the detector's `RULES`) and MUST NOT invent or copy a second keyword list into the plan or prompt. **Enforced:** `validators/validate-run.py` `_validate_detector_coverage` reruns that detector and compares every `(stage, kind)` plus its trigger evidence.
93
- - **Exactly-once coverage:** for every detector-produced `(stage, kind)`, the planner MUST write exactly one `designSurfaceCoverage` row on that stage. **Enforced:** `validators/validate-run.py` `_validate_detector_coverage` rejects missing, duplicate, extra-detector-kind, or evidence-mismatched rows; `schemas/final-report-v2.0.schema.json` `$defs.DesignSurfaceCoverage` enforces the row shape.
95
+ - **Detector SSOT:** the planner MUST run the V1 detector defined by `scripts/okstra_ctl/design_surfaces.py` (`detect_design_surfaces()` over the detector's `RULES`) and MUST NOT invent or copy a second keyword list into the plan or prompt. **Enforced:** `scripts/okstra_ctl/report_projections.py` `project_design` reruns that detector against the plan and raises `ReportProjectionError` when the snapshot's surfaces do not match it, so a hand-authored keyword list cannot survive projection.
96
+ - **Exactly-once coverage:** for every detector-produced `(stage, kind)`, exactly one `designSurfaceCoverage` row exists on that stage. **Enforced:** the rows are written once by `scripts/okstra_ctl/design_snapshot.py` `build_design_snapshot` (one per detected trigger, carrying its evidence) and `scripts/okstra_ctl/report_projections.py` `project_design` refuses any snapshot whose `(stage, kind)` set or trigger evidence differs from a fresh detector run — that is where a missing, duplicated, or evidence-drifted row stops; `schemas/final-report-v2.0.schema.json` `$defs.DesignSurfaceCoverage` enforces the row shape.
94
97
  - **Disposition:** the design-surface detector snapshot uses `inline-contract` only when the stage already states the kind-specific minimum implementation contract; otherwise it uses `prep-item`. `not-applicable` is legal only with a concrete rationale consistent with the stage action. **Enforced:** `scripts/okstra_ctl/report_projections.py::project_design`, `schemas/final-report-v3.0.schema.json`, and `P-Prep-S<stage>-<kind>` verification.
95
- - **AI-prepared proposal:** every referenced PREP item MUST record `kind`, `stageRefs`, `need`, evidence-cited `knownFacts`, `openQuestions`, a concrete evidence-backed `aiProposal` (`summary`, `details`, `assumptions`, `evidence`, `confidence`), `humanConfirmation`, explicit `status`, and the safest reversible default available. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.DesignPrepItem` / `$defs.DesignPrepProposal` enforce required fields, `validators/validate-run.py` `_validate_prep_references` enforces the bidirectional stage/kind link, and `prompts/lead/plan-body-verification.md` rejects empty or non-implementable proposals.
98
+ - **AI-prepared proposal:** every referenced PREP item MUST record `kind`, `stageRefs`, `need`, evidence-cited `knownFacts`, `openQuestions`, a concrete evidence-backed `aiProposal` (`summary`, `details`, `assumptions`, `evidence`, `confidence`), `humanConfirmation`, explicit `status`, and the safest reversible default available. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.DesignPrepItem` / `$defs.DesignPrepProposal` enforce required fields; the stage ↔ PREP link is not checked afterwards because it is not authored — `scripts/okstra_ctl/design_snapshot.py` `build_design_snapshot` emits each coverage row's `prepItemId` and the matching item's `stageRefs` in the same pass, so the two sides cannot disagree. `prompts/lead/plan-body-verification.md` rejects empty or non-implementable proposals.
96
99
  - **Status choice:** prefer `provisional` with a `workingAssumption`, concrete `guardrails`, `reviewAt`, `ifStillOpen`, and canonical `requestPath`; use `blocked` only for business policy, external authority, a destructive migration decision, or the absence of any safe reversible assumption. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.DesignPrepItem` enforces state-specific fields, `validators/validate-run.py` `_validate_design_prep_states` enforces confirmation/request invariants, and `prompts/lead/plan-body-verification.md` judges whether the disposition is justified. A declared `blocked` status does not by itself fail plan-body verification.
97
- - **External-reality anchoring (`external-interface` / `transformation-mapping` surfaces):** these two detector kinds are correct only against data whose shape lives *outside this repository* (a third-party response body / external payload format). For such a surface the referenced PREP item's `aiProposal` MUST derive the assumed shape — selectors, field paths, response structure — from a **captured real sample** and cite it in `knownFacts` / `evidence` (source + capture time); a shape invented from internal reasoning and marked `confidence: high` is the disallowed move, because a plan built on an assumed shape yields an implementation whose parser and fixture only ever agree with each other. When the brief supplies no sample and none is capturable at plan time, the item MUST stay `provisional` with a `workingAssumption` that the external shape is unverified against reality, a `guardrails` line forbidding the implementation from presenting a synthetic-fixture green run as reality-verified, and an `ifStillOpen` that routes to user confirmation against real data — it MUST NOT be dispositioned `inline-contract` / settled. **Enforced (semantic):** the surface's presence is machine-checked by `_validate_detector_coverage`; whether its proposal's evidence is genuinely external is judged by the §5.5.9 `P-Prep-S<stage>-<kind>` round (this phase runs it adversarially).
98
- - **Planner-only test surface:** add `manual-user-test` only when a test prerequisite changes the implementation interface or acceptance contract; the V1 detector never emits it. **Enforced:** `validators/validate-run.py` `_validate_detector_coverage` rejects detector-produced `manual-user-test`, `schemas/final-report-v2.0.schema.json` permits its planner-authored shape, and `prompts/lead/plan-body-verification.md` verifies the stage-action rationale.
100
+ - **External-reality anchoring (`external-interface` / `transformation-mapping` surfaces):** these two detector kinds are correct only against data whose shape lives *outside this repository* (a third-party response body / external payload format). For such a surface the referenced PREP item's `aiProposal` MUST derive the assumed shape — selectors, field paths, response structure — from a **captured real sample** and cite it in `knownFacts` / `evidence` (source + capture time); a shape invented from internal reasoning and marked `confidence: high` is the disallowed move, because a plan built on an assumed shape yields an implementation whose parser and fixture only ever agree with each other. When the brief supplies no sample and none is capturable at plan time, the item MUST stay `provisional` with a `workingAssumption` that the external shape is unverified against reality, a `guardrails` line forbidding the implementation from presenting a synthetic-fixture green run as reality-verified, and an `ifStillOpen` that routes to user confirmation against real data — it MUST NOT be dispositioned `inline-contract` / settled. **Enforced (semantic):** the surface's presence is machine-checked by `scripts/okstra_ctl/report_projections.py` `project_design`; whether its proposal's evidence is genuinely external is judged by the §5.5.9 `P-Prep-S<stage>-<kind>` round (this phase runs it adversarially).
101
+ - **Planner-only test surface:** add `manual-user-test` only when a test prerequisite changes the implementation interface or acceptance contract; the V1 detector never emits it. **Enforced:** the detector's `RULES` in `scripts/okstra_ctl/design_surfaces.py` contain no `manual-user-test` rule, so every such row is planner-authored by construction and none can arrive through the detector snapshot; `schemas/final-report-v2.0.schema.json` permits its planner-authored shape, and `prompts/lead/plan-body-verification.md` verifies the stage-action rationale.
99
102
  - **Trivial task:** when the detector returns no surfaces, its input uses `designPreparation.mode: no-design-inputs`, an empty `items` array, and a concrete reason tied to the plan. **Enforced:** `schemas/final-report-v3.0.schema.json` and `scripts/okstra_ctl/report_assembly.py`.
100
103
  - Approval gate (phase-specific addendum to shared authority rule):
101
104
  - The report record `frontmatter.approved` field is the only authorised approval gate. report-writer always emits `false`. The user clears it by invoking the next phase with `--approve`, or by confirming approval in the in-session wizard. Editing the full reading copy does not approve the plan. `okstra_ctl.run._validate_approved_plan` reads this field and refuses entry until it is `true`.
@@ -103,8 +106,8 @@ roles:
103
106
  - 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 / plan item) by re-inspecting its cited evidence; the burden of proof sits on the claim. See `prompts/lead/convergence.md` §"Adversarial Verification Mode".
104
107
  - §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 kind `a` (path/symbol mismatch) — and `f` on `P-Req-*` items. `P-Var-*` items are excepted from the kind-`a` exception: a variation-point defect takes a majority. Rollback ordering (`d`) is advisory and never blocks the gate — a rollback is executed by a human, not by okstra's workers or verifiers. 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").
105
108
  - **Incremental re-verification scope (clarification re-runs):** when the lead's `okstra incremental-scope` decision is `mode == "incremental"` (procedure in `prompts/launch.template.md` §"Clarification Response Carried In"), workers re-analyze ONLY the stages listed in `reverify_stages` (the downstream closure of the impacted stages). Workers MUST NOT re-open, re-score, or re-judge any stage in `carry_stages` — those stages' prior plan-item verdicts are carried forward verbatim, and a worker never overwrites a carried verdict with its own judgement. When the decision is `mode == "full"` (the default), every stage is re-analyzed as usual.
106
- - **Single incremental-scope decision:** the lead calls `okstra incremental-scope` once the inputs are complete, passing the answered `C-NNN` ids through `--answered-clarifications`, changed design-preparation IDs through `--prep-items`, and any lead-resolved stage numbers through `--impacted`; the CLI unions all three before applying the existing dependency closure and cutoff. The clarification ids are resolved to stages by the CLI from the prior report's own `planItems[].clarificationId` and `blocked C-NNN` coverage links — the lead does not map answers to stage numbers. An answer that changes the selected planning payload, Stage Map, or execution approach is not a local impact: pass `--full-reason`, which is the only structural path that still forces `mode == "full"`. A clarification id that traces to no stage returns `mode == "unresolved"` — ask the user for stage numbers and call again with `--impacted`; do not treat it as full and do not silently drop the id. Unknown PREP IDs or invalid `stageRefs` still return an explicit full decision instead of being guessed. When the wizard pin is `auto` and the CLI returns `mode == "incremental"`, keep it — do not upgrade to full. When the user pinned a scope at the wizard (`REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` in `prompts/launch.template.md` §"Clarification Response Carried In" step 0), that pin is an input to this same call — `full` supplies the `--full-reason`, and pinned stage numbers join `--impacted` — never a bypass of the CLI's closure and cutoff.
107
- - **Stage-aware carry:** for an incremental decision, the report writer copies each `carry_stages` stage row unchanged into its narrative. After plan-item seeding, pass the decision's `carry_stages` and `reverify_stages` CSVs unchanged to `okstra incremental-carry --cur-narrative ... --state ... --out-state ...`. The helper rejects a changed or missing carried stage and copies prior `P-Step-*` / `P-Prep-*` verdicts for `carry_stages`, plus unchanged `P-Val-*` / `P-Req-*` / `P-Rb-*` rows whose extract `contentHash` still matches. It rewrites `dispatchQueue` and the sibling `plan-items-*.json` so the next prompt does not re-score a carried checklist row. Overlap, omissions, and canonical conflicts return `CarryError`. On that error, discard the partial state and run full re-verification.
109
+ - **Single incremental-scope decision:** the lead calls `okstra incremental-scope` once the inputs are complete, passing the answered `C-NNN` ids through `--answered-clarifications`, changed design-preparation IDs through `--prep-items`, and any lead-resolved stage numbers through `--impacted`; the CLI unions all three before applying the existing dependency closure and cutoff. The clarification ids are resolved to stages by the CLI from the prior report's own `planItems[].clarificationId` and `blocked C-NNN` coverage links — the lead does not map answers to stage numbers. An answer that changes the selected planning payload, Stage Map, or execution approach is not a local impact: pass `--full-reason`, which is the only structural path that still forces `mode == "full"`. A clarification id that traces to no stage returns `mode == "unresolved"` — ask the user for stage numbers and call again with `--impacted`; do not treat it as full and do not silently drop the id. **When this run only ADDS stages** — every prior stage is `done` and none of them is being re-opened — there are no stage numbers to give: pass `--carry-all-reason` instead. Pinning any prior stage as impacted makes `incremental-carry` demand it from the current snapshot, which no longer holds it (a `done` stage is not in this run's narrative), so that path has no answer that works. `--carry-all-reason` still applies the base-ref check: if the branch moved, the decision degrades to `full` because the prior stages are no longer "as written". Unknown PREP IDs or invalid `stageRefs` still return an explicit full decision instead of being guessed. When the wizard pin is `auto` and the CLI returns `mode == "incremental"`, keep it — do not upgrade to full. When the user pinned a scope at the wizard (`REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` in `prompts/launch.template.md` §"Clarification Response Carried In" step 0), that pin is an input to this same call — `full` supplies the `--full-reason`, `carry-all` supplies the `--carry-all-reason`, and pinned stage numbers join `--impacted` — never a bypass of the CLI's closure and cutoff.
110
+ - **Stage-aware carry:** for an incremental decision, `okstra incremental-scope` writes the decision to the record this run's manifest names in `incrementalDecisionPath`. The report writer's duty to copy each `carry_stages` stage row unchanged is not stated here — it is generated into that writer's own authoring contract from the same record, with this run's stage numbers in it (`okstra_ctl.report_synthesis_packet.ReportSynthesisPacket._carry_instructions`). After plan-item seeding, run `okstra incremental-carry --cur-narrative ... --state ... --out-state ...` against that record. The helper rejects a changed or missing carried stage and copies prior `P-Step-*` / `P-Prep-*` verdicts for `carry_stages`, plus unchanged `P-Val-*` / `P-Req-*` / `P-Rb-*` rows whose extract `contentHash` still matches. It rewrites `dispatchQueue` and the sibling `plan-items-*.json` so the next prompt does not re-score a carried checklist row. Overlap, omissions, and canonical conflicts return `CarryError`. On that error, discard the partial state and run full re-verification.
108
111
  {{INCLUDE:_coverage-critic.md}}
109
112
  - Non-goals:
110
113
  - code-level micro-optimization unless it changes the implementation approach
@@ -115,16 +118,17 @@ roles:
115
118
  - writing artifacts anywhere except `<PROJECT_ROOT>/.okstra/` — the run's `reports/` directory is the canonical location for this phase
116
119
  - Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
117
120
  {{INCLUDE:_clarification-recommendation.md}}
118
- - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above. **Enforced:** `validators/validate-run.py` `_validate_clarification_evidence_note` requires the note to exist and to be either a concrete `<path>:<line>` or the `none — <reason>` form. The check proves the note exists, not that the lookup happened — a false `none` is caught by review, but it is now an attributable claim rather than a silent omission.
121
+ - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above. **Not machine-checked.** The note's presence used to be checked and no longer is: a check on the literal `Evidence checked:` could only prove the string was typed, never that the lookup happened, and it became one of the most-fired failures in the validator without catching a single false `none`. The note is still required of you — it turns "nobody looked" into an attributable claim the §5.5.9 round and the user can both read — but the thing that catches a wrong one is review, which was always true.
119
122
  - **`Blocks=approval` admissibility (scope boundary).** Requirement Coverage has an admissibility test and Clarification rows did not, so anything at all could acquire approval-blocking force — a prose inconsistency inside a discarded plan detail blocked a plan with the same weight as a business decision. A row earns `Blocks=approval` only when, **absent an answer, the `implementation` run would produce wrong or unsafe code**. Apply the test to the row's consequence, not its topic:
120
123
  - admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
121
124
  - NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything outside the chosen realization, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
122
125
  - A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
126
+ - **Backtrace at authoring (`Blocks=approval` rows).** Every approval blocker this run opens must be traceable to the plan by the time the report is assembled, through one of the two link shapes `okstra incremental-scope` reads: an activity citing the row's `C-NNN` in `clarificationRefs` and the affected `P-*` ids in `planItemIds` (assembly derives the plan items' `clarificationRefs` from exactly this — a row opened before plan items existed gets its backtrace on a later activity once the Stage Map is written), or the blocked requirement's coverage row carrying `blocked C-NNN`. An unlinked id cannot auto-narrow the answered re-run: `incremental-scope` returns `mode == "unresolved"` and the user must name stage numbers by hand. The plan-body promotion path (step 8) already records these links; this rule extends the same obligation to rows raised during planning itself — the observed unlinked rows were all of that class. A genuinely plan-wide blocker links the plan-wide items it judges (`P-Dir-1`, `P-Dep-*`); it still cannot narrow, and that is the honest answer for it. **Enforced (advisory):** `validators/validate-run.py` `_validate_approval_clarification_backtrace` fails an open approval blocker with no link or a link resolving to no stage.
123
127
  - Deliverable completeness contract (BLOCKING — the schema checks data keys, not heading strings):
124
128
  - For a selected-direction plan, the plan-ready schema branch requires `planningContract`, `outcome`, `selectedDirectionRef`, `directionRealization`, `stageMap`, `stages`, `designPreparation`, `dependencyMigrationRisk`, `validationChecklist`, `rollbackStrategy`, `requirementCoverage`, `coverageSummary`, `variationPointAnalysis`, and `planBodyVerification`. Its `direction-invalidated` branch contains no execution fields.
125
129
  - Each `stages[]` entry requires `stage`, `title`, `sliceValue`, `acceptance`, `carryIn`, `stepwiseExecution` (1–6 rows), `exitContract`, and `stageValidation`. Each `stageMap[]` row requires `stage`, `title`, `dependsOn`, `stepCount`, `exitContractSummary`.
126
130
  - Beyond the schema, `validators/validate-run.py` reads the same data.json for `_validate_planning_conformance_declared`, `_validate_end_state_coverage`, `_validate_requirement_provenance`, `_validate_stage_has_requirement`, and `_validate_plan_body_state_file`. These run for every planning report regardless of schema version.
127
- - **Do not chase English heading substrings.** `PLANNING_REQUIRED_SECTIONS` and the Markdown scan in `collect_validation_errors` live inside `validate_phase_boundary`, which returns immediately when `schemaVersion == "2.0"` — they gate historical v1 Markdown only. The v2 full reading copy template renders nine headings and serialises the plan as JSON beneath them, so those substrings cannot appear, and a report is not defective for lacking them.
131
+ - **Do not chase English heading substrings.** The Markdown heading scan (`collect_validation_errors`) is the fallback branch of `collect_plan_errors` in `validators/validate-implementation-plan-stages.py` and runs only for a report carrying no `implementationPlanning` data — historical v1 Markdown. A v2 report is validated from its data (`collect_data_validation_errors`, same file). The v2 full reading copy template renders nine headings and serialises the plan as JSON beneath them, so the v1 heading substrings cannot appear, and a report is not defective for lacking them.
128
132
  - Per-stage vertical slice and TDD contract (BLOCKING — enforced on the data, not on heading tokens):
129
133
  - Every stage declares `sliceValue`, `acceptance`, and the three cases `testCaseSuccess` / `testCaseBoundary` / `testCaseFailure` — happy path, edge/boundary input, failure input. **Enforced:** the v2 schema's `if not tddExemption then require` conditional on `ImplementationPlanStage`.
130
134
  - The first `stepwiseExecution` row's `action` starts with `RED:` and its `expected` reads FAIL; some later row's `action` starts with `GREEN:` and its `expected` reads PASS. **Enforced (S10c):** `collect_data_validation_errors` in `validators/validate-implementation-plan-stages.py`, run from `validate-run.py` `_append_stage_data_failures`.
@@ -136,7 +140,7 @@ roles:
136
140
  - Legacy candidate-comparison requires at least two implementation options. **Each option must include**:
137
141
  - **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
138
142
  - **Two-tier change description.** Each `fileStructure` row carries `summary` **and** optional `details`, and they are not interchangeable:
139
- - `summary` — one plain-language sentence a reviewer who has never opened this file can follow: what changes and why it is needed. Name behaviour and domain nouns, not identifiers. No function/type/variable names, no call chains, no line numbers, no conditional logic spelled out. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.OptionCandidate.fileStructure.items.summary` caps it at 120 characters, so a dense identifier dump fails schema validation.
143
+ - `summary` — one plain-language sentence a reviewer who has never opened this file can follow: what changes and why it is needed. Name behaviour and domain nouns, not identifiers. No function/type/variable names, no call chains, no line numbers, no conditional logic spelled out. Keep it to one sentence — there is no validator on its length, so the split between `summary` and `details` is the thing to get right, not a character count.
140
144
  - `details` — the technical specifics that used to be crammed into `summary`: exact symbol names, signatures, comparison semantics, line ranges, enum members. Omit the field when there is nothing beyond the summary.
141
145
  - Bad `summary` (identifier dump, no readable claim): `Widen the return of retrieveFontFamilyGroupWithFontVersionId to also yield the matched FontFamily (own key and enabled flag) and the matched Font.status.`
142
146
  - Good — `summary`: `Carry enough information out of the font lookup to tell whether the matched family is switched off.` / `details`: `Add the matched FontFamily (own key + enabled) and Font.status to the return of retrieveFontFamilyGroupWithFontVersionId. Both are already in scope in the :102-104 loop and dropped at return.`
@@ -169,7 +173,7 @@ roles:
169
173
  - **Clean-tree assertions use `okstra worktree-status --check-clean`.** A bare `git status --porcelain` is never empty there, so an assertion built on one fails on okstra's scaffolding rather than on the stage's work. The okstra command asks the same question over source paths only and exits 1 when dirty, so it stands alone as a step's assertion: `okstra worktree-status --check-clean`. Validator S13 rejects the bare form. Do not add a `git tag stage-<N>-exit` to the step — okstra writes that tag itself when it settles the stage, at the commit the carry evidence records, and a step that tags mid-stage puts it on an earlier commit.
170
174
  - **Never read an `.okstra/` artifact back out of a git object.** `.okstra/**` is gitignored and never committed — the executor aborts a commit that stages an ignored path and the verifier reports a committed `.okstra` path as a branch defect — so `git cat-file -e <tag>:.okstra/…`, `git show <tag>:.okstra/…`, and every variant of that read can never resolve, at any tag, in any stage. A later stage that needs a QA artifact reads it from the working tree or receives it through the carry sidecar / verifier result; do not design a stage contract around one being reachable from a tag. Validator S12 rejects the read.
171
175
  - **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
172
- - `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — declare that a Tier3 verification script will prove this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. This phase emits the line and the `requires` set only. Do NOT write `<task_root>/qa/scripts/stage-<N>.*` and do NOT add a `runCommand` or `conformance-manifest.json` entry here — the matching `implementation` stage run creates the script file and the manifest `runCommand`. A plan that declares tests with no script file on disk is valid at this gate.
176
+ - `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — declare that a Tier3 verification script will prove this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. This phase emits the line and the `requires` set only. Do NOT write `<task_root>/qa/scripts/stage-<N>.*` and do NOT add a `runCommand` or `conformance-manifest.json` entry here — the matching `implementation` stage run creates the script file and the manifest `runCommand`. A plan that declares tests with no script file on disk is valid at this gate. The data.json `conformanceTests` value carries only the remainder after the `Conformance tests: stage-<N> — ` prefix — never the `stage-<N> — ` label itself (report assembly strips a leftover label at publication, and the implementation entry gate rejects one).
173
177
  - `Conformance exemption: <reason>` — only for stages that touch no db/io/http/external surface, or where unit tests fully cover the increment. Exemption stays a planning declaration; do not move it to implementation. (If the eventual `implementation` diff actually touches one of those surfaces, `validate-run.py`'s diff-surface cross-check is BLOCKING — an exemption cannot hide a real db/io/http/external change.)
174
178
  - **External QA outcome guideline:** after satisfying the S11 declaration above, a line whose `requires` contains
175
179
  `db`, `http`, or `external` should name those capabilities here so the later `runCommand` can be written against them.
@@ -180,8 +184,9 @@ roles:
180
184
  Layout split (the implementer writes these, not this phase): executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` accepts a well-formed `Conformance tests:` line even when the script file and manifest entry are absent (malformed `requires` still fails); the matching `implementation` stage run that inherited `Conformance tests:` fails closed when the script file is missing (`_validate_conformance`); and the manifest JSON structure — including each entry's `script` living under `qa/scripts/` and a `runCommand` that does not change cwd — is enforced by `validate_conformance_manifest` when the implementer writes the entry.
181
185
  - `### Stage Exit Contract` — predicted added/modified files, newly exposed identifiers/types/endpoints, downstream-usable resources.
182
186
  - `### Stage Validation` — pre / mid / post exact commands or observable outcomes for this stage only.
187
+ - **Run-executable only (BLOCKING).** A `validationChecklist` row that carries `stageRefs` gates that stage's carry, so it MUST be executable by the implementation run itself — no deployment, no manual walk-through, no observation "by hand", no credentials the run does not hold. The implementation phase forbids deploys and holds no deploy credentials, so such a row can never pass and blocks a stage with zero code defects (observed: dev-10341 VC-013/VC-014). Manual or deployed-environment verification belongs in the brief's `External Gates` and final-verification's user-owned external QA — record it there without `stageRefs`. **Enforced:** `okstra_ctl.implementation_direction.validate_selected_direction_plan` (`_stage_validation_executability_errors`) rejects stage-gating rows whose observation text requires a manual or deployed-environment step.
183
188
  - **Dependency precondition (stages that run the project toolchain).** The planning worktree is created without installed dependencies, so a stage whose steps call `npm` / `yarn` / `pytest` / `cargo` / equivalent cannot have those commands succeed at plan time — they exit `127`, not RED/GREEN. Declare the install **once** as a `phase: pre` row in `### Validation Checklist` (e.g. `VC-008 — the implementation run's stage worktree has workspace dependencies installed`) and have every such stage's `Stage Validation` cite that `VC-NNN` in its `pre:` line. Do not repeat the install commands per stage, and do not silently assume the tooling is present: a plan that never states the precondition produces steps whose commands never resolve, which the §5.5.9 round then reports as unverifiable. **Enforced (advisory):** `validators/validate-run.py` `_detect_missing_dependency_precondition` warns when a toolchain-invoking stage cites no `VC-NNN`, or cites one that is not `phase: pre`. Whether the cited row genuinely covers dependencies is a §5.5.9 judgement, not a machine check. Detection uses the token allowlist in `scripts/okstra_ctl/build_tools.py`; a project overrides it with `buildToolTokens` in `.okstra/project.json`.
184
- - **Vertical-slice-first partition rule (1st-class):** the grouping anchor is a **thin end-to-end vertical slice** — one stage delivers a single user-observable increment, crossing whatever layers are needed (data → service → API → UI) to make that one increment work. File/module proximity is demoted to the **intra-slice grouping rule**: within a slice, keep steps touching the same file/directory/module together so the diff, PR, and rollback unit stay cohesive. **Horizontal layer-splitting is forbidden** — never carve "the DB layer" into one stage and "the service layer" into the next; that produces stages that ship no standalone user value. A stage is split ONLY when (a) a real `depends-on` data/contract dependency exists, (b) effective steps would exceed 8, or (c) it is a distinct vertical slice (a different user-value increment). Maximising the number of parallel stages is NOT a reason to split — parallelism is an emergent property of independent stages, never a partitioning goal.
189
+ - **Vertical-slice-first partition rule (1st-class):** the grouping anchor is a **thin end-to-end vertical slice** — one stage delivers a single user-observable increment, crossing whatever layers are needed (data → service → API → UI) to make that one increment work. File/module proximity is demoted to the **intra-slice grouping rule**: within a slice, keep steps touching the same file/directory/module together so the diff, PR, and rollback unit stay cohesive. **Horizontal layer-splitting is forbidden** — never carve "the DB layer" into one stage and "the service layer" into the next; that produces stages that ship no standalone user value. A stage is split ONLY when (a) a real `depends-on` data/contract dependency exists, (b) effective steps would exceed 8, or (c) it is a distinct vertical slice (a different user-value increment). Maximising the number of parallel stages is NOT a reason to split — parallelism is an emergent property of independent stages, never a partitioning goal. **Config propagation is not a slice (BLOCKING):** a dependent stage whose planned paths are all configuration/CI files (`Dockerfile`, `docker-compose*`, `*.yml` / `*.yaml`, `.env*`) merely propagates a value another stage introduced — it ships no user-value increment of its own. Fold that work into the introducing stage as its own step(s) and separate it by **commits**, never by stages: each extra stage costs a full implementation run (executor + verifiers, 30min+ observed) for a few config lines. A first stage with no dependency is exempt — a task whose whole scope is configuration is legitimate. **Enforced:** `okstra_ctl.implementation_direction.validate_selected_direction_plan` (`_micro_stage_fold_errors`).
185
190
  - **Project-boundary partition rule (hard boundary):** a *project* boundary is either (a) a different repository / `PROJECT_ROOT`, or (b) a different top-level independently-deployable module within one repo. A stage maps to a single worktree on one repo/branch, so **no stage may contain edits belonging to more than one project** — this is a hard split that overrides the ≤8-step merging allowance; never co-locate two projects' changes in one stage to save a stage. Two cases:
186
191
  - **Same repo, different modules** — put each project/module's increment in its own stage. Their file sets are disjoint, so they default to `depends-on (none)` and run in parallel; add a `depends-on` link ONLY when a real cross-module contract / shared-schema / deploy-order dependency exists.
187
192
  - **Different repos** — a single okstra task **cannot** span repos: every stage worktree is a `git worktree add` in one repo's main checkout (one `<project-id>`), the run-index / manifests / registry are keyed to that one project, and the edit allowlist only covers that project's tree plus `~/.okstra/worktrees/**`. This is a structural limit, not a style preference. Therefore cross-repo work MUST be split into **a separate okstra run per repo** — never modelled as stages of one task. State, in the Stage Map `Cross-project parallelism:` line, whether those per-repo runs can proceed in parallel. To avoid re-deriving shared analysis, the second repo's run should consume the first run's relevant plan/decision artifacts as brief Source Material (see the cross-project dependency rows below).
@@ -190,7 +195,7 @@ roles:
190
195
  - **Cross-project dependency rows (`crossProjectDependencies` array — replaces the old `## Cross-Repo Carry` appendix):** a dependency on another project (a different repo / a different top-level independently-deployable module / a published package) is recorded not as the freeform `## Cross-Repo Carry` appendix but as an **`XP-NNN` row of the structured field `crossProjectDependencies`**. Put one XP row per project dependency and fill in whichever `direction` applies (rendered as `### Cross-Project Dependencies` §5.4; a single-project plan uses an empty array). The row fields replace the old three subsections with — `requiredWork` (the concrete work the other side must build) / `verificationSignal` (the signal this run will observe) / `linkedWork` (the stage/step of this plan that is blocked until the signal is met) / `howToStart` (the exact handoff in the other repo).
191
196
  - `direction: upstream-precondition` — this run **waits on the other side's prerequisite work (precondition required)**: `requiredWork` = the concrete work the other side must implement first, `verificationSignal` = the signal this run must observe before proceeding (PR merged / endpoint live / version published), `linkedWork` = the stage/step of this plan that is blocked until the signal is met, `howToStart` = in the other repo, `okstra-brief-gen` (**cite this report's absolute path as Source Material** — the only permitted cross-`<PROJECT_ROOT>` read) → `okstra-run`.
192
197
  - `direction: downstream-carry` — this run **produces what the other side will consume (the existing carry)**: `requiredWork` = the self-contained B-portion the other side will implement next (from the other side's perspective, a new `R-NNN`, proposed stages, and affected files), `verificationSignal` = the (this-run-delivered) signal the other side confirms before proceeding, `linkedWork` = the stage/step of this plan that delivers that signal, `howToStart` is the same as upstream. Do not hand over this run's already-`done` stages as if they were the other side's stages — include only the portion the other side still has to build.
193
- - **A cross-repo dependency cannot be expressed with `depends-on`:** that gate only resolves commits inside a single repo's git graph (`scripts/okstra_ctl/run.py` `_resolve_stage_base_commit`), so it cannot point at a commit in another repo. Cross-repo work is split into a separate okstra run + XP row rather than a stage (the Different-repos rule). The other run is structurally independent, so it does not automatically recognise this run's completed stages as `done` (nor should it — that is this repo's work); the XP row is only a narrative input that seeds the other side's planning. This run does not write to the other repo's tree or its `.okstra/` (it emits only this report's XP rows). The old "Recognition caveat" wording has been moved into a render note (i18n `crossProjectRecognitionNote`) and is emitted automatically in the `### Cross-Project Dependencies` section.
198
+ - **A cross-repo dependency cannot be expressed with `depends-on`:** that gate only resolves commits inside a single repo's git graph (`scripts/okstra_ctl/stage_targets.py` `resolve_stage_base_commit`), so it cannot point at a commit in another repo. Cross-repo work is split into a separate okstra run + XP row rather than a stage (the Different-repos rule). The other run is structurally independent, so it does not automatically recognise this run's completed stages as `done` (nor should it — that is this repo's work); the XP row is only a narrative input that seeds the other side's planning. This run does not write to the other repo's tree or its `.okstra/` (it emits only this report's XP rows). The old "Recognition caveat" wording has been moved into a render note (i18n `crossProjectRecognitionNote`) and is emitted automatically in the `### Cross-Project Dependencies` section.
194
199
  - **Stage exit contract is the carry surface:** keep it as narrow as possible. Wider surface = more downstream coupling.
195
200
  - dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, repo-internal sequencing)
196
201
  - **Cross-Project Dependencies (conditionally required):** when the plan depends on work in another project / repo / published package, add (a) a `kind: cross-project` DM row to `dependencyMigrationRisk`, and (b) a matching `XP-NNN` row to `crossProjectDependencies`. An upstream-precondition row must have concrete `requiredWork` / `verificationSignal` / `howToStart` — `validators/validate-run.py` enforces that a DM `cross-project` ⇒ at least one `direction: upstream-precondition` XP row, and the schema enforces non-empty row fields. A cross-project dependency is recorded as this structured precondition, not as a soft Recommended Next Step. A single-project plan uses an empty array.
@@ -35,7 +35,8 @@ roles:
35
35
  - Executor model: `{{EXECUTOR_MODEL_DISPLAY}}` (CLI launch value: `{{EXECUTOR_MODEL_EXECUTION_VALUE}}`; host-native launch value: `{{EXECUTOR_HOST_MODEL_VALUE}}`)
36
36
  - Executor runner: `{{EXECUTOR_RUNNER}}`
37
37
  - Executor dispatch mode: `{{EXECUTOR_DISPATCH_MODE}}`
38
- - Wherever this profile mentions the `Executor`, it refers to the role bound above. Every analysis provider in the resolved roster **except `{{EXECUTOR_WORKER_ID}}`** is dispatched as a verifier, each in a fresh session with no shared context, so no verdict comes from the session that wrote the diff (`_implementation-verifier.md` owns this rule). The executor's own provider gets **no** second, verifier-role session in the current plumbing: `worker_prompt_policy` materializes that worker ID as the executor on every dispatch, so there is no way to ask for one — treat that as a follow-up design item, never as something to work around from the lead. Verifier dispatches remain strictly read-only.
38
+ - Wherever this profile mentions the `Executor`, it refers to the role bound above. Every roster worker **except `{{EXECUTOR_WORKER_ID}}`** is dispatched as a verifier, each in a fresh session with no shared context, so no verdict comes from the session that wrote the diff (`_implementation-verifier.md` owns this rule). Verifier dispatches remain strictly read-only.
39
+ - **Every roster slot names its role.** The executor holds its own roster slot. A worker ID is always `<provider>-<role>` — `claude-implementer` for the executor, `claude-verifier` for a verifier, `antigravity-verifier` even when antigravity fills only that one slot — and each slot carries its own `initial/<id>` invocation assignment. A repeated `(provider, role)` pair (two Claude verifiers on different models) numbers the later ones: `claude-verifier-2`. Read every worker ID off the roster; never assume it equals the provider name. Both roster rows may carry the same model — reusing the executor's model behind an independent verifier session is allowed; what is not allowed is two verifiers on the same model. **Enforcement:** `scripts/okstra_ctl/run.py` `_canonical_worker_id` issues the ID; `validators/validate-run.py` refuses a `duplicate worker role detected` roster.
39
40
  - **Dispatch order (BLOCKING).** Executor and verifiers are **two** dispatches, never one batch — a batch holding both makes every verifier observe base HEAD instead of the stage diff. Dispatch the executor alone, settle it with `okstra team await`, then dispatch the verifiers with a `--workers` list that **omits `{{EXECUTOR_WORKER_ID}}`**: that worker ID is materialized as the executor on every dispatch, so a second batch still carrying it is refused again. **Enforcement:** `scripts/okstra_ctl/dispatch_core.py` `_validate_implementation_phase_order` refuses such a batch, in `--dry-run` as well as in a real dispatch.
40
41
  {{INCLUDE:_common-contract.md}}
41
42
  {{INCLUDE:_stage-discipline.md}}
@@ -45,7 +46,7 @@ roles:
45
46
  - The `--approve` flag is meaningful ONLY with `--task-type implementation` and `--approved-plan <path>`; any other use raises `PrepareError`. Idempotent — re-running with `approved: true` already set does not write again.
46
47
  - determine the plan branch from the sibling data.json `implementationPlanning.planningContract`. For `selected-direction`, the authoritative scope is `selectedDirectionRef`, its validated snapshot, `directionRealization`, and the selected stage; the plan MUST be `plan-ready` with exact coverage, and both an `implementation-option:` frontmatter field and `--implementation-option` are forbidden. A direction change routes to `implementation-option-selection`; a detail-only plan correction routes to `implementation-planning`.
47
48
  - for the legacy candidate-comparison branch, the authoritative scope is the Option Candidate named by the report record `frontmatter.implementationOption` field. **If that field is empty, fall back to the plan's `Recommended Option`** (this is a soft fallback, not a hard block). The chosen option's step list becomes the authoritative scope. Any deviation MUST be justified in the final report AND routed to a new `implementation-planning` run; never silently expand scope. If the chosen option name does not match any heading under `Option Candidates`, record it as a deviation.
48
- - Stage worktree (provisioned by `okstra-ctl` at this implementation run's prep time):
49
+ - Stage worktree (provisioned by Okstra at this implementation run's prep time):
49
50
  - Status: `{{EXECUTOR_WORKTREE_STATUS}}` (one of: `created` | `reused` | `skipped-in-worktree` | `skipped-not-git`)
50
51
  - 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`.
51
52
  - 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`.
@@ -8,7 +8,7 @@ roles:
8
8
  max: 5
9
9
  duty: discovery-worker
10
10
  - role: critic
11
- min: 1
11
+ min: 0
12
12
  recommended: 1
13
13
  max: 1
14
14
  duty: scope-critic
@@ -99,6 +99,7 @@ roles:
99
99
  - next recommended phase and safe resume guidance
100
100
  - canonical-term resolution for every `terminology:*` brief item as `<term> = <definition>`, plus whether `<PROJECT_ROOT>/.okstra/glossary.md` should be updated
101
101
  - one `endStateCoverage` row per brief end-state id (`EB-NNN` / `PB-NNN` / `EO-NNN`). `coveredBy` names the routing decision, the fan-out unit id, or the `C-NNN` clarification that accounts for it. This phase does NOT author a goal of its own — the brief already pinned the end state, and restating it here is how four phases end up with four different targets. **Enforced:** `validators/validate-run.py` `_validate_end_state_coverage`.
102
+ - a `blocked` `endStateCoverage` row MUST carry `blockedBy` — `kind` one of `clarification` / `finding` / `execution` / `upstream` / `not-observed`, plus a `ref` for the first two. `blocked` on its own carries all five situations at once, and only `clarification` is something a person can answer; without the field nothing downstream can tell "the reporter must decide" from "the server would not boot". A `clarification` ref must be a `C-NNN` row of this report or one carried in — the same row the approval-decision ledger holds, not an id you only named in prose. A `finding` ref is required but not resolved: it legitimately points at the approved plan or an upstream report (`VC-003`, `CA-001`). **Enforced:** `validators/validate-run.py` `_validate_end_state_blocked_by`.
102
103
  - Report assembly instructions:
103
104
  - write canonical-term resolutions in a new `Domain Alignment` subsection of the final report; actual glossary writes happen via `okstra-brief-gen` Step 4.5 on a subsequent run
104
105
  - Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
@@ -101,6 +101,13 @@
101
101
  "errors": {
102
102
  "none": "같은 task에서 선택할 implementation-option-selection 최종 보고서를 찾을 수 없습니다.",
103
103
  "unknown": "목록에 없는 selected-direction 경로입니다: {value}"
104
+ },
105
+ "labels": {
106
+ "options": "{path} — 후보 {ids}",
107
+ "no_options": "{path} — 후보 없음 (이 리포트로는 계획을 열 수 없습니다)"
108
+ },
109
+ "echo_variants": {
110
+ "cleared_clarification": "selected-direction: {value} (새 계획이므로 clarification-response 는 비웁니다 — 답변 사이드카는 이 방향과 함께 전달됩니다)"
104
111
  }
105
112
  },
106
113
  "brief_keep": {
@@ -410,23 +417,32 @@
410
417
  },
411
418
  "reverify_scope_pick": {
412
419
  "label": "재검증 범위를 어떻게 할까요? (답변한 항목이 모두 직전 리포트의 stage 에 연결돼 있어 범위를 좁힐 수 있는 상태입니다 — 최종 확정은 run 시점 base-ref 비교)",
413
- "label_unlinked": "답변한 항목 중 직전 리포트의 stage 에 연결되지 않은 것이 있습니다. 다시 볼 stage 번호를 지정하거나 전체 재검증을 고르세요. 연결되지 않은 id 가 재실행 전체를 full 로 만들지는 않습니다.",
420
+ "label_unlinked": "답변한 항목 중 직전 리포트의 stage 에 연결되지 않은 것이 있습니다. 다시 볼 stage 번호를 지정하거나, 이번 재실행이 stage 를 추가만 한다면 전부 이월을, 아니면 전체 재검증을 고르세요. 연결되지 않은 id 가 재실행 전체를 full 로 만들지는 않습니다.",
414
421
  "echo_template": "reverify-scope: {value}",
415
422
  "options": {
416
423
  "auto": "관련 stage 만 (추천) — 답변이 닿는 stage 와 그 하위만 다시 검증하고 나머지는 직전 판정을 그대로 이월",
417
424
  "full": "전체 재검증 — stage 전부를 처음부터 다시 교차검증 (시간은 더 들지만 계획 형태가 바뀌었을 때 안전)",
418
- "__free_input__": "직접 입력 — 다시 볼 stage 번호를 지정"
425
+ "__free_input__": "직접 입력 — 다시 볼 stage 번호를 지정",
426
+ "carry-all": "재검증 없이 전부 이월 — 직전 stage 는 모두 완료됐고 이번 재실행은 stage 를 추가만 합니다. 기존 stage 는 직전 판정을 그대로 가져오고 새 stage 만 봅니다 (브랜치가 움직였으면 자동으로 전체 재검증으로 내려갑니다)",
427
+ "full_recommended": "전체 재검증 (추천) — 되짚기가 stage 를 못 냈으므로 좁힐 근거가 없습니다. 시간은 더 들지만 답변의 영향 범위를 놓치지 않습니다"
419
428
  },
420
429
  "echo_suffixes": {
421
430
  "auto": "reverify-scope: auto (좁힐 수 있으면 좁힘)",
422
- "full": "reverify-scope: full (전체 재검증)"
431
+ "full": "reverify-scope: full (전체 재검증)",
432
+ "carry-all": "reverify-scope: carry-all (재검증 없이 전부 이월)"
423
433
  },
424
434
  "errors": {
425
- "unlinked_auto": "직전 리포트의 stage 에 연결되지 않은 답변이 있어 자동 판정을 쓸 수 없습니다: {ids}. stage 번호를 지정하거나 전체 재검증을 고르세요."
435
+ "unlinked_auto": "직전 리포트의 stage 에 연결되지 않은 답변이 있어 자동 판정을 쓸 수 없습니다: {ids}. stage 번호를 지정하거나 전체 재검증을 고르세요.",
436
+ "no_answers_auto": "직전 리포트에 답변된 clarification 이 없어 자동 판정을 쓸 수 없습니다 — 되짚을 답이 없으면 자동은 전체 재검증으로만 떨어집니다. 다시 볼 stage 번호를 지정하거나 전체 재검증을 고르세요."
437
+ },
438
+ "label_no_answers": "직전 리포트에 답변된 clarification 이 없어 자동으로 좁힐 근거가 없습니다. 이번 재실행에서 다시 볼 stage 번호를 직접 지정하거나 전체 재검증을 고르세요 — 지정한 번호는 그대로 impacted 로 들어갑니다. 최종 확정은 run 시점 base-ref 비교.",
439
+ "labels": {
440
+ "unlinked_ids": " 연결되지 않은 항목: {ids}.",
441
+ "no_basis": " 무엇을 다시 볼지는 이번 재실행의 목적에만 달려 있어, 이 run 이 추천할 근거는 없습니다."
426
442
  }
427
443
  },
428
444
  "reverify_scope_stages": {
429
- "label": "다시 볼 stage 번호를 쉼표로 입력하세요 (예: 2,3 — 직전 리포트 Stage Map 에 있는 번호만 유효. 지정한 stage 의 하위 stage 까지 함께 재검증됩니다. 빈 줄이면 자동 판정)",
445
+ "label": "다시 볼 stage 번호를 쉼표로 입력하세요 (예: 2,3 — 직전 리포트 Stage Map 에 있는 번호만 유효. 지정한 stage 의 하위 stage 까지 함께 재검증됩니다. 빈 줄은 되짚을 답이 있을 때만 자동 판정으로 떨어집니다)",
430
446
  "echo_template": "reverify-scope: stage {value}",
431
447
  "echo_suffixes": {
432
448
  "auto": "reverify-scope: auto (좁힐 수 있으면 좁힘)"
@@ -435,7 +451,8 @@
435
451
  "not_a_number": "stage 번호는 정수여야 합니다: {token}",
436
452
  "unknown_stage": "직전 리포트의 Stage Map 에 없는 stage 입니다: {stages} (Stage Map 의 번호: {known})",
437
453
  "no_stage_map": "직전 리포트에서 Stage Map 을 읽을 수 없어 stage 번호를 검증할 수 없습니다: {reason}",
438
- "unlinked_empty": "직전 리포트의 stage 에 연결되지 않은 답변이 있어 빈 입력(자동)을 쓸 수 없습니다: {ids}. stage 번호를 입력하세요."
454
+ "unlinked_empty": "직전 리포트의 stage 에 연결되지 않은 답변이 있어 빈 입력(자동)을 쓸 수 없습니다: {ids}. stage 번호를 입력하세요.",
455
+ "no_answers_empty": "직전 리포트에 답변된 clarification 이 없어 빈 입력(자동)을 쓸 수 없습니다. 다시 볼 stage 번호를 입력하세요."
439
456
  }
440
457
  },
441
458
  "pr_template_pick": {
@@ -497,9 +514,12 @@
497
514
  "label": "선택 역할 {role} 을(를) 이번 run 에 추가할까요? (최대 {maximum}개)",
498
515
  "echo_template": "role-add: {value}",
499
516
  "options": {
500
- "skip": "추가 안 함{default_suffix}",
517
+ "skip": "추가 안 함{skip_warning}{default_suffix}",
501
518
  "add": "{count}개 추가{default_suffix}",
502
- "default_suffix": " (기본)"
519
+ "default_suffix": " (기본)",
520
+ "skip_warnings": {
521
+ "critic": " — 차단 kind 에서 분석자 표가 1대1 동수면 가를 주체가 없어, 그 항목마다 승인 결정과 Blocks=approval clarification 행이 열립니다 (자동 tie-break 대신 사용자 질문. phase 는 계속 진행됩니다)"
522
+ }
503
523
  }
504
524
  },
505
525
  "role_model": {
@@ -636,6 +656,7 @@
636
656
  "reverify_scope_unlinked": " reverify-scope: stage 번호 필요 — {ids} 이(가) 직전 리포트의 어느 stage 에도 연결되지 않음 (자동 판정 불가; full 로 강제되지는 않음)",
637
657
  "reverify_scope_full": " reverify-scope: full 예상 — {reason}\n (좁히지 못하는 것은 재검증 범위이지 답변이 아닙니다 — 답변은 모두 carry-in 되어 반영되고, 대신 stage 전체를 다시 검증합니다)",
638
658
  "reverify_scope_user_full": " reverify-scope: full (사용자 지정 — 전체 재검증)",
659
+ "reverify_scope_user_carry_all": " reverify-scope: carry-all (사용자 지정 — 재검증 없이 직전 판정 전부 이월, 새 stage 만 검증; 브랜치가 움직였으면 full 로 내려감)",
639
660
  "reverify_scope_user_stages": " reverify-scope: stage {stages} 재검증 지정 (사용자 지정 — 하위 stage 포함, 나머지는 직전 판정 이월)",
640
661
  "stage_whole_task": "전체 task",
641
662
  "handoff_scope_whole_task": "전체 task (whole-task 검증 기반)",
@@ -1,4 +1,4 @@
1
- """okstra-ctl 와 okstra-central 가 공유하는 순수 헬퍼 패키지.
1
+ """okstra 런타임이 공유하는 순수 헬퍼 패키지.
2
2
 
3
3
  기존 단일 모듈 `okstra_ctl_lib` 를 도메인 단위 서브모듈로 분해한 결과이며,
4
4
  모든 public 심볼을 패키지 루트에서 재노출한다. 호출자는
@@ -8,15 +8,13 @@ from __future__ import annotations
8
8
 
9
9
  # Public API — keep import order topological (leaves first) to avoid surprises.
10
10
  from .ids import (
11
- build_container_session_name,
12
11
  build_run_id,
13
12
  parse_run_id,
14
- run_id_to_session_name,
15
13
  slugify_task_segment,
16
14
  _escape_segment_for_join,
17
15
  _safe_fs_segment,
18
16
  )
19
- from .locks import central_lock, task_lock_filename
17
+ from .locks import central_lock
20
18
  from .invocation import invocation_path, save_invocation, load_invocation
21
19
  from .jsonl import (
22
20
  append_jsonl,
@@ -52,23 +50,17 @@ from .reconcile import (
52
50
  normalize_central_status,
53
51
  normalize_reconciled_report_status,
54
52
  reconcile_active,
53
+ reconcile_home,
55
54
  reconcile_recent,
55
+ settle_run_row,
56
56
  )
57
57
  from .listing import (
58
58
  _parse_since,
59
59
  absolute_final_report_path,
60
- find_row_by_run_id,
61
- format_projects_table,
62
- format_runs_table,
63
- format_show,
64
- list_projects,
65
60
  list_runs,
66
61
  )
67
- from .resolver import ResolveError, _all_run_ids, resolve_last, resolve_run_id
68
62
  from .run_index_row import read_run_index
69
63
  from .sequence import predict_next_run_seq
70
- from .tmux import _shell_quote, build_tmux_command
71
- from .batch import expand_selectors, make_batch_id, write_batch_meta
72
64
  from .backfill import (
73
65
  _apply_backfill_meta,
74
66
  backfill_project,
@@ -86,28 +78,18 @@ from .run_context import (
86
78
 
87
79
  __all__ = [
88
80
  "DEFAULT_ABORT_AFTER_SECONDS",
89
- "ResolveError",
90
81
  "absolute_final_report_path",
91
82
  "append_jsonl",
92
83
  "backfill_project",
93
- "build_container_session_name",
94
84
  "build_run_id",
95
- "build_tmux_command",
96
85
  "central_lock",
97
86
  "compute_and_write_run_context",
98
87
  "compute_run_paths",
99
88
  "discover_project_roots",
100
- "expand_selectors",
101
- "find_row_by_run_id",
102
- "format_projects_table",
103
- "format_runs_table",
104
- "format_show",
105
89
  "invocation_path",
106
- "list_projects",
107
90
  "list_runs",
108
91
  "load_invocation",
109
92
  "load_project_meta",
110
- "make_batch_id",
111
93
  "mark_backfilled",
112
94
  "next_run_seq",
113
95
  "normalize_central_status",
@@ -119,21 +101,18 @@ __all__ = [
119
101
  "read_run_context",
120
102
  "read_run_inputs",
121
103
  "reconcile_active",
104
+ "reconcile_home",
122
105
  "reconcile_recent",
106
+ "settle_run_row",
123
107
  "record_start",
124
108
  "remove_jsonl_row",
125
109
  "remove_reservation",
126
110
  "reserve_run_in_active",
127
- "resolve_last",
128
- "resolve_run_id",
129
111
  "resolve_under_root",
130
112
  "rotate_recent_if_needed",
131
- "run_id_to_session_name",
132
113
  "save_invocation",
133
114
  "slugify_task_segment",
134
- "task_lock_filename",
135
115
  "task_mutex",
136
116
  "upsert_project_meta",
137
- "write_batch_meta",
138
117
  "write_run_inputs",
139
118
  ]
@@ -141,7 +141,7 @@ For a `host-text` mapping, render each numbered item as its option label followe
141
141
  | `read_artifacts` | Use the host file-read primitive and preserve the core contract's read order. |
142
142
  | `write_artifact` | Use the host file-write primitive only for paths authorized by the active lifecycle phase. |
143
143
  | `prompt_user` | Use `AskUserQuestion` for approvals and clarifications that fit `nativeLimits`. Do not print a numbered list in chat while that tool is available. Do not infer an answer from silence. |
144
- | `dispatch_worker` | First verify the materialized invocation metadata. Dispatch `runner=native-session` through `Agent(name: "<role>", run_in_background: true)` without `team_name`, passing the verified final prompt and `hostModelValue`. Dispatch `runner=cli-wrapper` with the deterministic shell command `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; never wrap that process in another `Agent(...)` call. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row. |
144
+ | `dispatch_worker` | First verify the materialized invocation metadata. Dispatch `runner=native-session` through `Agent(name: "<role>", run_in_background: true)` without `team_name`, with `hostModelValue` and a summon message only — never the prompt body. The summon is exactly: `Your complete dispatch instructions are the document at <absolute promptPath>. Read that document in full before doing anything else — every line, continuing with offset until end of file — then execute it exactly. Do not act on this message alone.` The verified `promptPath` is already persisted and metadata-verified; inlining its body into the `Agent` call duplicates it in the lead context and lets the two copies drift. Dispatch `runner=cli-wrapper` with the deterministic shell command `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; never wrap that process in another `Agent(...)` call. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row. |
145
145
  | `await_workers` | Arm one background shell poll for the pending Result Paths; the spawn acknowledgement is not completion. |
146
146
  | `redispatch_worker` | Materialize and verify a fresh invocation, then use a fresh native `Agent(...)` session or `okstra worker-dispatch` attempt according to the persisted runner. |
147
147
  | `shutdown_workers` | For each confirmed-complete worker selected for cleanup, send `SendMessage(to: <name>, message: { type: "shutdown_request" })` to idle the roster member **and** call `TaskStop(task_id: "<name>")` to stop its background task. Both are required; neither subsumes the other. |
@@ -206,11 +206,10 @@ For a `host-text` mapping, render each numbered item as its option label followe
206
206
  - At run start, record `teamName` as the audit label in team-state and populate `lead.sessionId`; the session transcript lives under `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`. You do NOT write `teamCreate`: `okstra team dispatch` records the implicit-team marker (`{ attempted: false, status: "implicit" }`) itself, on every dispatch path, because v2.1.178 made that value a constant rather than a judgment. The one marker that IS yours is the concurrent-run decision — a concurrent run records `teamCreate: { attempted: false, status: "skipped", reason: "concurrent-run" }` **before** the first dispatch, and dispatch then leaves it alone.
207
207
  - Collect and persist token usage before any live-roster cleanup, including cleanup between batches and the run-end shutdown sequence.
208
208
  - Before each new worker batch (and before the next phase's render-bundle), close the panes of the dispatches that finished in the prior round, in two passes. First count: `okstra team reclaim --project-root "<PROJECT_ROOT>" --run-manifest "<RUN_MANIFEST_PATH>" --dry-run` closes nothing and prints one `<paneId>\t<kind>` line per pane it would close — count those lines as `<n>`. Then run the same command **without** `--dry-run` to close them, and emit the neutral contract's `PROGRESS: phase-batch-cleanup panes=<n>` checkpoint with that count. Call both passes after collecting that round's results and token usage and before the next dispatch. The command reads each dispatch's recorded status, so an in-progress worker keeps its pane whichever moment you call it — you do not scope the pass by hand. It closes only the panes okstra opened and recorded; a pane the harness opened for itself carries no recorded id and is not okstra's to close. A `cli-wrapper` run holds no pane at all, so `<n>` is `0` — still emit the checkpoint.
209
- - Reclaiming a pane does not stop the worker's background task. Every `dispatch_worker` Agent runs with `run_in_background: true`, so a worker whose result is already collected stays a live background task for the rest of the session — that residue is what fills the harness's exit-time `Background work is running` list. At the same batch boundary, right after the pane reclaim, call `TaskStop(task_id: "<name>")` once per worker of the completed batch, passing the exact `name` used at dispatch (`<workerId>-worker`, `<workerId>-worker-reverify-r<N>`, `<provider>-worker-critic`, `report-writer`). Stop only workers whose results were already collected — never an in-flight worker, never the lead, and keep `report-writer` while it is in flight, matching the pane pass's `--keep report-writer-worker`. `TaskStop` on an already-finished task is a no-op; treat a failure as benign, record nothing, and continue the boundary. This runs in a non-tmux session too, where the pane passes no-op but the background tasks still exist.
209
+ - Reclaiming a pane does not stop the worker's background task. Every `dispatch_worker` Agent runs with `run_in_background: true`, so a worker whose result is already collected stays a live background task for the rest of the session — that residue is what fills the harness's exit-time `Background work is running` list. At the same batch boundary, right after the pane reclaim, call `TaskStop(task_id: "<name>")` once per worker of the completed batch, passing the exact `name` used at dispatch (`<workerId>-worker`, `<workerId>-worker-reverify-r<N>`, `<provider>-worker-critic`, `report-writer`). Stop only workers whose results were already collected — never an in-flight worker, never the lead, and keep `report-writer` while it is in flight, matching the pane pass's `--keep report-writer-worker`. `TaskStop` on an already-finished task is a no-op; treat a failure as benign, record nothing, and continue the boundary. This runs even when the pane passes found nothing to close, because the background tasks exist either way.
210
210
  - Before any `prompt_user`/`AskUserQuestion` that follows worker dispatch — an approval, clarification, or decision gate — run the same two passes used at a round boundary: `okstra team reclaim … --dry-run` to count `<n>`, then the same command without `--dry-run` to close, and emit `PROGRESS: phase-gate-cleanup panes=<n>`. Then `TaskStop(task_id: "<name>")` each completed worker, exactly as at a batch boundary. A bare `TaskStop` idles the roster task and closes no pane, so it is never cleanup on its own. This keeps the user from being shown a gate while finished worker panes are still open. After that cleanup, follow the lead contract "User confirmation before an approval blocker": read cited plan items, worker findings, and files before asking, and ask in the user's language with each option's outcome.
211
211
  - After batch cleanup, record the current live session generation with `okstra token-usage "<TEAM_STATE_PATH>" --record-observed-session --project-root "<PROJECT_ROOT>"`. This protects usage accounting when Claude Code re-issues the session id after resume or compaction.
212
212
  - Claude Code cannot delete the implicit team or surgically remove an idle roster entry. Explain that teammates may remain visible until session end and, when needed, give the manual action `Delete team <teamName> in Teams/FleetView`.
213
- - The `SessionEnd` hook runs `$HOME/.okstra/bin/okstra-team-reconcile.sh --session-end` as the safety net for the current live session.
214
213
 
215
214
  ### Run-end cleanup sequence (BLOCKING)
216
215
 
@@ -222,6 +221,6 @@ For a `host-text` mapping, render each numbered item as its option label followe
222
221
  > <quoted `--list` output>
223
222
  > (Yes) Close everything and clean up teammates / (No) Keep everything
224
223
  5. On `keep`, preserve every residual resource and show `okstra team teardown --project-root "<PROJECT_ROOT>" --run-manifest "<RUN_MANIFEST_PATH>"` plus the manual Teams/FleetView action. Tell the user that `keep` holds only until the next boundary: if this session goes on to another phase/batch, that transition's round-boundary cleanup reclaims the kept **completed** panes unattended (in-flight resources and the lead pane are never touched).
225
- 6. On approved `clean`, emit the teardown checkpoint, run `okstra team teardown --project-root "<PROJECT_ROOT>" --run-manifest "<RUN_MANIFEST_PATH>"`, then run `$HOME/.okstra/bin/okstra-team-reconcile.sh --project-root "<PROJECT_ROOT>" --fallback-team "session-<lead.sessionId-prefix>"` exactly once. The resolver reads the current live session's `~/.claude/teams/session-<live>/config.json`, falling back to the snapshot directory only when the live directory is absent, and prints `dismissible-member: <name>` records.
226
- 7. Send `SendMessage(to: <name>, message: { type: "shutdown_request" })` to each printed, confirmed-complete non-lead member. The `message` MUST be the object literal shown, NEVER a JSON string in a text field. Never target the lead.
224
+ 6. On approved `clean`, emit the teardown checkpoint and run `okstra team teardown --project-root "<PROJECT_ROOT>" --run-manifest "<RUN_MANIFEST_PATH>"` exactly once.
225
+ 7. Send `SendMessage(to: <name>, message: { type: "shutdown_request" })` to each confirmed-complete non-lead teammate this run dispatched. The `message` MUST be the object literal shown, NEVER a JSON string in a text field. Never target the lead.
227
226
  8. Call `TaskStop(task_id: "<name>")` for every worker this run dispatched, reusing the step-7 names plus any batch worker already reclaimed earlier. `shutdown_request` only idles the roster member and the step-6 pane reclaim only closes the pane — neither ends the background task, so this step is the one that empties the harness's exit-time `Background work is running` list. Never target the lead; a `TaskStop` on an already-finished task is a benign no-op.
@@ -37,6 +37,25 @@ DESCRIPTOR = HostDescriptor(
37
37
 
38
38
 
39
39
  def _okstra_home_write_checks(context) -> tuple[dict[str, object], ...]:
40
+ """Whether THIS codex session can host the lead, not whether the run can go.
41
+
42
+ okstra's cross-project state lives in `~/.okstra`, and every run takes the
43
+ worktree registry lock there before the wizard starts. A codex session the
44
+ Codex app started under its `workspace-write` default cannot write that
45
+ path, and macOS seatbelt is inherited by children, so no okstra process
46
+ launched from inside that session can either.
47
+
48
+ That is a fact about the session, not about the machine. okstra already
49
+ knows how to run codex with no sandbox — `LeadLaunchSpec.sandbox_waiver`
50
+ puts `-s danger-full-access` on every lead it spawns itself. The waiver has
51
+ nowhere to go in `current-session` mode because there is no process being
52
+ launched. So the answer is to launch one: this check reports
53
+ `lead-must-spawn`, and the run proceeds in `spawn-process` mode.
54
+
55
+ It is deliberately not a blocker. Stopping here asked the user to widen
56
+ their whole Codex sandbox for a boundary okstra does not want anywhere else
57
+ — every provider CLI now runs without one.
58
+ """
40
59
  if context.entry_mode != "current-session":
41
60
  return ({"id": "okstra-home-write", "status": "not-applicable"},)
42
61
 
@@ -51,8 +70,8 @@ def _okstra_home_write_checks(context) -> tuple[dict[str, object], ...]:
51
70
  except PermissionError:
52
71
  return ({
53
72
  "id": "okstra-home-write",
54
- "status": "required",
55
- "action": "switch-codex-to-full-access-and-rerun",
73
+ "status": "lead-must-spawn",
74
+ "action": "spawn-unsandboxed-codex-lead",
56
75
  },)
57
76
  return ({"id": "okstra-home-write", "status": "accepted"},)
58
77
 
@@ -10,9 +10,10 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
10
10
  INTERACTION_FUNCTIONS,
11
11
  PENDING_HOST_PORT,
12
12
  CapabilityHostAdapter,
13
+ no_automatic_claim,
13
14
  numbered_interaction_port,
14
15
  )
15
- from okstra_ctl.domain.host import HostClaim, HostDescriptor, HostResolutionContext
16
+ from okstra_ctl.domain.host import HostDescriptor
16
17
  from okstra_ctl.ports.host_model import FailClosedHostModelBindingPort
17
18
  from okstra_ctl.registry.provider_registry import ProviderRegistry
18
19
 
@@ -34,16 +35,6 @@ DESCRIPTOR = HostDescriptor(
34
35
  )
35
36
 
36
37
 
37
- def _detect_tmux(context: HostResolutionContext) -> HostClaim | None:
38
- if "tmux" not in context.signals:
39
- return None
40
- return HostClaim(
41
- DESCRIPTOR.id,
42
- 10,
43
- "No Claude Code host signal; tmux is available.",
44
- )
45
-
46
-
47
38
  def create_adapter(
48
39
  *,
49
40
  executable_finder: Callable[[str], str | None] = shutil.which,
@@ -69,7 +60,7 @@ def create_adapter(
69
60
  worker_dispatch_port=worker_dispatch_port,
70
61
  usage_accounting_port=usage_accounting_port,
71
62
  supported_functions=INTERACTION_FUNCTIONS,
72
- detector=_detect_tmux,
63
+ detector=no_automatic_claim,
73
64
  provider_registry=provider_registry,
74
65
  host_model_port=(
75
66
  host_model_port or FailClosedHostModelBindingPort(DESCRIPTOR.id)