okstra 0.186.7 → 0.188.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 (546) 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 +107 -40
  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 +39 -38
  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 +47 -22
  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/adapters/cmux.md +3 -3
  100. package/runtime/prompts/lead/convergence.md +99 -88
  101. package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
  102. package/runtime/prompts/lead/plan-body-verification.md +65 -46
  103. package/runtime/prompts/lead/report-writer.md +35 -5
  104. package/runtime/prompts/lead/team-contract.md +4 -1
  105. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
  106. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  107. package/runtime/prompts/profiles/_common-contract.md +3 -3
  108. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  109. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  110. package/runtime/prompts/profiles/_implementation-executor.md +3 -1
  111. package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
  112. package/runtime/prompts/profiles/error-analysis.md +4 -3
  113. package/runtime/prompts/profiles/final-verification.md +2 -2
  114. package/runtime/prompts/profiles/implementation-planning.md +22 -17
  115. package/runtime/prompts/profiles/implementation.md +3 -2
  116. package/runtime/prompts/profiles/requirements-discovery.md +2 -1
  117. package/runtime/prompts/wizard/prompts.ko.json +29 -8
  118. package/runtime/python/okstra_ctl/__init__.py +6 -27
  119. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
  120. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
  121. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
  122. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
  123. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
  124. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
  125. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
  126. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
  127. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
  128. package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
  129. package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
  130. package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
  131. package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
  132. package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
  133. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
  134. package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
  135. package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
  136. package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
  137. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
  138. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
  139. package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
  140. package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
  141. package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
  142. package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
  143. package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
  144. package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
  145. package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
  146. package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
  147. package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
  148. package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
  149. package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
  150. package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
  151. package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
  152. package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
  153. package/runtime/python/okstra_ctl/cmux.py +38 -31
  154. package/runtime/python/okstra_ctl/code_review_target.py +163 -1
  155. package/runtime/python/okstra_ctl/conformance.py +23 -0
  156. package/runtime/python/okstra_ctl/container.py +38 -421
  157. package/runtime/python/okstra_ctl/context_cost.py +32 -4
  158. package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
  159. package/runtime/python/okstra_ctl/convergence.py +107 -16
  160. package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
  161. package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
  162. package/runtime/python/okstra_ctl/convergence_store.py +5 -29
  163. package/runtime/python/okstra_ctl/design_prep.py +108 -12
  164. package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
  165. package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
  166. package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
  167. package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
  168. package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
  169. package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
  170. package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
  171. package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
  172. package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
  173. package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
  174. package/runtime/python/okstra_ctl/error_log_core.py +1 -1
  175. package/runtime/python/okstra_ctl/error_report.py +14 -0
  176. package/runtime/python/okstra_ctl/error_zip.py +18 -2
  177. package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
  178. package/runtime/python/okstra_ctl/execution_identity.py +100 -7
  179. package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
  180. package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
  181. package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
  182. package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
  183. package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
  184. package/runtime/python/okstra_ctl/handoff.py +24 -1
  185. package/runtime/python/okstra_ctl/ids.py +6 -16
  186. package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
  187. package/runtime/python/okstra_ctl/implementation_options.py +32 -13
  188. package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
  189. package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
  190. package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
  191. package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
  192. package/runtime/python/okstra_ctl/index.py +2 -2
  193. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
  194. package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
  195. package/runtime/python/okstra_ctl/json_boundary.py +10 -0
  196. package/runtime/python/okstra_ctl/listing.py +0 -91
  197. package/runtime/python/okstra_ctl/locks.py +5 -19
  198. package/runtime/python/okstra_ctl/log_report.py +14 -0
  199. package/runtime/python/okstra_ctl/manager_cli.py +22 -3
  200. package/runtime/python/okstra_ctl/manager_launch.py +4 -8
  201. package/runtime/python/okstra_ctl/material.py +2 -2
  202. package/runtime/python/okstra_ctl/migrate.py +23 -1
  203. package/runtime/python/okstra_ctl/model_cli.py +19 -5
  204. package/runtime/python/okstra_ctl/model_discovery.py +46 -47
  205. package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
  206. package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
  207. package/runtime/python/okstra_ctl/model_io/references.py +370 -0
  208. package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
  209. package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
  210. package/runtime/python/okstra_ctl/model_pool.py +10 -6
  211. package/runtime/python/okstra_ctl/models.py +8 -8
  212. package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
  213. package/runtime/python/okstra_ctl/next_phase.py +121 -69
  214. package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
  215. package/runtime/python/okstra_ctl/pane_title.py +11 -8
  216. package/runtime/python/okstra_ctl/path_hints.py +36 -11
  217. package/runtime/python/okstra_ctl/paths.py +76 -8
  218. package/runtime/python/okstra_ctl/plan_items.py +132 -16
  219. package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
  220. package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
  221. package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
  222. package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
  223. package/runtime/python/okstra_ctl/prepare_error.py +20 -0
  224. package/runtime/python/okstra_ctl/prior_planning.py +157 -0
  225. package/runtime/python/okstra_ctl/profile_show.py +18 -1
  226. package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
  227. package/runtime/python/okstra_ctl/recap.py +22 -1
  228. package/runtime/python/okstra_ctl/reconcile.py +84 -0
  229. package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
  230. package/runtime/python/okstra_ctl/render.py +98 -32
  231. package/runtime/python/okstra_ctl/render_final_report.py +4 -8
  232. package/runtime/python/okstra_ctl/report_assembly.py +297 -7
  233. package/runtime/python/okstra_ctl/report_contract.py +30 -7
  234. package/runtime/python/okstra_ctl/report_finalize.py +213 -16
  235. package/runtime/python/okstra_ctl/report_html/common.py +2 -2
  236. package/runtime/python/okstra_ctl/report_html/render.py +8 -12
  237. package/runtime/python/okstra_ctl/report_language.py +8 -4
  238. package/runtime/python/okstra_ctl/report_narrative.py +220 -34
  239. package/runtime/python/okstra_ctl/report_projections.py +8 -2
  240. package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
  241. package/runtime/python/okstra_ctl/report_translation.py +2 -35
  242. package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
  243. package/runtime/python/okstra_ctl/report_views.py +4 -22
  244. package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
  245. package/runtime/python/okstra_ctl/rollup.py +13 -0
  246. package/runtime/python/okstra_ctl/run.py +639 -138
  247. package/runtime/python/okstra_ctl/run_audit.py +13 -0
  248. package/runtime/python/okstra_ctl/run_index_row.py +0 -12
  249. package/runtime/python/okstra_ctl/seeding.py +21 -11
  250. package/runtime/python/okstra_ctl/sequence.py +1 -1
  251. package/runtime/python/okstra_ctl/session.py +64 -14
  252. package/runtime/python/okstra_ctl/set_work_status.py +18 -0
  253. package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
  254. package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
  255. package/runtime/python/okstra_ctl/stage_map.py +57 -11
  256. package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
  257. package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
  258. package/runtime/python/okstra_ctl/stage_targets.py +3 -12
  259. package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
  260. package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
  261. package/runtime/python/okstra_ctl/task_target.py +4 -16
  262. package/runtime/python/okstra_ctl/team.py +49 -2
  263. package/runtime/python/okstra_ctl/time_report.py +17 -2
  264. package/runtime/python/okstra_ctl/usage_report.py +11 -0
  265. package/runtime/python/okstra_ctl/user_response.py +55 -218
  266. package/runtime/python/okstra_ctl/user_response_values.py +242 -0
  267. package/runtime/python/okstra_ctl/validation_contract.py +3 -0
  268. package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
  269. package/runtime/python/okstra_ctl/verification_target.py +68 -0
  270. package/runtime/python/okstra_ctl/wizard.py +457 -92
  271. package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
  272. package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
  273. package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
  274. package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
  275. package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
  276. package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
  277. package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
  278. package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
  279. package/runtime/python/okstra_ctl/worker_request.py +2 -1
  280. package/runtime/python/okstra_ctl/worker_runner.py +58 -17
  281. package/runtime/python/okstra_ctl/worker_state.py +16 -1
  282. package/runtime/python/okstra_ctl/workflow.py +18 -1
  283. package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
  284. package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
  285. package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
  286. package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
  287. package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
  288. package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
  289. package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
  290. package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
  291. package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
  292. package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
  293. package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
  294. package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
  295. package/runtime/python/okstra_ctl/write_policy.py +90 -213
  296. package/runtime/python/okstra_project/__init__.py +0 -4
  297. package/runtime/python/okstra_project/dirs.py +2 -2
  298. package/runtime/python/okstra_project/phase_pointer.py +84 -0
  299. package/runtime/python/okstra_project/slug.py +24 -0
  300. package/runtime/python/okstra_project/state.py +22 -230
  301. package/runtime/python/okstra_token_usage/antigravity.py +50 -12
  302. package/runtime/python/okstra_token_usage/claude.py +9 -1
  303. package/runtime/python/okstra_token_usage/cli.py +3 -1
  304. package/runtime/python/okstra_token_usage/collect.py +171 -36
  305. package/runtime/python/okstra_token_usage/cursor.py +2 -0
  306. package/runtime/python/okstra_token_usage/grok.py +1 -5
  307. package/runtime/python/okstra_token_usage/paths.py +26 -0
  308. package/runtime/python/okstra_token_usage/pricing.py +23 -8
  309. package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
  310. package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
  311. package/runtime/schemas/final-report-v2.0.schema.json +9 -26
  312. package/runtime/schemas/final-report-v3.0.schema.json +173 -41
  313. package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
  314. package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
  315. package/runtime/skills/okstra-container-build/SKILL.md +16 -47
  316. package/runtime/skills/okstra-inspect/facets/history.md +1 -1
  317. package/runtime/skills/okstra-inspect/facets/status.md +7 -6
  318. package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
  319. package/runtime/skills/okstra-run/SKILL.md +7 -5
  320. package/runtime/templates/report-writer-prompt-preamble.md +3 -1
  321. package/runtime/templates/reports/brief.template.md +6 -2
  322. package/runtime/templates/reports/error-analysis-input.template.md +2 -0
  323. package/runtime/templates/reports/final-verification-input.template.md +2 -0
  324. package/runtime/templates/reports/implementation-input.template.md +7 -1
  325. package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
  326. package/runtime/templates/reports/quick-input.template.md +2 -0
  327. package/runtime/templates/reports/release-handoff-input.template.md +6 -1
  328. package/runtime/templates/reports/report.js +20 -3
  329. package/runtime/templates/reports/schedule.template.md +2 -0
  330. package/runtime/templates/reports/settings.template.json +0 -10
  331. package/runtime/templates/reports/task-brief.template.md +3 -0
  332. package/runtime/templates/reports/user-response.template.md +7 -3
  333. package/runtime/validators/checks/fixtures-01.py +57 -0
  334. package/runtime/validators/checks/fixtures-02.py +565 -0
  335. package/runtime/validators/checks/runners-01.py +108 -0
  336. package/runtime/validators/checks/validate-assets-01.py +60 -0
  337. package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
  338. package/runtime/validators/checks/validate-tasks-01.py +46 -0
  339. package/runtime/validators/checks/validate-tasks-02.py +85 -0
  340. package/runtime/validators/checks/validate-tasks-03.py +61 -0
  341. package/runtime/validators/checks/validate-tasks-04.py +118 -0
  342. package/runtime/validators/forbidden_actions.py +73 -3
  343. package/runtime/validators/lib/common.sh +5 -0
  344. package/runtime/validators/lib/fixtures.sh +7 -591
  345. package/runtime/validators/lib/paths.sh +13 -0
  346. package/runtime/validators/lib/runners.sh +6 -104
  347. package/runtime/validators/lib/summary.sh +1 -1
  348. package/runtime/validators/lib/validate-assets.sh +6 -56
  349. package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
  350. package/runtime/validators/lib/validate-tasks.sh +9 -294
  351. package/runtime/validators/validate-implementation-plan-stages.py +4 -4
  352. package/runtime/validators/validate-run.py +1363 -2643
  353. package/runtime/validators/validate-workflow.sh +56 -16
  354. package/runtime/validators/validate_improvement_report.py +2 -1
  355. package/runtime/validators/validate_session_conformance.py +295 -49
  356. package/dist/commands/execute/agent-prompt.d.mts +0 -1
  357. package/dist/commands/execute/agent-prompt.mjs +0 -24
  358. package/dist/commands/execute/agent-prompt.mjs.map +0 -1
  359. package/dist/commands/execute/codex-dispatch.d.mts +0 -3
  360. package/dist/commands/execute/codex-dispatch.mjs +0 -6
  361. package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
  362. package/dist/commands/execute/codex-run.d.mts +0 -3
  363. package/dist/commands/execute/codex-run.mjs +0 -62
  364. package/dist/commands/execute/codex-run.mjs.map +0 -1
  365. package/dist/commands/execute/convergence.d.mts +0 -1
  366. package/dist/commands/execute/convergence.mjs +0 -38
  367. package/dist/commands/execute/convergence.mjs.map +0 -1
  368. package/dist/commands/execute/error-log.d.mts +0 -1
  369. package/dist/commands/execute/error-log.mjs +0 -18
  370. package/dist/commands/execute/error-log.mjs.map +0 -1
  371. package/dist/commands/execute/git-reconcile.d.mts +0 -1
  372. package/dist/commands/execute/git-reconcile.mjs +0 -30
  373. package/dist/commands/execute/git-reconcile.mjs.map +0 -1
  374. package/dist/commands/execute/handoff.d.mts +0 -1
  375. package/dist/commands/execute/handoff.mjs +0 -31
  376. package/dist/commands/execute/handoff.mjs.map +0 -1
  377. package/dist/commands/execute/incremental-carry.d.mts +0 -1
  378. package/dist/commands/execute/incremental-carry.mjs +0 -20
  379. package/dist/commands/execute/incremental-carry.mjs.map +0 -1
  380. package/dist/commands/execute/incremental-scope.d.mts +0 -1
  381. package/dist/commands/execute/incremental-scope.mjs +0 -29
  382. package/dist/commands/execute/incremental-scope.mjs.map +0 -1
  383. package/dist/commands/execute/integrate-stages.d.mts +0 -1
  384. package/dist/commands/execute/integrate-stages.mjs +0 -24
  385. package/dist/commands/execute/integrate-stages.mjs.map +0 -1
  386. package/dist/commands/execute/pane-title.d.mts +0 -1
  387. package/dist/commands/execute/pane-title.mjs +0 -20
  388. package/dist/commands/execute/pane-title.mjs.map +0 -1
  389. package/dist/commands/execute/plan-items.d.mts +0 -1
  390. package/dist/commands/execute/plan-items.mjs +0 -9
  391. package/dist/commands/execute/plan-items.mjs.map +0 -1
  392. package/dist/commands/execute/plan-validate.d.mts +0 -1
  393. package/dist/commands/execute/plan-validate.mjs +0 -68
  394. package/dist/commands/execute/plan-validate.mjs.map +0 -1
  395. package/dist/commands/execute/plan-verify.d.mts +0 -1
  396. package/dist/commands/execute/plan-verify.mjs +0 -43
  397. package/dist/commands/execute/plan-verify.mjs.map +0 -1
  398. package/dist/commands/execute/spawn-followups.d.mts +0 -1
  399. package/dist/commands/execute/spawn-followups.mjs +0 -22
  400. package/dist/commands/execute/spawn-followups.mjs.map +0 -1
  401. package/dist/commands/execute/team.d.mts +0 -3
  402. package/dist/commands/execute/team.mjs +0 -66
  403. package/dist/commands/execute/team.mjs.map +0 -1
  404. package/dist/commands/execute/token-usage.d.mts +0 -1
  405. package/dist/commands/execute/token-usage.mjs +0 -19
  406. package/dist/commands/execute/token-usage.mjs.map +0 -1
  407. package/dist/commands/execute/worker-audit-check.d.mts +0 -1
  408. package/dist/commands/execute/worker-audit-check.mjs +0 -34
  409. package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
  410. package/dist/commands/execute/worker-dispatch.d.mts +0 -7
  411. package/dist/commands/execute/worker-dispatch.mjs +0 -64
  412. package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
  413. package/dist/commands/execute/worker-state.d.mts +0 -1
  414. package/dist/commands/execute/worker-state.mjs +0 -28
  415. package/dist/commands/execute/worker-state.mjs.map +0 -1
  416. package/dist/commands/execute/worktree-lookup.d.mts +0 -1
  417. package/dist/commands/execute/worktree-lookup.mjs +0 -92
  418. package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
  419. package/dist/commands/execute/worktree-status.d.mts +0 -1
  420. package/dist/commands/execute/worktree-status.mjs +0 -121
  421. package/dist/commands/execute/worktree-status.mjs.map +0 -1
  422. package/dist/commands/inspect/code-review.d.mts +0 -1
  423. package/dist/commands/inspect/code-review.mjs +0 -32
  424. package/dist/commands/inspect/code-review.mjs.map +0 -1
  425. package/dist/commands/inspect/container.d.mts +0 -1
  426. package/dist/commands/inspect/container.mjs +0 -25
  427. package/dist/commands/inspect/container.mjs.map +0 -1
  428. package/dist/commands/inspect/context-cost.d.mts +0 -1
  429. package/dist/commands/inspect/context-cost.mjs +0 -25
  430. package/dist/commands/inspect/context-cost.mjs.map +0 -1
  431. package/dist/commands/inspect/design-prep.d.mts +0 -1
  432. package/dist/commands/inspect/design-prep.mjs +0 -22
  433. package/dist/commands/inspect/design-prep.mjs.map +0 -1
  434. package/dist/commands/inspect/error-report.d.mts +0 -1
  435. package/dist/commands/inspect/error-report.mjs +0 -25
  436. package/dist/commands/inspect/error-report.mjs.map +0 -1
  437. package/dist/commands/inspect/error-zip.d.mts +0 -1
  438. package/dist/commands/inspect/error-zip.mjs +0 -24
  439. package/dist/commands/inspect/error-zip.mjs.map +0 -1
  440. package/dist/commands/inspect/log-report.d.mts +0 -1
  441. package/dist/commands/inspect/log-report.mjs +0 -26
  442. package/dist/commands/inspect/log-report.mjs.map +0 -1
  443. package/dist/commands/inspect/model-io.d.mts +0 -1
  444. package/dist/commands/inspect/model-io.mjs +0 -25
  445. package/dist/commands/inspect/model-io.mjs.map +0 -1
  446. package/dist/commands/inspect/profile-show.d.mts +0 -1
  447. package/dist/commands/inspect/profile-show.mjs +0 -28
  448. package/dist/commands/inspect/profile-show.mjs.map +0 -1
  449. package/dist/commands/inspect/recap.d.mts +0 -1
  450. package/dist/commands/inspect/recap.mjs +0 -30
  451. package/dist/commands/inspect/recap.mjs.map +0 -1
  452. package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
  453. package/dist/commands/inspect/resolve-task-key.mjs +0 -24
  454. package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
  455. package/dist/commands/inspect/rollup.d.mts +0 -1
  456. package/dist/commands/inspect/rollup.mjs +0 -25
  457. package/dist/commands/inspect/rollup.mjs.map +0 -1
  458. package/dist/commands/inspect/run-audit.d.mts +0 -1
  459. package/dist/commands/inspect/run-audit.mjs +0 -25
  460. package/dist/commands/inspect/run-audit.mjs.map +0 -1
  461. package/dist/commands/inspect/set-work-status.d.mts +0 -1
  462. package/dist/commands/inspect/set-work-status.mjs +0 -29
  463. package/dist/commands/inspect/set-work-status.mjs.map +0 -1
  464. package/dist/commands/inspect/stage-map.d.mts +0 -1
  465. package/dist/commands/inspect/stage-map.mjs +0 -131
  466. package/dist/commands/inspect/stage-map.mjs.map +0 -1
  467. package/dist/commands/inspect/task-list.d.mts +0 -1
  468. package/dist/commands/inspect/task-list.mjs +0 -149
  469. package/dist/commands/inspect/task-list.mjs.map +0 -1
  470. package/dist/commands/inspect/task-show.d.mts +0 -1
  471. package/dist/commands/inspect/task-show.mjs +0 -108
  472. package/dist/commands/inspect/task-show.mjs.map +0 -1
  473. package/dist/commands/inspect/time-report.d.mts +0 -1
  474. package/dist/commands/inspect/time-report.mjs +0 -24
  475. package/dist/commands/inspect/time-report.mjs.map +0 -1
  476. package/dist/commands/inspect/usage-report.d.mts +0 -1
  477. package/dist/commands/inspect/usage-report.mjs +0 -23
  478. package/dist/commands/inspect/usage-report.mjs.map +0 -1
  479. package/dist/commands/inspect/user-response.d.mts +0 -1
  480. package/dist/commands/inspect/user-response.mjs +0 -35
  481. package/dist/commands/inspect/user-response.mjs.map +0 -1
  482. package/dist/commands/inspect/worker-liveness.d.mts +0 -1
  483. package/dist/commands/inspect/worker-liveness.mjs +0 -45
  484. package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
  485. package/dist/commands/lifecycle/contract-check.d.mts +0 -1
  486. package/dist/commands/lifecycle/contract-check.mjs +0 -18
  487. package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
  488. package/dist/commands/lifecycle/migrate.d.mts +0 -1
  489. package/dist/commands/lifecycle/migrate.mjs +0 -30
  490. package/dist/commands/lifecycle/migrate.mjs.map +0 -1
  491. package/dist/commands/lifecycle/model.d.mts +0 -1
  492. package/dist/commands/lifecycle/model.mjs +0 -22
  493. package/dist/commands/lifecycle/model.mjs.map +0 -1
  494. package/dist/commands/manager.d.mts +0 -3
  495. package/dist/commands/manager.mjs +0 -50
  496. package/dist/commands/manager.mjs.map +0 -1
  497. package/dist/commands/report/agent-activity.d.mts +0 -1
  498. package/dist/commands/report/agent-activity.mjs +0 -20
  499. package/dist/commands/report/agent-activity.mjs.map +0 -1
  500. package/dist/commands/report/approval-decision.d.mts +0 -1
  501. package/dist/commands/report/approval-decision.mjs +0 -21
  502. package/dist/commands/report/approval-decision.mjs.map +0 -1
  503. package/dist/commands/report/design-snapshot.d.mts +0 -1
  504. package/dist/commands/report/design-snapshot.mjs +0 -19
  505. package/dist/commands/report/design-snapshot.mjs.map +0 -1
  506. package/dist/commands/report/finalize.d.mts +0 -4
  507. package/dist/commands/report/finalize.mjs +0 -64
  508. package/dist/commands/report/finalize.mjs.map +0 -1
  509. package/dist/commands/report/inject-report-index.d.mts +0 -1
  510. package/dist/commands/report/inject-report-index.mjs +0 -21
  511. package/dist/commands/report/inject-report-index.mjs.map +0 -1
  512. package/dist/commands/report/render-final-report.d.mts +0 -1
  513. package/dist/commands/report/render-final-report.mjs +0 -23
  514. package/dist/commands/report/render-final-report.mjs.map +0 -1
  515. package/dist/commands/report/render-views.d.mts +0 -1
  516. package/dist/commands/report/render-views.mjs +0 -27
  517. package/dist/commands/report/render-views.mjs.map +0 -1
  518. package/dist/commands/report/translate.d.mts +0 -1
  519. package/dist/commands/report/translate.mjs +0 -33
  520. package/dist/commands/report/translate.mjs.map +0 -1
  521. package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
  522. package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
  523. package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
  524. package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
  525. package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
  526. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
  527. package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
  528. package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
  529. package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
  530. package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
  531. package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
  532. package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
  533. package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
  534. package/runtime/bin/okstra-central.sh +0 -152
  535. package/runtime/bin/okstra-incremental-carry.py +0 -10
  536. package/runtime/bin/okstra-incremental-scope.py +0 -10
  537. package/runtime/bin/okstra-team-reconcile.sh +0 -36
  538. package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
  539. package/runtime/python/okstra_ctl/batch.py +0 -60
  540. package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
  541. package/runtime/python/okstra_ctl/container_registry.py +0 -72
  542. package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
  543. package/runtime/python/okstra_ctl/resolver.py +0 -54
  544. package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
  545. package/runtime/python/okstra_ctl/tmux.py +0 -134
  546. package/runtime/python/okstra_ctl/worktree.py +0 -1099
