okstra 0.206.0 → 0.207.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 (259) hide show
  1. package/README.md +3 -3
  2. package/dist/cli-registry.mjs +7 -1
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/lifecycle/install.mjs +1 -1
  5. package/dist/commands/lifecycle/install.mjs.map +1 -1
  6. package/docs/architecture/storage-model.md +1 -0
  7. package/docs/architecture.md +40 -16
  8. package/docs/cli.md +17 -15
  9. package/docs/contributor-change-matrix.md +3 -2
  10. package/docs/performance-improvement-plan-v2.md +1 -1
  11. package/docs/project-structure-overview.md +43 -20
  12. package/package.json +1 -1
  13. package/runtime/BUILD.json +2 -2
  14. package/runtime/agents/operations/code-review.json +1 -1
  15. package/runtime/bin/lib/okstra/usage.sh +3 -3
  16. package/runtime/bin/okstra-compact-reminder.sh +1 -1
  17. package/runtime/bin/okstra-spawn-followups.py +2 -2
  18. package/runtime/prompts/duties/direction-selection-worker.json +1 -1
  19. package/runtime/prompts/launch.template.md +2 -2
  20. package/runtime/prompts/lead/adapters/cmux.md +4 -3
  21. package/runtime/prompts/lead/context-loader.md +1 -1
  22. package/runtime/prompts/lead/convergence.md +44 -12
  23. package/runtime/prompts/lead/okstra-lead-contract.md +44 -73
  24. package/runtime/prompts/lead/phase-routing.md +64 -0
  25. package/runtime/prompts/lead/report-writer.md +10 -8
  26. package/runtime/prompts/lead/team-contract.md +1 -1
  27. package/runtime/prompts/profiles/_clarification-recommendation.md +4 -4
  28. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  29. package/runtime/prompts/profiles/_common-contract.md +2 -2
  30. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  31. package/runtime/prompts/profiles/forbidden-actions.json +0 -94
  32. package/runtime/prompts/wizard/prompts.ko.json +2 -1
  33. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -1
  34. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +5 -5
  35. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
  36. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -2
  37. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
  38. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +1 -1
  39. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +17 -26
  40. package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +183 -0
  41. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +60 -10
  42. package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
  43. package/runtime/python/okstra_ctl/agent/prompt_cli/jobs.py +21 -4
  44. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +10 -1
  45. package/runtime/python/okstra_ctl/analysis_inputs.py +0 -39
  46. package/runtime/python/okstra_ctl/analysis_scope.py +31 -0
  47. package/runtime/python/okstra_ctl/approval_decisions.py +32 -2
  48. package/runtime/python/okstra_ctl/asset_roots.py +19 -0
  49. package/runtime/python/okstra_ctl/assignment_resolver.py +8 -0
  50. package/runtime/python/okstra_ctl/blocking_checks.py +7 -0
  51. package/runtime/python/okstra_ctl/code_review_target.py +92 -6
  52. package/runtime/python/okstra_ctl/consumers.py +12 -0
  53. package/runtime/python/okstra_ctl/dispatch_checkpoints.py +121 -0
  54. package/runtime/python/okstra_ctl/dispatch_core.py +54 -32
  55. package/runtime/python/okstra_ctl/dispatch_state.py +34 -5
  56. package/runtime/python/okstra_ctl/doctor.py +2 -1
  57. package/runtime/python/okstra_ctl/domain/provider.py +5 -0
  58. package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -2
  59. package/runtime/python/okstra_ctl/domain/write_policy.py +2 -1
  60. package/runtime/python/okstra_ctl/execution_mutation_audit.py +46 -9
  61. package/runtime/python/okstra_ctl/handoff.py +11 -466
  62. package/runtime/python/okstra_ctl/handoff_error.py +5 -0
  63. package/runtime/python/okstra_ctl/implementation_direction.py +0 -477
  64. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +13 -1
  65. package/runtime/python/okstra_ctl/lead_progress.py +33 -1
  66. package/runtime/python/okstra_ctl/manager_view.py +26 -19
  67. package/runtime/python/okstra_ctl/model_io/lines.py +21 -4
  68. package/runtime/python/okstra_ctl/models.py +4 -1
  69. package/runtime/python/okstra_ctl/operation_invocation.py +11 -2
  70. package/runtime/python/okstra_ctl/option_comparison.py +3 -165
  71. package/runtime/python/okstra_ctl/option_votes.py +3 -191
  72. package/runtime/python/okstra_ctl/paths.py +8 -6
  73. package/runtime/python/okstra_ctl/phases/catalog.py +56 -12
  74. package/runtime/python/okstra_ctl/phases/change_impact_analysis/boundary.json +11 -0
  75. package/runtime/python/okstra_ctl/phases/change_impact_analysis/entry.py +39 -0
  76. package/runtime/python/okstra_ctl/{report_html/view_models/change_impact_analysis.py → phases/change_impact_analysis/report.py} +3 -3
  77. package/runtime/python/okstra_ctl/phases/change_impact_analysis/spec.md +26 -0
  78. package/runtime/python/okstra_ctl/phases/change_impact_analysis/validation.py +23 -0
  79. package/runtime/python/okstra_ctl/phases/error_analysis/__init__.py +1 -0
  80. package/runtime/python/okstra_ctl/phases/error_analysis/boundary.json +9 -0
  81. package/runtime/{prompts/profiles/error-analysis.md → python/okstra_ctl/phases/error_analysis/profile.md} +2 -2
  82. package/runtime/python/okstra_ctl/{report_html/view_models/error_analysis.py → phases/error_analysis/report.py} +9 -8
  83. package/runtime/{templates/reports → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis-input.template.md +1 -1
  84. package/runtime/python/okstra_ctl/phases/error_analysis/spec.md +118 -0
  85. package/runtime/python/okstra_ctl/phases/error_analysis/validation.py +241 -0
  86. package/runtime/python/okstra_ctl/phases/feature_analysis/__init__.py +1 -0
  87. package/runtime/python/okstra_ctl/phases/feature_analysis/boundary.json +8 -0
  88. package/runtime/python/okstra_ctl/phases/feature_analysis/entry.py +63 -0
  89. package/runtime/python/okstra_ctl/{report_html/view_models/feature_analysis.py → phases/feature_analysis/report.py} +12 -5
  90. package/runtime/python/okstra_ctl/phases/feature_analysis/spec.md +22 -0
  91. package/runtime/python/okstra_ctl/phases/feature_analysis/validation.py +27 -0
  92. package/runtime/python/okstra_ctl/phases/feature_analysis/wizard.py +95 -0
  93. package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
  94. package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -2
  95. package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
  96. package/runtime/python/okstra_ctl/phases/final_verification/spec.md +1 -1
  97. package/runtime/python/okstra_ctl/phases/implementation/__init__.py +1 -0
  98. package/runtime/python/okstra_ctl/phases/implementation/boundary.json +17 -0
  99. package/runtime/python/okstra_ctl/{implementation_stage.py → phases/implementation/entry.py} +22 -10
  100. package/runtime/{prompts/host-orchestration/implementation.md → python/okstra_ctl/phases/implementation/host-rules.md} +1 -1
  101. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-deliverable.md +1 -1
  102. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-executor.md +4 -3
  103. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-verifier.md +18 -7
  104. package/runtime/{prompts/profiles/implementation.md → python/okstra_ctl/phases/implementation/profile.md} +5 -5
  105. package/runtime/python/okstra_ctl/{report_html/view_models/implementation.py → phases/implementation/report.py} +3 -3
  106. package/runtime/{templates/reports → python/okstra_ctl/phases/implementation/report_assets}/implementation-input.template.md +1 -1
  107. package/runtime/python/okstra_ctl/phases/implementation/spec.md +238 -0
  108. package/runtime/python/okstra_ctl/phases/implementation/validation.py +205 -0
  109. package/runtime/python/okstra_ctl/phases/implementation/wizard.py +39 -0
  110. package/runtime/python/okstra_ctl/phases/implementation_option_selection/__init__.py +1 -0
  111. package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +80 -0
  112. package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +10 -0
  113. package/runtime/python/okstra_ctl/phases/implementation_option_selection/comparison.py +168 -0
  114. package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +27 -0
  115. package/runtime/{prompts/profiles/implementation-option-selection.md → python/okstra_ctl/phases/implementation_option_selection/profile.md} +3 -3
  116. package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
  117. package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +83 -0
  118. package/runtime/python/okstra_ctl/{implementation_options.py → phases/implementation_option_selection/validation.py} +3 -3
  119. package/runtime/python/okstra_ctl/phases/implementation_option_selection/votes.py +194 -0
  120. package/runtime/python/okstra_ctl/phases/implementation_planning/__init__.py +1 -0
  121. package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +2345 -0
  122. package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +12 -0
  123. package/runtime/python/okstra_ctl/phases/implementation_planning/entry.py +161 -0
  124. package/runtime/python/okstra_ctl/phases/implementation_planning/guidance.py +178 -0
  125. package/runtime/{prompts/lead → python/okstra_ctl/phases/implementation_planning/instructions}/plan-body-verification.md +61 -51
  126. package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +3295 -0
  127. package/runtime/{prompts/profiles/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/profile.md} +74 -25
  128. package/runtime/python/okstra_ctl/phases/implementation_planning/report.py +237 -0
  129. package/runtime/{templates/reports → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning-input.template.md +2 -2
  130. package/runtime/python/okstra_ctl/phases/implementation_planning/spec.md +204 -0
  131. package/runtime/python/okstra_ctl/phases/implementation_planning/validation.py +597 -0
  132. package/runtime/python/okstra_ctl/phases/implementation_planning/wizard.py +166 -0
  133. package/runtime/python/okstra_ctl/phases/improvement_discovery/boundary.json +12 -0
  134. package/runtime/python/okstra_ctl/{improvement_lenses.py → phases/improvement_discovery/lenses.py} +1 -6
  135. package/runtime/{prompts/profiles/improvement-discovery.md → python/okstra_ctl/phases/improvement_discovery/profile.md} +5 -5
  136. package/runtime/python/okstra_ctl/{report_html/view_models/improvement_discovery.py → phases/improvement_discovery/report.py} +3 -3
  137. package/runtime/{templates/reports → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery-input.template.md +1 -2
  138. package/runtime/python/okstra_ctl/phases/improvement_discovery/spec.md +29 -0
  139. package/runtime/{validators/validate_improvement_report.py → python/okstra_ctl/phases/improvement_discovery/validation.py} +5 -14
  140. package/runtime/python/okstra_ctl/phases/project_analysis/__init__.py +1 -0
  141. package/runtime/python/okstra_ctl/phases/project_analysis/boundary.json +8 -0
  142. package/runtime/python/okstra_ctl/phases/project_analysis/entry.py +11 -0
  143. package/runtime/python/okstra_ctl/{report_html/view_models/project_analysis.py → phases/project_analysis/report.py} +3 -3
  144. package/runtime/python/okstra_ctl/phases/project_analysis/spec.md +33 -0
  145. package/runtime/python/okstra_ctl/phases/project_analysis/validation.py +55 -0
  146. package/runtime/python/okstra_ctl/phases/release_handoff/__init__.py +1 -0
  147. package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +17 -0
  148. package/runtime/python/okstra_ctl/phases/release_handoff/entry.py +147 -0
  149. package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +446 -0
  150. package/runtime/{prompts/profiles/release-handoff.md → python/okstra_ctl/phases/release_handoff/profile.md} +3 -3
  151. package/runtime/python/okstra_ctl/{report_html/view_models/release_handoff.py → phases/release_handoff/report.py} +3 -3
  152. package/runtime/{templates/reports → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff-input.template.md +1 -1
  153. package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +233 -0
  154. package/runtime/python/okstra_ctl/phases/release_handoff/wizard.py +84 -0
  155. package/runtime/python/okstra_ctl/phases/requirements_discovery/__init__.py +1 -0
  156. package/runtime/python/okstra_ctl/phases/requirements_discovery/boundary.json +9 -0
  157. package/runtime/{prompts/profiles/requirements-discovery.md → python/okstra_ctl/phases/requirements_discovery/profile.md} +2 -3
  158. package/runtime/python/okstra_ctl/{report_html/view_models/requirements_discovery.py → phases/requirements_discovery/report.py} +3 -3
  159. package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +132 -0
  160. package/runtime/{validators/validate_fanout.py → python/okstra_ctl/phases/requirements_discovery/validation.py} +11 -12
  161. package/runtime/python/okstra_ctl/phases/technical_verification/__init__.py +1 -0
  162. package/runtime/python/okstra_ctl/phases/technical_verification/boundary.json +9 -0
  163. package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +100 -0
  164. package/runtime/{prompts/profiles/technical-verification.md → python/okstra_ctl/phases/technical_verification/profile.md} +2 -2
  165. package/runtime/python/okstra_ctl/{report_html/view_models/technical_verification.py → phases/technical_verification/report.py} +2 -2
  166. package/runtime/python/okstra_ctl/phases/technical_verification/spec.md +37 -0
  167. package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +90 -0
  168. package/runtime/python/okstra_ctl/plan_approval.py +70 -0
  169. package/runtime/python/okstra_ctl/plan_items_cli.py +2 -2130
  170. package/runtime/python/okstra_ctl/process_group.py +118 -0
  171. package/runtime/python/okstra_ctl/profile_show.py +3 -3
  172. package/runtime/python/okstra_ctl/render.py +15 -4
  173. package/runtime/python/okstra_ctl/report_assembly.py +28 -92
  174. package/runtime/python/okstra_ctl/report_finalize.py +106 -2
  175. package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
  176. package/runtime/python/okstra_ctl/report_projections.py +1 -36
  177. package/runtime/python/okstra_ctl/report_routing.py +23 -0
  178. package/runtime/python/okstra_ctl/report_synthesis_packet.py +4 -73
  179. package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
  180. package/runtime/python/okstra_ctl/report_views.py +1 -1
  181. package/runtime/python/okstra_ctl/run.py +68 -350
  182. package/runtime/python/okstra_ctl/run_artifact_prune.py +200 -0
  183. package/runtime/python/okstra_ctl/stage_map.py +13 -0
  184. package/runtime/python/okstra_ctl/team.py +108 -9
  185. package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
  186. package/runtime/python/okstra_ctl/wizard/__init__.py +31 -31
  187. package/runtime/python/okstra_ctl/wizard/api.py +18 -0
  188. package/runtime/python/okstra_ctl/wizard/outcome.py +3 -12
  189. package/runtime/python/okstra_ctl/wizard/registry.py +20 -12
  190. package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
  191. package/runtime/python/okstra_ctl/wizard/steps_options.py +8 -0
  192. package/runtime/python/okstra_ctl/wizard/steps_plan.py +10 -263
  193. package/runtime/python/okstra_ctl/wizard/steps_roles.py +2 -1
  194. package/runtime/python/okstra_ctl/work_categories.py +1 -1
  195. package/runtime/python/okstra_ctl/worker_dispatch.py +44 -3
  196. package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
  197. package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -0
  198. package/runtime/python/okstra_ctl/worker_runner.py +21 -3
  199. package/runtime/python/okstra_ctl/workflow.py +26 -143
  200. package/runtime/python/okstra_ctl/write_policy.py +57 -7
  201. package/runtime/python/okstra_project/dirs.py +14 -0
  202. package/runtime/python/okstra_project/resolver.py +2 -1
  203. package/runtime/schemas/execution-manifest-v2.schema.json +2 -1
  204. package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
  205. package/runtime/skills/okstra-code-review/SKILL.md +70 -32
  206. package/runtime/skills/okstra-code-review/references/review-calibration.md +26 -6
  207. package/runtime/skills/okstra-run/SKILL.md +3 -3
  208. package/runtime/templates/manager/view.template.html +18 -1
  209. package/runtime/templates/reports/quick-input.template.md +1 -1
  210. package/runtime/templates/reports/task-brief.template.md +1 -1
  211. package/runtime/validators/validate-brief.py +2 -2
  212. package/runtime/validators/validate-run.py +299 -3940
  213. package/runtime/validators/validate_analysis_report.py +14 -126
  214. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +0 -147
  215. package/runtime/python/okstra_ctl/technical_verification.py +0 -195
  216. /package/runtime/{prompts/profiles/change-impact-analysis.json → python/okstra_ctl/phases/change_impact_analysis/profile.json} +0 -0
  217. /package/runtime/{prompts/profiles/change-impact-analysis.md → python/okstra_ctl/phases/change_impact_analysis/profile.md} +0 -0
  218. /package/runtime/{templates/reports → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis-input.template.md +0 -0
  219. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.html +0 -0
  220. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.md +0 -0
  221. /package/runtime/{prompts/profiles/error-analysis.json → python/okstra_ctl/phases/error_analysis/profile.json} +0 -0
  222. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.html +0 -0
  223. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.md +0 -0
  224. /package/runtime/{prompts/profiles/feature-analysis.json → python/okstra_ctl/phases/feature_analysis/profile.json} +0 -0
  225. /package/runtime/{prompts/profiles/feature-analysis.md → python/okstra_ctl/phases/feature_analysis/profile.md} +0 -0
  226. /package/runtime/{templates/reports → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis-input.template.md +0 -0
  227. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.html +0 -0
  228. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.md +0 -0
  229. /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-diff-review.md +0 -0
  230. /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-self-check.md +0 -0
  231. /package/runtime/{prompts/profiles/implementation.json → python/okstra_ctl/phases/implementation/profile.json} +0 -0
  232. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.html +0 -0
  233. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.md +0 -0
  234. /package/runtime/{prompts/profiles/implementation-option-selection.json → python/okstra_ctl/phases/implementation_option_selection/profile.json} +0 -0
  235. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.html +0 -0
  236. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.md +0 -0
  237. /package/runtime/{prompts/host-orchestration/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/host-rules.md} +0 -0
  238. /package/runtime/{prompts/profiles/implementation-planning.json → python/okstra_ctl/phases/implementation_planning/profile.json} +0 -0
  239. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.html +0 -0
  240. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.md +0 -0
  241. /package/runtime/{prompts/profiles/improvement-discovery.json → python/okstra_ctl/phases/improvement_discovery/profile.json} +0 -0
  242. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.html +0 -0
  243. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.md +0 -0
  244. /package/runtime/{prompts/profiles/project-analysis.json → python/okstra_ctl/phases/project_analysis/profile.json} +0 -0
  245. /package/runtime/{prompts/profiles/project-analysis.md → python/okstra_ctl/phases/project_analysis/profile.md} +0 -0
  246. /package/runtime/{templates/reports → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis-input.template.md +0 -0
  247. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.html +0 -0
  248. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.md +0 -0
  249. /package/runtime/{prompts/profiles/release-handoff.json → python/okstra_ctl/phases/release_handoff/profile.json} +0 -0
  250. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.html +0 -0
  251. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.md +0 -0
  252. /package/runtime/python/okstra_ctl/{fanout.py → phases/requirements_discovery/fanout.py} +0 -0
  253. /package/runtime/{prompts/profiles/requirements-discovery.json → python/okstra_ctl/phases/requirements_discovery/profile.json} +0 -0
  254. /package/runtime/{templates/reports → python/okstra_ctl/phases/requirements_discovery/report_assets}/fan-out-unit.template.md +0 -0
  255. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.html +0 -0
  256. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.md +0 -0
  257. /package/runtime/{prompts/profiles/technical-verification.json → python/okstra_ctl/phases/technical_verification/profile.json} +0 -0
  258. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.html +0 -0
  259. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.md +0 -0
@@ -0,0 +1,200 @@
1
+ """run 이 끝난 뒤 읽는 곳이 없는 산출물을 찾고 지운다.
2
+
3
+ - 의존성·빌드 디렉터리(`node_modules`, `.next`): technical-verification 워커는
4
+ `experiments/<seq>/<worker-id>/` 에 사이트 복사본을 만들고 그 안에서 설치·빌드한다.
5
+ 증거는 계획·로그·lockfile·diff 이고, 설치·빌드 결과는 lockfile 로 다시 만든다. 태스크
6
+ 하나에 `node_modules` 46개와 `.next` 16개(약 22GB)가 남은 사례가 있다(2026-09-27).
7
+ - dispatch 스냅샷(`*.mutation-audit.json`)과 프롬프트 발행 잠금(`*.publish.lock`): 워커
8
+ 시도의 종결 판정과 같은 run 의 증거 복구만 읽는다. 결과는 시도 기록의 변경 요약에
9
+ 남고, `validate-run` 은 경로 문자열만 본다. 검증을 통과한 run 과, 같은 run 디렉터리에서
10
+ 더 뒤의 run 이 검증을 통과한 run 의 dispatch 기록이 가리키는 것만 지운다 — 가장 최근의
11
+ 실패한 run 은 같은 run 안에서 워커를 다시 보낼 수 있다.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import json
17
+ import os
18
+ import re
19
+ import shutil
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from okstra_project import resolve_project_root
24
+
25
+ from .fixed_text import line
26
+ from .json_boundary import JsonBoundaryError, load_owned_object
27
+ from .paths import task_dir
28
+
29
+ PRUNED_DIR_NAMES = frozenset({"node_modules", ".next"})
30
+ _DISPATCH_ARRAYS = ("agentDispatches", "workerDispatches")
31
+ _MANIFEST_SEQ = re.compile(r"^run-manifest-.+-(\d{3,})\.json$")
32
+
33
+
34
+ def _tree_bytes(root: Path) -> int:
35
+ total = 0
36
+ for current, _dirs, files in os.walk(root):
37
+ for name in files:
38
+ try:
39
+ total += os.lstat(os.path.join(current, name)).st_size
40
+ except OSError:
41
+ continue
42
+ return total
43
+
44
+
45
+ def find_build_dirs(root: Path) -> list[dict[str, Any]]:
46
+ """`root` 아래 의존성·빌드 디렉터리. 심볼릭 링크는 따라가지도 지우지도 않는다."""
47
+ found: list[dict[str, Any]] = []
48
+ # `.okstra` 아래는 `Path.glob("**")` 이 숨김 디렉터리를 건너뛰므로 os.walk 로 훑는다.
49
+ for current, dirs, _files in os.walk(root):
50
+ kept = []
51
+ for name in dirs:
52
+ path = Path(current) / name
53
+ if name in PRUNED_DIR_NAMES and not path.is_symlink():
54
+ found.append({"path": str(path), "bytes": _tree_bytes(path)})
55
+ else:
56
+ kept.append(name)
57
+ dirs[:] = kept
58
+ return sorted(found, key=lambda row: row["path"])
59
+
60
+
61
+ def _resolve(project_root: Path, value: str) -> Path:
62
+ path = Path(value)
63
+ return path if path.is_absolute() else project_root / path
64
+
65
+
66
+ def _settled_manifests(manifests_dir: Path, names: list[str]) -> list[dict[str, Any]]:
67
+ """검증을 통과했거나, 같은 디렉터리의 더 뒤 run 이 통과한 run 매니페스트.
68
+
69
+ 한 `manifests/` 의 seq 는 한 카운터에서 나오므로 숫자 순서가 실행 순서다.
70
+ """
71
+ loaded: list[tuple[int, dict[str, Any]]] = []
72
+ for name in names:
73
+ match = _MANIFEST_SEQ.match(name)
74
+ if match is None:
75
+ continue
76
+ try:
77
+ manifest = load_owned_object(manifests_dir / name, artifact="run manifest")
78
+ except (OSError, JsonBoundaryError):
79
+ continue
80
+ loaded.append((int(match[1]), manifest))
81
+ passed = [
82
+ seq for seq, manifest in loaded
83
+ if (manifest.get("validation") or {}).get("status") == "passed"
84
+ ]
85
+ if not passed:
86
+ return []
87
+ return [manifest for seq, manifest in loaded if seq <= max(passed)]
88
+
89
+
90
+ def find_dispatch_leftovers(project_root: Path, root: Path) -> list[dict[str, Any]]:
91
+ """`root` 아래 끝난 run(`_settled_manifests`)의 dispatch 스냅샷과 발행 잠금."""
92
+ found: dict[str, int] = {}
93
+ for current, dirs, files in os.walk(root):
94
+ dirs[:] = [name for name in dirs if name not in PRUNED_DIR_NAMES]
95
+ if Path(current).name != "manifests":
96
+ continue
97
+ for manifest in _settled_manifests(Path(current), files):
98
+ try:
99
+ team_state = load_owned_object(
100
+ _resolve(project_root, str(manifest.get("teamStatePath") or "")),
101
+ artifact="team state",
102
+ )
103
+ except (OSError, JsonBoundaryError):
104
+ continue
105
+ for key in _DISPATCH_ARRAYS:
106
+ for row in team_state.get(key) or ():
107
+ if not isinstance(row, dict):
108
+ continue
109
+ candidates = [row.get("mutationAuditSnapshotPath")]
110
+ if row.get("promptPath"):
111
+ candidates.append(f"{row['promptPath']}.publish.lock")
112
+ for value in candidates:
113
+ if not isinstance(value, str) or not value:
114
+ continue
115
+ path = _resolve(project_root, value)
116
+ if path.is_file() and not path.is_symlink():
117
+ found[str(path)] = path.stat().st_size
118
+ return [{"path": path, "bytes": size} for path, size in sorted(found.items())]
119
+
120
+
121
+ def prune_run_artifacts(project_root: Path, root: Path, *, apply: bool) -> dict[str, Any]:
122
+ dirs = find_build_dirs(root) if root.is_dir() else []
123
+ files = find_dispatch_leftovers(project_root, root) if root.is_dir() else []
124
+ if apply:
125
+ for row in dirs:
126
+ shutil.rmtree(row["path"])
127
+ for row in files:
128
+ Path(row["path"]).unlink(missing_ok=True)
129
+ return {
130
+ "root": str(root),
131
+ "applied": apply,
132
+ "dirs": dirs,
133
+ "files": files,
134
+ "totalBytes": sum(row["bytes"] for row in (*dirs, *files)),
135
+ }
136
+
137
+
138
+ def render_result_text(result: dict[str, Any]) -> str:
139
+ parts = [
140
+ "# Okstra Run Artifact Prune\n\n",
141
+ line("Root", result.get("root")),
142
+ line("Applied", result.get("applied")),
143
+ line("Directory count", len(result.get("dirs") or [])),
144
+ line("File count", len(result.get("files") or [])),
145
+ line("Total bytes", result.get("totalBytes")),
146
+ ]
147
+ for row in result.get("dirs") or []:
148
+ parts.extend(("\n## Directory\n\n", line("Path", row["path"]), line("Bytes", row["bytes"])))
149
+ return "".join(parts)
150
+
151
+
152
+ _CLI_EPILOG = r"""Usage:
153
+ okstra prune-run-artifacts [--project-root <dir>] [--cwd <dir>]
154
+ [--task-group <group> --task-id <id>] [--apply] [--text]
155
+
156
+ Lists what no reader needs once a run has ended, under
157
+ <project-root>/.okstra/tasks (or one task root), with sizes and a total:
158
+ - every node_modules and .next directory (symbolic links are neither
159
+ followed nor removed);
160
+ - the dispatch snapshots (*.mutation-audit.json) and prompt publication
161
+ locks (*.publish.lock) recorded by a run whose validation passed, or by
162
+ an earlier run in the same run directory as a run that passed.
163
+ Nothing is removed without --apply. Source copies, logs, lockfiles, diffs,
164
+ prompts and results stay. --text lists directories only; files are counted.
165
+ """
166
+
167
+
168
+ def main(argv: list[str] | None = None) -> int:
169
+ parser = argparse.ArgumentParser(
170
+ epilog=_CLI_EPILOG,
171
+ formatter_class=argparse.RawDescriptionHelpFormatter,
172
+ prog="okstra prune-run-artifacts",
173
+ description="List, and with --apply remove, run artifacts no reader needs after the run.")
174
+ parser.add_argument("--project-root", default="")
175
+ parser.add_argument("--cwd", default=".")
176
+ parser.add_argument("--task-group", default="")
177
+ parser.add_argument("--task-id", default="")
178
+ parser.add_argument("--apply", action="store_true", help="remove the listed paths")
179
+ parser.add_argument("--text", action="store_true", help="emit fixed text fields")
180
+ args = parser.parse_args(argv)
181
+ if bool(args.task_group) != bool(args.task_id):
182
+ parser.error("--task-group and --task-id go together")
183
+
184
+ project_root = resolve_project_root(explicit_root=args.project_root, cwd=args.cwd)
185
+ root = (
186
+ task_dir(project_root, args.task_group, args.task_id)
187
+ if args.task_group else project_root / ".okstra" / "tasks"
188
+ )
189
+ result = {
190
+ "ok": True,
191
+ "projectRoot": str(project_root),
192
+ **prune_run_artifacts(project_root, root, apply=args.apply),
193
+ }
194
+ output = render_result_text(result) if args.text else json.dumps(result, ensure_ascii=False, indent=2)
195
+ print(output, end="" if args.text else "\n")
196
+ return 0
197
+
198
+
199
+ if __name__ == "__main__":
200
+ raise SystemExit(main())
@@ -641,3 +641,16 @@ def _resolve_plan_path(task_root: Path, value: str) -> Path:
641
641
  # 예전에는 okstra_project.state 가 이 어댑터를 들고 okstra_ctl 을 함수 본문에서
642
642
  # 지연 import 했다. 하위 계층이 상위를 되짚는 배치였다. 읽는 대상이 okstra_ctl 의
643
643
  # Stage Map 과 consumer 상태이므로 소유자를 여기로 옮긴다.
644
+
645
+
646
+ def parse_stage_map_for_prepare(plan_path: str) -> list:
647
+ """Parse the approved plan into context records for execution consumers."""
648
+ from .prepare_error import PrepareError
649
+
650
+ try:
651
+ return stage_map_records(parse_stage_map_file(Path(plan_path)))
652
+ except StageMapError as exc:
653
+ raise PrepareError(
654
+ "approved-plan 의 Stage Map 을 신뢰할 수 없어 거부합니다 "
655
+ f"({plan_path}): {exc.reason}. plan 의 Stage Map 을 점검하세요."
656
+ ) from exc
@@ -36,6 +36,12 @@ from .dispatch_core import (
36
36
  await_dispatches,
37
37
  dispatch_plan,
38
38
  )
39
+ from .dispatch_checkpoints import (
40
+ record_collect_checkpoints,
41
+ record_dispatch_checkpoints,
42
+ settled_initial_dispatches,
43
+ )
44
+ from .lead_progress import record_checkpoint, render_progress_line
39
45
  from .ports.worker_dispatch import WorkerDispatchPort, WorkerDispatchRequest
40
46
  from .registry.host_registry import default_host_registry
41
47
  from .registry.provider_registry import default_provider_registry
@@ -44,6 +50,7 @@ from .json_boundary import load_owned_object
44
50
 
45
51
 
46
52
  _SUPPORTED_WRAPPERS = provider_worker_wrappers(default_provider_registry())
53
+ _SOURCE = "okstra team"
47
54
 
48
55
 
49
56
  def main(argv: Sequence[str] | None = None) -> int:
@@ -73,7 +80,7 @@ _CLI_EPILOG = r"""Usage:
73
80
  [--poll-interval-seconds <n>] [--timeout-seconds <n>] \
74
81
  [--heartbeat-seconds <n>] [--json]
75
82
  okstra team reclaim --project-root <dir> --run-manifest <path> \
76
- [--dry-run] [--json]
83
+ [--dry-run] [--gate] [--json]
77
84
  okstra team teardown --project-root <dir> --run-manifest <path> \
78
85
  [--dry-run] [--json]
79
86
 
@@ -84,11 +91,23 @@ descriptor owns the team lifecycle (launch_mode=team) — a worker there is a CL
84
91
  wrapper subprocess holding no pane, so there is nothing to close.
85
92
 
86
93
  reclaim round boundary: closes the panes of dispatches that have finished and
87
- leaves the in-progress ones open. Run it with --dry-run first to
88
- count, then again to close.
94
+ leaves the in-progress ones open, then on a cmux-pane run records
95
+ `PROGRESS: phase-batch-cleanup panes=<n>`. With --gate it is the
96
+ cleanup before a user gate and prints
97
+ `PROGRESS: phase-gate-cleanup panes=<n>` without recording it.
98
+
89
99
  teardown end of run: closes every recorded pane and writes off any dispatch
90
100
  that never reached a terminal status.
91
101
 
102
+ On a cmux-pane run, dispatch records `phase-3-team-create` when it writes the
103
+ implicit-team marker, `phase-4-dispatch` for each `initial` job and
104
+ `phase-6-synthesis` on the first report-writer dispatch; await records
105
+ `phase-5-poll` on entry and again when the counts changed, and
106
+ `phase-5-collect` for each `initial` dispatch it settled. The lead does not
107
+ call `okstra lead-progress append` for those checkpoints; each command prints
108
+ the `PROGRESS:` lines to emit (`progressLines` in JSON output). On any other
109
+ backend the team commands record nothing.
110
+
92
111
  --workspace-root and --okstra-bin are owned by this command.
93
112
  """
94
113
  _CLI_DESCRIPTION = "Dispatch, await, and close okstra-owned worker panes."
@@ -148,6 +167,10 @@ def _add_reclaim_parser(sub) -> None:
148
167
  )
149
168
  _add_run_args(parser)
150
169
  parser.add_argument("--dry-run", action="store_true")
170
+ parser.add_argument(
171
+ "--gate", action="store_true",
172
+ help="cleanup before a user gate: print phase-gate-cleanup, record nothing",
173
+ )
151
174
  parser.add_argument("--json", action="store_true")
152
175
 
153
176
 
@@ -179,11 +202,31 @@ def _dispatch(args) -> int:
179
202
  if args.dry_run:
180
203
  _print_json(backend_plan.to_payload(dry_run=True))
181
204
  return 0
182
- result = dispatch_plan(backend_plan, wait=False)
183
- _print_json(backend_plan.to_payload(dry_run=False))
205
+ lines: list[str] = []
206
+ result = dispatch_plan(
207
+ backend_plan, wait=False,
208
+ before_start=lambda prepared: lines.extend(
209
+ _record_dispatch(args, manifest, prepared)
210
+ ),
211
+ )
212
+ payload = backend_plan.to_payload(dry_run=False)
213
+ payload["progressLines"] = lines
214
+ _print_json(payload)
184
215
  return result
185
216
 
186
217
 
218
+ def _record_dispatch(args, manifest: Mapping[str, Any], prepared) -> list[str]:
219
+ # cmux-pane run 만 체크포인트를 team 명령에 맡긴다. external 리드는 CLI 워커도
220
+ # `team await` 로 기다리지만 체크포인트는 직접 append 하므로, 여기서 쓰면 행이 둘이 된다.
221
+ if not _is_cmux_run(manifest):
222
+ return []
223
+ return record_dispatch_checkpoints(
224
+ prepared.project_root, args.run_manifest,
225
+ _load_json(prepared.team_state_path, "team-state"), prepared.jobs,
226
+ source=_SOURCE,
227
+ )
228
+
229
+
187
230
  def team_dispatch_port(manifest: Mapping[str, Any]) -> WorkerDispatchPort:
188
231
  """이 run 의 워커를 띄울 포트. cmux run 이면 pane, 아니면 리드 호스트의 포트.
189
232
 
@@ -209,19 +252,59 @@ def _await(args) -> int:
209
252
  _validate_team_manifest(manifest)
210
253
  _observe_lead_session_from_manifest(Path(args.project_root).resolve(), manifest)
211
254
  plan = _plan_for_existing(Path(args.project_root), Path(args.workspace_root), Path(args.run_manifest), manifest)
255
+ should_record = _is_cmux_run(manifest)
256
+ entry_state = _load_json(plan.team_state_path, "team-state")
257
+ entry = _dispatch_counts(entry_state)
258
+ lines = [_record_poll(args, entry)] if should_record else []
212
259
  code = await_dispatches(
213
260
  plan,
214
261
  poll_interval_seconds=args.poll_interval_seconds,
215
262
  timeout_seconds=args.timeout_seconds,
216
263
  heartbeat_seconds=0 if args.json else args.heartbeat_seconds,
217
264
  )
265
+ if should_record:
266
+ exit_state = _load_json(plan.team_state_path, "team-state")
267
+ lines.extend(record_collect_checkpoints(
268
+ plan.project_root, args.run_manifest,
269
+ settled_initial_dispatches(entry_state),
270
+ settled_initial_dispatches(exit_state),
271
+ source=_SOURCE,
272
+ ))
273
+ exit_counts = _dispatch_counts(exit_state)
274
+ if exit_counts != entry:
275
+ lines.append(_record_poll(args, exit_counts))
276
+ lines = [line for line in lines if line]
218
277
  if args.json:
219
- _print_json(_await_payload(plan, code == 0))
278
+ payload = _await_payload(plan, code == 0)
279
+ payload["progressLines"] = lines
280
+ _print_json(payload)
220
281
  else:
282
+ for line in lines:
283
+ print(line)
221
284
  print("ALL_WORKERS_DONE" if code == 0 else "POLL_TIMEOUT")
222
285
  return code
223
286
 
224
287
 
288
+ def _dispatch_counts(team_state: Mapping[str, Any]) -> tuple[int, int]:
289
+ """이 run 의 dispatch 행 기준 (pending, done) — `phase-5-poll` 의 두 값."""
290
+ statuses = [
291
+ record.get("status")
292
+ for record in team_state.get("workerDispatches", [])
293
+ if isinstance(record, dict)
294
+ ]
295
+ done = sum(1 for status in statuses if status in TERMINAL_WORKER_STATUSES)
296
+ return len(statuses) - done, done
297
+
298
+
299
+ def _record_poll(args, counts: tuple[int, int]) -> str | None:
300
+ pending, done = counts
301
+ return record_checkpoint(
302
+ Path(args.project_root), args.run_manifest, "phase-5-poll",
303
+ source=_SOURCE,
304
+ fields=(("pending", str(pending)), ("done", str(done))),
305
+ )
306
+
307
+
225
308
  def _teardown(args) -> int:
226
309
  manifest = _load_manifest(args.project_root, args.run_manifest)
227
310
  _validate_team_manifest(manifest)
@@ -259,7 +342,16 @@ def _reclaim(args) -> int:
259
342
  _emit_panes(args.json, panes)
260
343
  return 0
261
344
  _close_panes(manifest, panes)
262
- _emit_panes(args.json, panes)
345
+ count = (("panes", str(len(panes))),)
346
+ line: str | None = None
347
+ if args.gate:
348
+ line = render_progress_line("phase-gate-cleanup", count)
349
+ elif _is_cmux_run(manifest):
350
+ line = record_checkpoint(
351
+ Path(args.project_root), args.run_manifest, "phase-batch-cleanup",
352
+ source=_SOURCE, fields=count,
353
+ )
354
+ _emit_panes(args.json, panes, line)
263
355
  return 0
264
356
 
265
357
 
@@ -434,12 +526,19 @@ def _mark_teardown_errors(team_state_path: Path) -> None:
434
526
  mutate_team_state(team_state_path, mark)
435
527
 
436
528
 
437
- def _emit_panes(as_json: bool, panes: list[dict[str, str]]) -> None:
529
+ def _emit_panes(
530
+ as_json: bool, panes: list[dict[str, str]], progress_line: str | None = None
531
+ ) -> None:
438
532
  if as_json:
439
- _print_json({"panes": panes})
533
+ payload: dict[str, Any] = {"panes": panes}
534
+ if progress_line:
535
+ payload["progressLines"] = [progress_line]
536
+ _print_json(payload)
440
537
  return
441
538
  for pane in panes:
442
539
  print(f"{pane['paneId']}\t{pane['kind']}")
540
+ if progress_line:
541
+ print(progress_line)
443
542
 
444
543
 
445
544
  def _manifest_backend(manifest: Mapping[str, Any]) -> str:
@@ -0,0 +1,52 @@
1
+ """방향 비교와 기술 검증이 함께 읽는 미확정 사실 계약."""
2
+ from __future__ import annotations
3
+
4
+ from collections.abc import Mapping
5
+ from typing import Any, TypedDict
6
+
7
+ from .clarification_items import USER_INPUT_BLOCKS, progress_blocking_ids
8
+
9
+
10
+ class VerificationFact(TypedDict):
11
+ id: str
12
+ candidateId: str
13
+ factIndex: int
14
+ fact: str
15
+ whyItMatters: str
16
+ evidence: list[str]
17
+
18
+
19
+ class TechnicalVerificationError(ValueError):
20
+ """시험 입력 또는 관측 결과가 원본 사실과 일치하지 않는다."""
21
+
22
+
23
+ def technical_verification_facts(data: Mapping[str, Any]) -> list[VerificationFact]:
24
+ """안전 차단이 없는 후보의 명시적인 기술 검증 사실만 추출한다."""
25
+ blockers = progress_blocking_ids(
26
+ data.get("clarificationItems"), USER_INPUT_BLOCKS, report_data=data
27
+ )
28
+ if blockers:
29
+ raise TechnicalVerificationError(
30
+ "unresolved user decisions: " + ", ".join(blockers)
31
+ )
32
+ selection = data.get("implementationOptionSelection") or {}
33
+ facts: list[VerificationFact] = []
34
+ for candidate in selection.get("candidateAudit") or []:
35
+ if candidate.get("safetyBlockers"):
36
+ continue
37
+ for index, fact in enumerate(candidate.get("unresolvedFeasibilityFacts") or []):
38
+ if fact.get("resolutionKind") != "technical-verification":
39
+ continue
40
+ facts.append(
41
+ {
42
+ "id": f"TV-{len(facts) + 1:03d}",
43
+ "candidateId": candidate["id"],
44
+ "factIndex": index,
45
+ "fact": fact["fact"],
46
+ "whyItMatters": fact["whyItMatters"],
47
+ "evidence": fact["evidence"],
48
+ }
49
+ )
50
+ if not facts:
51
+ raise TechnicalVerificationError("no eligible technical-verification facts")
52
+ return facts
@@ -15,11 +15,11 @@ Public surface (re-exported here; the skill calls it through the
15
15
  - ``render_args()`` - final args for ``okstra render-bundle`` (``render``).
16
16
  - ``wizard_outcome()`` - final args plus persistence actions and confirmation (``render``).
17
17
 
18
- Module order is the import order: ``ids`` → ``state`` → ``prompts`` → ``sources``
19
- → ``roles`` → ``statefile`` → ``steps_*`` → ``confirmation`` → ``registry`` →
20
- ``engine`` → ``render`` → ``cli``. A module imports only modules before it;
21
- ``tests/contract/test_no_import_cycles.py`` keeps it that way.
18
+ Package initialization loads identifiers and state types only. Public execution
19
+ functions are composed by ``api`` when requested. Phase modules can import state
20
+ and prompt readers without initializing the registry that imports those phases.
22
21
  """
22
+
23
23
  from __future__ import annotations
24
24
 
25
25
  from .ids import (
@@ -118,31 +118,31 @@ from .state import (
118
118
  WizardError,
119
119
  WizardState,
120
120
  )
121
- from .roles import (
122
- next_role_prompt,
123
- )
124
- from .statefile import (
125
- load_state_file,
126
- save_state_file,
127
- )
128
- from .confirmation import (
129
- confirmation_block,
130
- )
131
- from .registry import (
132
- STEPS,
133
- STEP_BY_ID,
134
- )
135
- from .engine import (
136
- init_state,
137
- next_prompt,
138
- prompt_payload,
139
- submit,
140
- )
141
- from .render import (
142
- render_args,
143
- render_role_args,
144
- )
145
- from .outcome import wizard_outcome
146
- from .cli import (
147
- main,
121
+
122
+ _API_EXPORTS = (
123
+ "next_role_prompt",
124
+ "load_state_file",
125
+ "save_state_file",
126
+ "confirmation_block",
127
+ "STEPS",
128
+ "STEP_BY_ID",
129
+ "init_state",
130
+ "next_prompt",
131
+ "prompt_payload",
132
+ "submit",
133
+ "render_args",
134
+ "render_role_args",
135
+ "wizard_outcome",
136
+ "main",
148
137
  )
138
+
139
+ __all__ = [name for name in globals() if not name.startswith("_")] + list(_API_EXPORTS)
140
+
141
+
142
+ def __getattr__(name: str):
143
+ """기존 공개 이름을 실행 조립 모듈에 연결한다."""
144
+ if name not in _API_EXPORTS:
145
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
146
+ from . import api
147
+
148
+ return getattr(api, name)
@@ -0,0 +1,18 @@
1
+ """질문 실행기의 공개 조립 지점. 단계의 값·프롬프트 판독에서는 적재하지 않는다."""
2
+
3
+ from .roles import next_role_prompt as next_role_prompt
4
+ from .statefile import (
5
+ load_state_file as load_state_file,
6
+ save_state_file as save_state_file,
7
+ )
8
+ from .confirmation import confirmation_block as confirmation_block
9
+ from .registry import STEPS as STEPS, STEP_BY_ID as STEP_BY_ID
10
+ from .engine import (
11
+ init_state as init_state,
12
+ next_prompt as next_prompt,
13
+ prompt_payload as prompt_payload,
14
+ submit as submit,
15
+ )
16
+ from .render import render_args as render_args, render_role_args as render_role_args
17
+ from .outcome import wizard_outcome as wizard_outcome
18
+ from .cli import main as main
@@ -25,18 +25,9 @@ def _render_argv(rendered: dict[str, Any], *, host_runtime: str) -> list[str]:
25
25
  def _wizard_persist_actions(state: WizardState) -> list[dict[str, str]]:
26
26
  if state.task_type != "release-handoff":
27
27
  return []
28
- if not state.pr_template_path:
29
- return []
30
- if state.pr_template_scope not in ("project", "global"):
31
- return []
32
- return [
33
- {
34
- "command": "config.set",
35
- "key": "pr-template-path",
36
- "scope": state.pr_template_scope,
37
- "value": state.pr_template_path,
38
- }
39
- ]
28
+ from okstra_ctl.phases.release_handoff.wizard import pr_template_persist_actions
29
+
30
+ return pr_template_persist_actions(state)
40
31
 
41
32
 
42
33
  def wizard_outcome(state: WizardState) -> dict[str, Any]:
@@ -1,6 +1,20 @@
1
1
  """단계 등록표 — 순서가 곧 질문 순서인 `STEPS`, `STEP_BY_ID`, 등록표를 되감는 `_reset_from` 과 되감기를 일으키는 단계(edit target, brief carry)."""
2
2
  from __future__ import annotations
3
3
 
4
+ from okstra_ctl.phases.change_impact_analysis.entry import (
5
+ _build_feature_evidence,
6
+ _build_feature_evidence_pick,
7
+ _submit_feature_evidence,
8
+ _submit_feature_evidence_pick,
9
+ )
10
+
11
+ from okstra_ctl.phases.implementation_planning.wizard import (
12
+ _selected_direction_candidates,
13
+ _planning_rerun_selected,
14
+ _build_selected_direction_pick,
15
+ _submit_selected_direction_pick,
16
+ )
17
+
4
18
  from typing import Optional
5
19
 
6
20
  from .ids import (
@@ -119,27 +133,25 @@ from .steps_identity import (
119
133
  _submit_task_type,
120
134
  _submit_task_type_text,
121
135
  )
122
- from .steps_analysis import (
123
- _analysis_inputs_after_reuse,
136
+ from okstra_ctl.phases.feature_analysis.wizard import (
124
137
  _build_analysis_target,
125
138
  _build_analysis_target_pick,
139
+ _submit_analysis_target,
140
+ _submit_analysis_target_pick,
141
+ )
142
+ from .steps_analysis import (
143
+ _analysis_inputs_after_reuse,
126
144
  _build_design_prep_confirm,
127
145
  _build_design_prep_decision,
128
146
  _build_design_prep_overrides,
129
- _build_feature_evidence,
130
- _build_feature_evidence_pick,
131
147
  _build_project_evidence,
132
148
  _build_project_evidence_pick,
133
149
  _design_prep_confirm_applies,
134
150
  _design_prep_decision_applies,
135
151
  _design_prep_overrides_applies,
136
- _submit_analysis_target,
137
- _submit_analysis_target_pick,
138
152
  _submit_design_prep_confirm,
139
153
  _submit_design_prep_decision,
140
154
  _submit_design_prep_overrides,
141
- _submit_feature_evidence,
142
- _submit_feature_evidence_pick,
143
155
  _submit_project_evidence,
144
156
  _submit_project_evidence_pick,
145
157
  )
@@ -151,12 +163,9 @@ from .steps_plan import (
151
163
  _build_handoff_stage_pick,
152
164
  _build_reverify_scope_pick,
153
165
  _build_reverify_scope_stages,
154
- _build_selected_direction_pick,
155
166
  _build_stage_pick,
156
167
  _fix_cycle_confirm_required,
157
- _planning_rerun_selected,
158
168
  _reverify_scope_step_pending,
159
- _selected_direction_candidates,
160
169
  _submit_approve_plan_confirm,
161
170
  _submit_approved_plan,
162
171
  _submit_approved_plan_pick,
@@ -164,7 +173,6 @@ from .steps_plan import (
164
173
  _submit_handoff_stage_pick,
165
174
  _submit_reverify_scope_pick,
166
175
  _submit_reverify_scope_stages,
167
- _submit_selected_direction_pick,
168
176
  _submit_stage_pick,
169
177
  )
170
178
  from .steps_options import (