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
@@ -0,0 +1,135 @@
1
+ """처분(disposition)의 어휘와 그것이 진행을 막는지의 판정.
2
+
3
+ `Blocks` 값과 답변 처분만 보고 결정한다 — 파일도, 표도 열지 않는다. 게이트를
4
+ 읽는 쪽(`.scan`)과 리포트를 읽는 쪽(`.rows`)이 같은 정의를 쓰게 하는 것이
5
+ 이 층이 따로 있는 이유다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Mapping
10
+
11
+
12
+ APPROVAL_BLOCKS = frozenset({"approval"})
13
+
14
+
15
+ USER_INPUT_BLOCKS = frozenset({"approval", "next-phase"})
16
+
17
+
18
+ PROCEEDING_DISPOSITIONS = frozenset({"answer", "select", "accept-risk"})
19
+
20
+
21
+ RETURN_DISPOSITIONS = frozenset({"request-revision", "reject"})
22
+
23
+
24
+ INCORPORATED_LEDGER_DISPOSITIONS = frozenset(
25
+ {"superseded", "no-dependent-statement"}
26
+ )
27
+
28
+
29
+ def clarification_disposition(row: Mapping[str, object]) -> str:
30
+ """행에 기록된 사용자 처분. 없으면 빈 문자열."""
31
+ resolution = row.get("resolution")
32
+ if not isinstance(resolution, Mapping):
33
+ context = row.get("approvalContext")
34
+ if isinstance(context, Mapping):
35
+ resolution = context.get("resolution")
36
+ if isinstance(resolution, Mapping):
37
+ disposition = str(resolution.get("disposition") or "").strip()
38
+ if disposition:
39
+ return disposition
40
+ user_input = str(row.get("userInput") or "").strip()
41
+ options = row.get("options")
42
+ if user_input and isinstance(options, list):
43
+ for option in options:
44
+ if (
45
+ isinstance(option, Mapping)
46
+ and str(option.get("answer") or "").strip() == user_input
47
+ ):
48
+ return str(option.get("disposition") or "").strip()
49
+ return ""
50
+
51
+
52
+ def incorporated_clarification_ids(
53
+ report_data: Mapping[str, object] | None,
54
+ ) -> frozenset[str]:
55
+ """이 런이 답을 본문에 반영했다고 원장에 적은 C-id.
56
+
57
+ `superseded` 와 `no-dependent-statement` 만 센다. 원장에 없는
58
+ `request-revision` / `reject` 는 아직 다음 계획을 막는 되돌림이다.
59
+ """
60
+ if not isinstance(report_data, Mapping):
61
+ return frozenset()
62
+ planning = report_data.get("implementationPlanning")
63
+ if not isinstance(planning, Mapping):
64
+ return frozenset()
65
+ ledger = planning.get("supersessionLedger")
66
+ if not isinstance(ledger, list):
67
+ return frozenset()
68
+ ids: set[str] = set()
69
+ for entry in ledger:
70
+ if not isinstance(entry, Mapping):
71
+ continue
72
+ row_id = str(entry.get("clarificationId") or "").strip()
73
+ disposition = str(entry.get("disposition") or "").strip().lower()
74
+ if row_id and disposition in INCORPORATED_LEDGER_DISPOSITIONS:
75
+ ids.add(row_id)
76
+ return frozenset(ids)
77
+
78
+
79
+ def row_blocks_progress(
80
+ status: str,
81
+ disposition: str = "",
82
+ *,
83
+ incorporated: bool = False,
84
+ ) -> bool:
85
+ """이 행이 승인·다음 단계 진입을 막는가.
86
+
87
+ 진행 처분(`accept-risk` / `select` / `answer`)은 고치지 않은 DISAGREE 를
88
+ 행과 투표에 남긴 채로 게이트만 내린다. `request-revision` / `reject` 는
89
+ 사용자가 진행을 거절한 것이므로 막는다. 다만 이 보고서 원장이 그 답을
90
+ 이미 반영했으면(`incorporated`) 같은 되돌림이 다음 계획 런을 강제하지
91
+ 않는다. 처분이 없는 `answered` 도 판단 기록이므로 막지 않는다.
92
+ """
93
+ normalized_status = status.strip().lower()
94
+ normalized_disposition = disposition.strip().lower()
95
+ if normalized_status == "obsolete":
96
+ return False
97
+ if normalized_disposition in PROCEEDING_DISPOSITIONS:
98
+ return False
99
+ if normalized_disposition in RETURN_DISPOSITIONS:
100
+ return not incorporated
101
+ return normalized_status not in {"answered", "resolved"}
102
+
103
+
104
+ def progress_blocking_ids(
105
+ rows: object,
106
+ blocking_values: frozenset[str] = APPROVAL_BLOCKS,
107
+ *,
108
+ report_data: Mapping[str, object] | None = None,
109
+ ) -> list[str]:
110
+ """게이트를 아직 막는 행 id. 사용자 진행 처분이 있는 행은 빠진다.
111
+
112
+ ``report_data`` 가 있으면 원장에 반영된 되돌림 행도 빠진다.
113
+ """
114
+ if not isinstance(rows, list):
115
+ return []
116
+ incorporated = incorporated_clarification_ids(report_data)
117
+ ids: list[str] = []
118
+ for row in rows:
119
+ if not isinstance(row, Mapping):
120
+ continue
121
+ blocks = str(row.get("blocks") or "").strip().lower()
122
+ status = str(row.get("status") or "").strip()
123
+ row_id = row.get("id")
124
+ if (
125
+ blocks in blocking_values
126
+ and row_blocks_progress(
127
+ status,
128
+ clarification_disposition(row),
129
+ incorporated=isinstance(row_id, str) and row_id in incorporated,
130
+ )
131
+ and isinstance(row_id, str)
132
+ and row_id
133
+ ):
134
+ ids.append(row_id)
135
+ return ids
@@ -0,0 +1,253 @@
1
+ """schema-v1 의 `## 1. Clarification Items` 표를 읽는다.
2
+
3
+ 행 하나가 `ClarificationItem` 이 되고, 그 표를 찾고 걷고 메타 셀을 푸는 것이
4
+ 여기 전부다. 판정(무엇이 진행을 막는가)은 `.dispositions`, 파일을 여는 것은
5
+ `.rows` 다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from ..md_table import is_separator_row, split_pipe_row, to_cell_text
10
+ from dataclasses import dataclass
11
+ from typing import Optional
12
+ import re
13
+
14
+
15
+ SECTION_HEADING_PATTERN = re.compile(
16
+ r'^##\s+1\.\s+Clarification Items\s*(?:<a id="[^"]+"></a>\s*)?$',
17
+ re.MULTILINE,
18
+ )
19
+
20
+
21
+ NEXT_TOP_LEVEL_HEADING_PATTERN = re.compile(r"^##\s+(?!1\.)", re.MULTILINE)
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class ClarificationItem:
26
+ """One row of the §1 table.
27
+
28
+ ``raw_*`` fields preserve the exact cell text (after backtick stripping)
29
+ for diagnostics; canonical lowercased versions live in ``blocks`` /
30
+ ``status`` for predicate use.
31
+ """
32
+ row_id: str
33
+ kind: str # "material" | "decision" | "data-point" | other
34
+ blocks: str # canonical lowercase: "approval" | "next-phase" | "none" | other
35
+ status: str # canonical lowercase: "open" | "answered" | "resolved" | "obsolete" | other
36
+ raw_blocks: str
37
+ raw_status: str
38
+
39
+
40
+ _CELL_ANCHOR_RE = re.compile(r'<a id="([^"]*)"></a>')
41
+
42
+
43
+ def _strip_backticks(cell: str) -> str:
44
+ s = _CELL_ANCHOR_RE.sub("", cell.strip()).strip()
45
+ if s.startswith("`") and s.endswith("`") and len(s) >= 2:
46
+ s = s[1:-1].strip()
47
+ return s
48
+
49
+
50
+ def _split_pipe_row(line: str) -> list[str]:
51
+ """``md_table.split_pipe_row`` + §1-specific cell normalization
52
+ (scroll-anchor removal, outer-backtick unwrap)."""
53
+ return [_strip_backticks(c) for c in split_pipe_row(line)]
54
+
55
+
56
+ def _section_1_slice(report_text: str) -> Optional[str]:
57
+ """Return the substring spanning the §1 section (heading exclusive of the
58
+ next ``##`` heading), or None if §1 is absent."""
59
+ start_match = SECTION_HEADING_PATTERN.search(report_text)
60
+ if not start_match:
61
+ return None
62
+ rest = report_text[start_match.end():]
63
+ end_match = NEXT_TOP_LEVEL_HEADING_PATTERN.search(rest)
64
+ return rest[: end_match.start()] if end_match else rest
65
+
66
+
67
+ _META_ID_RE = re.compile(r"([A-Za-z][A-Za-z0-9]*-\d+)")
68
+
69
+
70
+ def _meta_field(cell: str, key: str) -> str:
71
+ """Extract a ``<key>: value`` field from a stacked §1 meta cell.
72
+
73
+ The §1 table collapses the short columns into one ``<br>``-delimited
74
+ metadata cell (``**C-101**<br>Ticket: `DEV-1`<br>Kind: `decision`<br>
75
+ Blocks: `approval`<br>Status: open``). Values may be backtick-wrapped.
76
+ """
77
+ m = re.search(
78
+ rf"{re.escape(key)}:\s*`?\s*([^`<|]+?)\s*`?\s*(?:<br\s*/?>|$)",
79
+ cell,
80
+ re.IGNORECASE,
81
+ )
82
+ return m.group(1).strip() if m else ""
83
+
84
+
85
+ def _meta_id(cell: str) -> str:
86
+ """The bold headline ID of a §1 meta cell (the part before the first <br>)."""
87
+ headline = re.split(r"<br\s*/?>", cell, maxsplit=1)[0]
88
+ m = _META_ID_RE.search(headline)
89
+ return m.group(1) if m else ""
90
+
91
+
92
+ def parse_meta_cell(cell: str) -> Optional[ClarificationItem]:
93
+ """Parse one §1 stacked meta cell into a ``ClarificationItem``, or ``None``
94
+ when the cell is not a §1 meta cell (no ``Blocks:``/``Status:`` markers —
95
+ e.g. a header or an unrelated table). Shared by the approval-gate parser
96
+ and the HTML view's form-attach pass so both read the cell identically.
97
+ """
98
+ raw_blocks = _meta_field(cell, "Blocks")
99
+ raw_status = _meta_field(cell, "Status")
100
+ if not (raw_blocks and raw_status):
101
+ return None
102
+ return ClarificationItem(
103
+ row_id=_meta_id(cell),
104
+ kind=_meta_field(cell, "Kind").lower(),
105
+ blocks=raw_blocks.lower(),
106
+ status=raw_status.lower(),
107
+ raw_blocks=raw_blocks,
108
+ raw_status=raw_status,
109
+ )
110
+
111
+
112
+ @dataclass(frozen=True)
113
+ class _Section1Table:
114
+ """Outcome of walking the §1 slice for its data table.
115
+
116
+ ``items`` is ``None`` when no recognizable table header exists among the
117
+ pipe lines. ``unparsed_row_count`` counts body rows whose metadata cell
118
+ failed ``parse_meta_cell`` (all-empty filler rows excluded).
119
+ ``has_pipe_lines`` distinguishes the renderer's legitimate table-less
120
+ placeholder (emptyState bullet) from a table whose header drifted.
121
+ """
122
+ items: Optional[list[ClarificationItem]]
123
+ unparsed_row_count: int
124
+ has_pipe_lines: bool
125
+
126
+
127
+ def _walk_section_1_table(section: str) -> _Section1Table:
128
+ lines = section.splitlines()
129
+ has_pipe_lines = any(line.lstrip().startswith("|") for line in lines)
130
+ # Locate the §1 data table by its header. The merged-meta layout collapses
131
+ # ID/Ticket/Kind/Blocks/Status into one metadata cell and keeps the
132
+ # English `Statement` + `User input` columns; detect on those two (any
133
+ # other table — intro, legacy 5.1/5.2 — is rejected).
134
+ header_idx = -1
135
+ for idx, line in enumerate(lines):
136
+ if not line.lstrip().startswith("|"):
137
+ continue
138
+ cells = [c.lower() for c in _split_pipe_row(line)]
139
+ if "user input" in cells and any(c.startswith("statement") for c in cells):
140
+ header_idx = idx
141
+ break
142
+ if header_idx < 0:
143
+ return _Section1Table(None, 0, has_pipe_lines)
144
+
145
+ items: list[ClarificationItem] = []
146
+ unparsed = 0
147
+ body_started = False
148
+ for line in lines[header_idx + 1:]:
149
+ if not line.lstrip().startswith("|"):
150
+ if body_started:
151
+ break
152
+ continue
153
+ if is_separator_row(line):
154
+ body_started = True
155
+ continue
156
+ if not body_started:
157
+ continue
158
+ cells = _split_pipe_row(line)
159
+ if not any(cells):
160
+ continue
161
+ item = parse_meta_cell(cells[0])
162
+ if item is None:
163
+ unparsed += 1
164
+ continue
165
+ items.append(item)
166
+ return _Section1Table(items, unparsed, True)
167
+
168
+
169
+ def parse_clarification_items(report_text: str) -> Optional[list[ClarificationItem]]:
170
+ """Return the list of §1 rows. ``None`` means "no §1 meta table detected"
171
+ (missing section or unrecognized table header) — caller must NOT treat
172
+ that as "table is empty".
173
+
174
+ Lenient view-renderer contract: rows whose metadata cell fails to parse
175
+ are skipped, not surfaced. The approval gate must use
176
+ ``scan_approval_gate`` instead, which fail-closes on those rows.
177
+ """
178
+ section = _section_1_slice(report_text)
179
+ if section is None:
180
+ return None
181
+ return _walk_section_1_table(section).items
182
+
183
+
184
+ def parse_section_1_rows(report_text: str) -> list[dict]:
185
+ """§1 테이블 한 행마다 메타(ClarificationItem) + 원문 Statement / Expected
186
+ form 셀.
187
+
188
+ 입력은 §1 을 담은 마크다운 본문이다 — 리포트 전문일 수도 있고, 다음 run 에
189
+ 첨부되는 carry-in 본문(``clarification_response_with_sidecars``)일 수도
190
+ 있다. §1 셀 추출의 단일 참조점 — 다른 모듈이 §1 레이아웃을 다시 훑지 않도록
191
+ 이 함수로 통일한다.
192
+ """
193
+ section = _section_1_slice(report_text)
194
+ if section is None:
195
+ return []
196
+ lines = section.splitlines()
197
+ header_idx = -1
198
+ for idx, line in enumerate(lines):
199
+ if not line.lstrip().startswith("|"):
200
+ continue
201
+ cells = [c.lower() for c in _split_pipe_row(line)]
202
+ if "user input" in cells and any(c.startswith("statement") for c in cells):
203
+ header_idx = idx
204
+ break
205
+ if header_idx < 0:
206
+ return []
207
+ header = [c.lower() for c in _split_pipe_row(lines[header_idx])]
208
+ s_col = next((j for j, h in enumerate(header) if h.startswith("statement")), -1)
209
+ e_col = next((j for j, h in enumerate(header) if h.startswith("expected form")), -1)
210
+ rows: list[dict] = []
211
+ body = False
212
+ for line in lines[header_idx + 1:]:
213
+ if not line.lstrip().startswith("|"):
214
+ if body:
215
+ break
216
+ continue
217
+ if is_separator_row(line):
218
+ body = True
219
+ continue
220
+ if not body:
221
+ continue
222
+ cells = _split_pipe_row(line)
223
+ item = parse_meta_cell(cells[0]) if cells else None
224
+ if item is None:
225
+ continue
226
+ rows.append({
227
+ "item": item,
228
+ "statement": cells[s_col] if 0 <= s_col < len(cells) else "",
229
+ "expected_form": cells[e_col] if 0 <= e_col < len(cells) else "",
230
+ # v1 cells are strings; the structured options live only in v2's
231
+ # data sibling. The key stays so callers never branch on schema.
232
+ "options": [],
233
+ })
234
+ return rows
235
+
236
+
237
+ _LOOSE_SECTION_1_RE = re.compile(r"^##\s+1\.\s+Clarification Items\b", re.MULTILINE)
238
+
239
+
240
+ def section_1_present_but_unparsed(report_text: str) -> bool:
241
+ """§1 헤딩이 느슨 탐지엔 잡히지만 엄격 SECTION_HEADING_PATTERN 에는 매칭하지
242
+ 못하는 경우 True — 헤딩 형태가 어긋나(앵커·포맷 drift) §1 슬라이스 자체가
243
+ 실패하는 상태다.
244
+
245
+ 이때 ``_section_1_slice`` 가 None 을 반환해 parse 가 통째로 None 이 되고 승인
246
+ 게이트가 "schema 없음 → soft-pass" 로 조용히 열린다. §1 앵커 버그가 정확히 이
247
+ 메커니즘으로 터졌다. 헤딩 자체가 없는 legacy 리포트(둘 다 불매칭)와, 엄격
248
+ 매칭에 성공하는 정상 헤딩(테이블이 없는 emptyState placeholder 포함)은 False —
249
+ placeholder 는 헤딩이 멀쩡하므로 fail-closed 로 오인하지 않는다. 정규식만 넓혀 온
250
+ 과거 수정과 달리, 이 판별은 "헤딩 형태 drift" 자체를 차단해 재발 클래스를 닫는다."""
251
+ if SECTION_HEADING_PATTERN.search(report_text):
252
+ return False
253
+ return bool(_LOOSE_SECTION_1_RE.search(report_text))
@@ -0,0 +1,115 @@
1
+ """리포트에서 clarification 행을 읽는다 — 어느 스키마가 썼든.
2
+
3
+ schema-v2 는 `.data.json` 의 `clarificationItems[]`, schema-v1 은 §1 표다.
4
+ 읽기 함수가 텍스트가 아니라 **경로**를 받는 이유가 이것이다.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from ..final_report_paths import final_report_data_path
9
+ from ..json_boundary import load_owned_object
10
+ from pathlib import Path
11
+ from typing import Optional
12
+ from .parsing import (
13
+ ClarificationItem,
14
+ parse_section_1_rows,
15
+ )
16
+ from .dispositions import (
17
+ clarification_disposition,
18
+ )
19
+
20
+
21
+ STRUCTURED_REPORT_VERSIONS = frozenset({"2.0", "3.0"})
22
+
23
+
24
+ def _read_report_text(report_path: Path) -> str:
25
+ return report_path.read_text(encoding="utf-8", errors="replace")
26
+
27
+
28
+ def _structured_report_data(report_path: Path) -> Optional[dict]:
29
+ """``report_path`` 의 구조화 리포트 레코드, 아니면 ``None``.
30
+
31
+ 파일이 없거나 JSON 이 깨졌거나 구조화 판본이 아니면 ``None`` — 호출자는 v1
32
+ 마크다운 경로로 폴백하고, 진짜 구조화 리포트인데 레코드가 깨진 경우는 그
33
+ 폴백이 "§1 없음" fail-closed 로 잡는다.
34
+
35
+ 판본을 `2.0` 하나로 못박아 두던 동안 3.0 리포트가 여기서 ``None`` 이 됐고,
36
+ 그러면 호출자가 JSON 본문에서 `## 1.` 헤딩을 찾다 실패해 **리포트 전문을
37
+ 그대로 복사**했다 — 이 좁히기가 막으려던 바로 그 중복이다. 실측: 66K 짜리
38
+ 추출본이 나와야 할 자리에 693K data.json 이 스테이징됐고, 패킷의
39
+ `## Clarification Carry-In Extract` 가 0행이 되어 워커가 사용자 답변을
40
+ 전혀 받지 못했다.
41
+ """
42
+ data_path = final_report_data_path(report_path)
43
+ if not data_path.is_file():
44
+ return None
45
+ try:
46
+ data = load_owned_object(data_path, artifact="final report record")
47
+ except (OSError, ValueError):
48
+ return None
49
+ if (
50
+ not isinstance(data, dict)
51
+ or data.get("schemaVersion") not in STRUCTURED_REPORT_VERSIONS
52
+ ):
53
+ return None
54
+ return data
55
+
56
+
57
+ def _v2_row(entry: dict) -> Optional[dict]:
58
+ """``clarificationItems[]`` 한 행을 §1 행과 같은 shape 으로. 필수 필드가
59
+ 빠졌으면 ``None`` — 호출자가 fail-closed 로 셀 수 있게 한다."""
60
+ row_id = entry.get("id")
61
+ raw_blocks = entry.get("blocks")
62
+ raw_status = entry.get("status")
63
+ if not (isinstance(row_id, str) and row_id):
64
+ return None
65
+ if not (isinstance(raw_blocks, str) and raw_blocks):
66
+ return None
67
+ if not (isinstance(raw_status, str) and raw_status):
68
+ return None
69
+ kind = entry.get("kind")
70
+ item = ClarificationItem(
71
+ row_id=row_id,
72
+ kind=kind.lower() if isinstance(kind, str) else "",
73
+ blocks=raw_blocks.lower(),
74
+ status=raw_status.lower(),
75
+ raw_blocks=raw_blocks,
76
+ raw_status=raw_status,
77
+ )
78
+ options = entry.get("options")
79
+ return {
80
+ "item": item,
81
+ "statement": str(entry.get("statement") or ""),
82
+ "expected_form": str(entry.get("expectedForm") or ""),
83
+ "options": (
84
+ [option for option in options if isinstance(option, dict)]
85
+ if isinstance(options, list)
86
+ else []
87
+ ),
88
+ "disposition": clarification_disposition(entry),
89
+ }
90
+
91
+
92
+ def _v2_clarification_rows(report_path: Path) -> Optional[list[dict]]:
93
+ """schema-v2 clarification 행들, 이 리포트가 v2 가 아니면 ``None``.
94
+ 필수 필드가 빠진 행은 건너뛰는 lenient 계약(§1 파서와 동일)."""
95
+ data = _structured_report_data(report_path)
96
+ if data is None:
97
+ return None
98
+ entries = data.get("clarificationItems")
99
+ if not isinstance(entries, list):
100
+ return []
101
+ rows = [_v2_row(e) for e in entries if isinstance(e, dict)]
102
+ return [row for row in rows if row is not None]
103
+
104
+
105
+ def read_clarification_rows(report_path: Path) -> list[dict]:
106
+ """리포트 한 건의 clarification 행 — schema-v2 는 data.json 사이드카에서,
107
+ schema-v1 은 §1 테이블에서. 스키마 버전을 아는 유일한 읽기 지점이다.
108
+
109
+ ``parse_section_1_rows`` 와 같은 lenient 계약: 어느 쪽에서도 행을 못 찾으면
110
+ ``[]``.
111
+ """
112
+ v2_rows = _v2_clarification_rows(report_path)
113
+ if v2_rows is not None:
114
+ return v2_rows
115
+ return parse_section_1_rows(_read_report_text(report_path))