@@ -4,7 +4,8 @@ This is a deliberately narrow JSON Schema implementation that supports
4
4
  exactly the keywords used by ``schemas/final-report-v2.0.schema.json``:
5
5
 
6
6
  type, required, properties, additionalProperties, enum, const, pattern,
7
- minLength, minItems, maxItems, uniqueItems, items, minimum, maximum, $ref ($defs),
7
+ minLength, minItems, maxItems, uniqueItems, prefixItems, items, minimum, maximum,
8
+ boolean schemas, $ref ($defs),
8
9
  oneOf, allOf, if/then/else, contains, minContains, maxContains, not
9
10
 
10
11
  We do NOT depend on the ``jsonschema`` PyPI package because its
@@ -21,7 +22,7 @@ from __future__ import annotations
21
22
  import json
22
23
  import re
23
24
  from pathlib import Path
24
- from typing import Any
25
+ from typing import Any, Mapping
25
26
 
26
27
  class SchemaError(ValueError):
27
28
  """Raised when a schema itself is malformed (e.g. a $ref points
@@ -102,12 +103,32 @@ def _resolve_ref(ref: str, root: dict) -> dict:
102
103
  return node
103
104
 
104
105
 
106
+ _BRANCH_ROOT_PREFIX = f"{_format_path(())}: "
107
+ """분기 probe 의 경로는 분기 루트 기준이라 `<root>:` 가 매 줄 반복된다.
108
+ 바깥 오류가 이미 실제 경로를 싣고 있으므로 안쪽에서는 뗀다."""
109
+
110
+ _BRANCH_ERROR_LIMIT = 8
111
+ """분기 하나당 실을 오류 수. 나머지는 개수만 적는다 — 열세 개 짜리 목록도
112
+ 앞의 여덟이면 어느 블록을 안 썼는지 판정된다."""
113
+
114
+
105
115
  class _Validator:
106
116
  def __init__(self, root_schema: dict):
107
117
  self.root = root_schema
108
118
  self.errors: list[str] = []
109
119
 
110
- def validate(self, instance: Any, schema: dict, path: tuple[str | int, ...]) -> None:
120
+ def validate(
121
+ self,
122
+ instance: Any,
123
+ schema: dict | bool,
124
+ path: tuple[str | int, ...],
125
+ ) -> None:
126
+ if schema is True:
127
+ return
128
+ if schema is False:
129
+ self._err(path, "value is not allowed by the false schema")
130
+ return
131
+
111
132
  # Handle $ref first — everything else is composed under the
112
133
  # resolved schema.
113
134
  if "$ref" in schema:
@@ -196,9 +217,14 @@ class _Validator:
196
217
  for previous in instance[:index]
197
218
  ):
198
219
  self._err(path, "array items are not unique")
220
+ prefix_items = schema.get("prefixItems") or []
221
+ for i, item_schema in enumerate(prefix_items[: len(instance)]):
222
+ self.validate(instance[i], item_schema, path + (i,))
223
+
199
224
  items = schema.get("items")
200
225
  if items is not None:
201
- for i, value in enumerate(instance):
226
+ remaining_items = instance[len(prefix_items) :]
227
+ for i, value in enumerate(remaining_items, start=len(prefix_items)):
202
228
  self.validate(value, items, path + (i,))
203
229
  contains = schema.get("contains")
204
230
  if contains is not None:
@@ -229,30 +255,78 @@ class _Validator:
229
255
  if else_schema is not None:
230
256
  self.validate(instance, else_schema, path)
231
257
 
232
- def _validate_one_of(self, instance: Any, branches: list[dict], path: tuple[str | int, ...]) -> None:
233
- matches = sum(1 for b in branches if self._matches(instance, b))
234
- if matches != 1:
258
+ def _validate_one_of(
259
+ self,
260
+ instance: Any,
261
+ branches: list[dict | bool],
262
+ path: tuple[str | int, ...],
263
+ ) -> None:
264
+ per_branch = [self._branch_errors(instance, branch) for branch in branches]
265
+ matches = sum(1 for errors in per_branch if not errors)
266
+ if matches == 1:
267
+ return
268
+ if matches == 0:
235
269
  self._err(
236
270
  path,
237
- f"oneOf matched {matches} branches (expected exactly 1); "
238
- f"branches: {[self._summarise(b) for b in branches]}",
271
+ "oneOf matched no branch (expected exactly 1); "
272
+ + self._branch_diagnosis(per_branch),
239
273
  )
274
+ return
275
+ self._err(
276
+ path,
277
+ f"oneOf matched {matches} branches (expected exactly 1); "
278
+ f"branches: {[self._summarise(b) for b in branches]}",
279
+ )
280
+
281
+ @staticmethod
282
+ def _branch_diagnosis(per_branch: list[list[str]]) -> str:
283
+ """왜 각 분기가 안 맞았는지. 분기 스키마를 인라인하지 않는다.
240
284
 
241
- def _validate_not(self, instance: Any, sub_schema: dict, path: tuple[str | int, ...]) -> None:
285
+ 종전에는 `oneOf` 실패가 분기 스키마 두 개를 통째로 찍었다. 1,500자
286
+ 한 줄이면서 **어느 필드가 빠졌는지는 말하지 않아서**, 읽는 사람이
287
+ narrative 의 키 목록을 뽑아 스키마의 `required` 와 손으로 대조해야
288
+ 했다. 분기가 실제로 낸 오류가 그 대조의 답이므로 그것을 싣는다.
289
+ """
290
+ parts: list[str] = []
291
+ for index, errors in enumerate(per_branch, start=1):
292
+ shown = [
293
+ error[len(_BRANCH_ROOT_PREFIX):]
294
+ if error.startswith(_BRANCH_ROOT_PREFIX)
295
+ else error
296
+ for error in errors[:_BRANCH_ERROR_LIMIT]
297
+ ]
298
+ remainder = len(errors) - len(shown)
299
+ if remainder > 0:
300
+ shown.append(f"(+{remainder} more)")
301
+ parts.append(f"branch {index}: " + "; ".join(shown))
302
+ return " | ".join(parts)
303
+
304
+ def _branch_errors(self, instance: Any, schema: dict | bool) -> list[str]:
305
+ probe = _Validator(self.root)
306
+ probe.validate(instance, schema, ())
307
+ return probe.errors
308
+
309
+ def _validate_not(
310
+ self,
311
+ instance: Any,
312
+ sub_schema: dict | bool,
313
+ path: tuple[str | int, ...],
314
+ ) -> None:
242
315
  if self._matches(instance, sub_schema):
243
316
  self._err(path, f"value must NOT match {self._summarise(sub_schema)}")
244
317
 
245
- def _matches(self, instance: Any, schema: dict) -> bool:
246
- """Cheap 'does this validate' probe used by if/oneOf/contains.
318
+ def _matches(self, instance: Any, schema: dict | bool) -> bool:
319
+ """Cheap 'does this validate' probe used by if/contains.
247
320
  Returns True iff the sub-schema produces zero errors. Does not
248
- mutate ``self.errors``.
321
+ mutate ``self.errors``. `oneOf` keeps the errors themselves
322
+ (`_branch_errors`) because it has to say why every branch failed.
249
323
  """
250
- probe = _Validator(self.root)
251
- probe.validate(instance, schema, ())
252
- return not probe.errors
324
+ return not self._branch_errors(instance, schema)
253
325
 
254
326
  @staticmethod
255
- def _summarise(schema: dict) -> str:
327
+ def _summarise(schema: dict | bool) -> str:
328
+ if isinstance(schema, bool):
329
+ return str(schema).lower()
256
330
  keys = sorted(k for k in schema if k not in ("description", "$comment"))
257
331
  return "{" + ", ".join(f"{k}={schema[k]!r}" for k in keys[:3]) + "}"
258
332
 
@@ -260,6 +334,234 @@ class _Validator:
260
334
  self.errors.append(f"{_format_path(path)}: {message}")
261
335
 
262
336
 
337
+ def verdict_token_rule(schema: Mapping[str, Any], task_type: str) -> tuple[str, ...]:
338
+ """이 task type 의 `finalVerdict.verdictToken` 에 스키마가 허용하는 값.
339
+
340
+ 스키마는 `allOf` 의 if/then 가지로 세 묶음을 나눈다(분석형 enum,
341
+ final-verification enum, 나머지 `not-applicable` const). 작성기는 그 가지를
342
+ 읽지 못해 `analysis-complete` 를 error-analysis 에 썼고(2026-09-02 실측),
343
+ 조립이 아니라 HTML 렌더에서야 거절됐다. 여기서 뽑아 저작 계약에 싣는다.
344
+ 가지가 없으면 빈 튜플이다 — 제약이 없다는 뜻이지 실패가 아니다.
345
+ """
346
+ def _walk(node: Any) -> tuple[str, ...]:
347
+ if isinstance(node, dict):
348
+ condition = node.get("if")
349
+ then = node.get("then")
350
+ if isinstance(condition, dict) and isinstance(then, dict):
351
+ header = (condition.get("properties") or {}).get("header") or {}
352
+ selector = (header.get("properties") or {}).get("taskType") or {}
353
+ allowed = selector.get("enum")
354
+ if allowed is None and "const" in selector:
355
+ allowed = [selector["const"]]
356
+ if isinstance(allowed, list) and task_type in allowed:
357
+ verdict = (then.get("properties") or {}).get("finalVerdict") or {}
358
+ token = (verdict.get("properties") or {}).get("verdictToken") or {}
359
+ if "const" in token:
360
+ return (str(token["const"]),)
361
+ if isinstance(token.get("enum"), list):
362
+ return tuple(str(value) for value in token["enum"])
363
+ for value in node.values():
364
+ found = _walk(value)
365
+ if found:
366
+ return found
367
+ elif isinstance(node, list):
368
+ for value in node:
369
+ found = _walk(value)
370
+ if found:
371
+ return found
372
+ return ()
373
+
374
+ return _walk(dict(schema))
375
+
376
+
377
+ def task_block_rules(schema: Mapping[str, Any], block_key: str) -> tuple[str, ...]:
378
+ """이 task type 의 데이터 블록(`properties[block_key]`)이 요구하는 모양을 한
379
+ 객체당 한 줄의 저작 계약 문장으로 편다.
380
+
381
+ 리드는 report-writer 지시문을 쓸 때 블록 안쪽의 필수 필드·식별자 패턴·
382
+ 정수 필드·고정 길이 배열을 알 길이 프롬프트 경로에 없어 스키마 JSON 을
383
+ 손으로 파싱했고, 그래도 놓친 만큼 같은 리포트를 다시 썼다(2026-09-03 실측,
384
+ dev-10626 implementation-option-selection: 네 회차). 여기서 뽑아 합성 묶음의
385
+ 저작 계약에 싣는다. 블록이 없거나 객체가 아니면 빈 튜플이다.
386
+
387
+ 경로는 스키마 키다(`rankedOptions[]` 는 `- Item N` 목록의 각 항목). 같은
388
+ 정의(`$ref`)가 두 경로에 나오면 두 번째는 첫 경로를 가리킨다 — 랭킹 옵션과
389
+ 감사 항목이 하위 구조 여섯 개를 공유한다.
390
+ """
391
+ root = dict(schema)
392
+ block = (root.get("properties") or {}).get(block_key)
393
+ if not isinstance(block, dict):
394
+ return ()
395
+ lines: list[str] = []
396
+ described: dict[str, str] = {}
397
+ _describe_object(root, block, block_key, lines, described)
398
+ return tuple(lines)
399
+
400
+
401
+ def _resolved(root: dict, node: Any) -> tuple[dict, str | None]:
402
+ """`$ref` 를 풀고, allOf 가지의 properties/required 를 얕게 합친다.
403
+
404
+ 반환값의 둘째는 이 노드가 가리킨 정의 이름 — 같은 정의를 두 경로에서
405
+ 두 번 펴지 않기 위해 쓴다.
406
+ """
407
+ if not isinstance(node, dict):
408
+ return {}, None
409
+ ref_name: str | None = None
410
+ if "$ref" in node:
411
+ ref_name = str(node["$ref"]).rsplit("/", 1)[-1]
412
+ node = _resolve_ref(node["$ref"], root)
413
+ merged = dict(node)
414
+ for branch in node.get("allOf") or []:
415
+ resolved, branch_ref = _resolved(root, branch)
416
+ if ref_name is None:
417
+ ref_name = branch_ref
418
+ properties = dict(merged.get("properties") or {})
419
+ for key, value in (resolved.get("properties") or {}).items():
420
+ existing = properties.get(key)
421
+ properties[key] = {**existing, **value} if isinstance(existing, dict) else value
422
+ merged["properties"] = properties
423
+ merged["required"] = list(
424
+ dict.fromkeys([*(merged.get("required") or []), *(resolved.get("required") or [])])
425
+ )
426
+ for keyword in ("type", "pattern", "const", "enum", "minItems", "maxItems", "items", "prefixItems"):
427
+ if keyword in resolved and keyword not in merged:
428
+ merged[keyword] = resolved[keyword]
429
+ return merged, ref_name
430
+
431
+
432
+ def _describe_object(
433
+ root: dict, node: Any, path: str, lines: list[str], described: dict[str, str],
434
+ ) -> None:
435
+ schema, ref_name = _resolved(root, node)
436
+ if ref_name and ref_name in described:
437
+ lines.append(f"`{path}`: same shape as `{described[ref_name]}`")
438
+ return
439
+ if ref_name:
440
+ described[ref_name] = path
441
+ properties = schema.get("properties") or {}
442
+ required = [key for key in (schema.get("required") or []) if isinstance(key, str)]
443
+ optional = [key for key in properties if key not in required]
444
+ parts: list[str] = []
445
+ if required:
446
+ parts.append("required " + ", ".join(f"`{key}`" for key in required))
447
+ if optional:
448
+ parts.append("optional " + ", ".join(f"`{key}`" for key in optional))
449
+ nested: list[tuple[str, Any]] = []
450
+ for key, value in properties.items():
451
+ constraint = _describe_property(root, value, f"{path}.{key}", nested)
452
+ if constraint:
453
+ parts.append(f"`{key}` {constraint}")
454
+ lines.append(f"`{path}`: " + "; ".join(parts))
455
+ for child_path, child in nested:
456
+ _describe_object(root, child, child_path, lines, described)
457
+
458
+
459
+ def _describe_property(
460
+ root: dict, node: Any, path: str, nested: list[tuple[str, Any]],
461
+ ) -> str:
462
+ """한 속성의 제약을 한 구절로. 객체 자식은 `nested` 에 넣어 자기 줄을 받는다."""
463
+ schema, _ref = _resolved(root, node)
464
+ branches = schema.get("oneOf") or schema.get("anyOf") or []
465
+ nullable = False
466
+ if branches:
467
+ resolved_branches = [_resolved(root, branch)[0] for branch in branches]
468
+ nullable = any(branch.get("type") == "null" for branch in resolved_branches)
469
+ others = [branch for branch in branches if _resolved(root, branch)[0].get("type") != "null"]
470
+ if len(others) == 1:
471
+ schema = _resolved(root, others[0])[0] | {"__branch": others[0]}
472
+ types = schema.get("type")
473
+ types = types if isinstance(types, list) else ([types] if types else [])
474
+ if "null" in types:
475
+ nullable = True
476
+ types = [item for item in types if item != "null"]
477
+ suffix = " or `_none_`" if nullable else ""
478
+ if "const" in schema:
479
+ return f"exactly `{schema['const']}`{suffix}"
480
+ if isinstance(schema.get("enum"), list):
481
+ return "one of " + ", ".join(f"`{value}`" for value in schema["enum"]) + suffix
482
+ if "properties" in schema or types == ["object"]:
483
+ nested.append((path, schema.get("__branch", node)))
484
+ return f"object (see `{path}`){suffix}"
485
+ if types == ["array"] or "items" in schema or "prefixItems" in schema:
486
+ return _describe_array(root, schema, path, nested) + suffix
487
+ if types == ["integer"]:
488
+ return "integer" + _range(schema) + suffix
489
+ if types == ["number"]:
490
+ return "number" + _range(schema) + suffix
491
+ if types == ["string"]:
492
+ pattern = schema.get("pattern")
493
+ return (f"matches `{pattern}`" if pattern else "string") + suffix
494
+ if types == ["boolean"]:
495
+ return "`true` or `false`" + suffix
496
+ return ""
497
+
498
+
499
+ def _describe_array(
500
+ root: dict, schema: dict, path: str, nested: list[tuple[str, Any]],
501
+ ) -> str:
502
+ minimum = schema.get("minItems")
503
+ maximum = schema.get("maxItems")
504
+ if maximum == 0:
505
+ return "must be empty (`> _none_`)"
506
+ if minimum is not None and minimum == maximum:
507
+ size = f"exactly {maximum} items"
508
+ else:
509
+ bounds = [
510
+ text
511
+ for text in (
512
+ f"at least {minimum}" if minimum else "",
513
+ f"at most {maximum}" if maximum is not None else "",
514
+ )
515
+ if text
516
+ ]
517
+ size = "list" + (f" ({', '.join(bounds)})" if bounds else "")
518
+ prefix_items = schema.get("prefixItems")
519
+ if isinstance(prefix_items, list) and prefix_items:
520
+ order = _prefix_order(root, prefix_items)
521
+ first, _ = _resolved(root, prefix_items[0])
522
+ if "properties" in first:
523
+ nested.append((f"{path}[]", prefix_items[0]))
524
+ return f"{size} in this order: {order}; each item (see `{path}[]`)"
525
+ return f"{size} in this order: {order}"
526
+ items = schema.get("items")
527
+ item_schema, _ = _resolved(root, items) if items is not None else ({}, None)
528
+ if "properties" in item_schema:
529
+ nested.append((f"{path}[]", items))
530
+ return f"{size} of objects (see `{path}[]`)"
531
+ if "pattern" in item_schema:
532
+ return f"{size} of strings matching `{item_schema['pattern']}`"
533
+ if isinstance(item_schema.get("enum"), list):
534
+ return f"{size}, each one of " + ", ".join(f"`{value}`" for value in item_schema["enum"])
535
+ if item_schema.get("type"):
536
+ return f"{size} of {item_schema['type']}"
537
+ return size
538
+
539
+
540
+ def _prefix_order(root: dict, prefix_items: list) -> str:
541
+ """고정 순서 배열의 각 자리가 못 박은 값 — `criterion` 처럼 상수인 속성."""
542
+ labels: list[str] = []
543
+ for item in prefix_items:
544
+ merged, _ = _resolved(root, item)
545
+ consts = [
546
+ f"`{key}`=`{value['const']}`"
547
+ for key, value in (merged.get("properties") or {}).items()
548
+ if isinstance(value, dict) and "const" in value
549
+ ]
550
+ labels.append(", ".join(consts) if consts else "item")
551
+ return "; ".join(labels)
552
+
553
+
554
+ def _range(schema: dict) -> str:
555
+ low, high = schema.get("minimum"), schema.get("maximum")
556
+ if low is not None and high is not None:
557
+ return f" {low}..{high}"
558
+ if low is not None:
559
+ return f" >= {low}"
560
+ if high is not None:
561
+ return f" <= {high}"
562
+ return ""
563
+
564
+
263
565
  def validate(data: Any, schema: dict) -> list[str]:
264
566
  """Validate ``data`` against ``schema``. Returns the list of human-
265
567
  readable error messages (empty when the data is valid)."""
@@ -273,12 +273,32 @@ def _items_as_json(items: List[StaleItem]) -> list:
273
273
  "suggested_commit": i.suggested_commit} for i in items]
274
274
 
275
275
 
276
+ _CLI_EPILOG = r"""기본은 검사:
277
+ stale 항목을 JSON 으로 출력하고, confirm 항목이 남으면 exit 2.
278
+ patch-id 로 내용 동일성이 증명되는 항목(auto)은 --apply 로 일괄 보정되고,
279
+ 내용이 바뀐 항목(confirm)은 --stage/--use-ref 로만 보정된다.
280
+
281
+ Usage:
282
+ okstra git-reconcile --plan-run-root <dir> --project-id <id> \
283
+ --task-group <g> --task-id <t> --work-category <c> \
284
+ [--apply] [--stage <N> --use-ref <ref>] [--reset-anchor <ref>] [--json]
285
+
286
+ Exit codes:
287
+ 0 stale 없음 또는 보정 완료
288
+ 2 confirm 항목 잔존 (check: 확인 필요 / apply: 일부 미보정)
289
+ 1 error (resolve 실패 등)
290
+ """
291
+ _CLI_DESCRIPTION = "Okstra \ubc16 git \ud788\uc2a4\ud1a0\ub9ac \ubcc0\uacbd \ud6c4 \uae30\ub85d \ud654\ud574."
292
+
293
+
276
294
  def _parse_args(argv: Optional[list]):
277
295
  import argparse
278
296
 
279
297
  p = argparse.ArgumentParser(
280
- prog="okstra git-reconcile",
281
- epilog="exit codes: 0 = clean/applied, 2 = confirm items remain, 1 = error")
298
+ description=_CLI_DESCRIPTION,
299
+ epilog=_CLI_EPILOG,
300
+ formatter_class=argparse.RawDescriptionHelpFormatter,
301
+ prog="okstra git-reconcile")
282
302
  p.add_argument("--project-root", default=".")
283
303
  p.add_argument("--plan-run-root", required=True,
284
304
  help="consumers.jsonl 이 있는 runs/implementation-planning/ 경로")
@@ -422,10 +422,33 @@ def _parse_stages_csv(raw: str) -> List[int]:
422
422
  return out
423
423
 
424
424
 
425
+ _CLI_EPILOG = r"""JSON 출력.
426
+
427
+ Usage:
428
+ okstra handoff eligible --plan-run-root <dir> --approved-plan <md>
429
+ okstra handoff assemble --plan-run-root <dir> --approved-plan <md> \
430
+ --project-root <dir> --project-id <id> --task-group <g> --task-id <t> \
431
+ --work-category <c> --stages 2,3 --base <branch>
432
+ okstra handoff record-verified --plan-run-root <dir> --stage <N> \
433
+ --report-path <md> --data-json <json>
434
+ okstra handoff record-pr --plan-run-root <dir> --stages 2,3 \
435
+ --branch <b> --url <u>
436
+ okstra handoff local-checkout --project-root <dir> --project-id <id> \
437
+ --task-group <g> --task-id <t> [--stage <N>]
438
+
439
+ Exit codes: 0 ok / 1 자격·전제 위반 / 2 stage 간 merge 충돌(conflicts 동봉)
440
+ """
441
+ _CLI_DESCRIPTION = "Release-handoff stage-group \ubcf4\uc870 (\uc790\uaca9/\uc218\uc9d1/\uae30\ub85d)."
442
+
443
+
425
444
  def main(argv: Optional[list] = None) -> int:
426
445
  import argparse
427
446
 
428
- p = argparse.ArgumentParser(prog="okstra handoff")
447
+ p = argparse.ArgumentParser(
448
+ description=_CLI_DESCRIPTION,
449
+ epilog=_CLI_EPILOG,
450
+ formatter_class=argparse.RawDescriptionHelpFormatter,
451
+ prog="okstra handoff")
429
452
  sub = p.add_subparsers(dest="cmd", required=True)
430
453
 
431
454
  def common(sp, *, plan=True):
@@ -5,7 +5,9 @@ import hashlib as _hashlib
5
5
  import re as _re
6
6
  from typing import Dict
7
7
 
8
- from okstra_project.state import slugify as _slugify
8
+ # leaf 규칙 모듈에서 바로 가져온다. state 를 거치면 okstra_ctl 패키지 __init__ 이
9
+ # 상태 접근자 전체를 끌어와 okstra_project ↔ okstra_ctl 순환이 된다.
10
+ from okstra_project.slug import slugify as _slugify
9
11
 
10
12
  # docker compose project name 길이 상한. compose 는 [a-z0-9][a-z0-9_-]* 만 허용한다.
11
13
  CONTAINER_PROJECT_NAME_MAX = 60
@@ -73,28 +75,16 @@ def _escape_segment_for_join(segment: str) -> str:
73
75
  return segment.replace("-", "--")
74
76
 
75
77
 
76
- def run_id_to_session_name(run_id: str) -> str:
77
- """tmux 세션명으로 변환한다. 'okstra-' 접두사 + 세그먼트 escape 후 `-` 조인.
78
- 예: 'a/b-c/r01' → 'okstra-a-b--c-r01'. 세그먼트 내부 `-` 는 `--` 로
79
- 이스케이프되어, 슬래시 위치가 다른 두 runId 가 동일 세션명으로
80
- 충돌하지 않는다. 세그먼트는 fs-safe 슬러그로 강제 정규화해
81
- `..` 같은 traversal 토큰이 세션명에 그대로 노출되지 않게 한다.
82
- """
83
- parts = [_escape_segment_for_join(_safe_fs_segment(seg))
84
- for seg in run_id.split("/")]
85
- return "okstra-" + "-".join(parts)
86
-
87
-
88
78
  def slugify_task_segment(value: str) -> str:
89
79
  """Match okstra.sh slugify_value for task directory segments."""
90
80
  return _re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
91
81
 
92
82
 
93
- def build_container_session_name(project_id: str, group: str, task_id: str) -> str:
94
- """container detached tmux 세션명을 합친다 → 'okstra-container-<slug>'.
83
+ def build_container_run_trace(project_id: str, group: str, task_id: str) -> str:
84
+ """container 배포의 `okstra.run-trace` 라벨값을 합친다 → 'okstra-container-<slug>'.
95
85
 
96
86
  세그먼트는 task_dir 가 쓰는 slugify_task_segment 와 동일 규칙으로 정규화해
97
- 세션명 slug 가 프로젝트 디스크 경로 slug 와 분기하지 않게 한다.
87
+ 라벨 slug 가 프로젝트 디스크 경로 slug 와 분기하지 않게 한다.
98
88
  """
99
89
  seg = slugify_task_segment
100
90
  return f"okstra-container-{seg(project_id)}-{seg(group)}-{seg(task_id)}"