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
@@ -14,9 +14,15 @@ state passing, and are read once at the start.
14
14
  """
15
15
  from __future__ import annotations
16
16
 
17
- from .technical_verification import (
18
- resolve_technical_verification_input,
19
- write_technical_verification_input,
17
+ from okstra_ctl.phases.feature_analysis.entry import (
18
+ resolve_analysis_target, validate_feature_target_input,
19
+ )
20
+
21
+ from okstra_ctl.phases.implementation_planning.validation import validate_selected_direction_plan
22
+
23
+ from .phases.technical_verification.entry import (
24
+ prepare_technical_verification_input,
25
+ materialize_technical_input_packet,
20
26
  )
21
27
 
22
28
  from argparse import (
@@ -40,6 +46,13 @@ from typing import Mapping, Sequence
40
46
 
41
47
  from okstra_project import project_json_path, upsert_project_json
42
48
  from okstra_project.slug import slugify
49
+ from .phases.catalog import host_rules
50
+ from .phases.implementation_planning.entry import (
51
+ _resolve_planning_input_path,
52
+ _validate_planning_entry_inputs,
53
+ _resolve_planning_direction,
54
+ _validate_reverify_scope,
55
+ )
43
56
  from . import fix_cycles
44
57
  from .analysis_packet import build_analysis_packet
45
58
  from . import group_context
@@ -55,7 +68,6 @@ from .analysis_inputs import (
55
68
  load_candidate_map,
56
69
  parse_evidence_paths,
57
70
  resolve_analysis_head,
58
- resolve_analysis_target,
59
71
  resolve_evidence_inputs,
60
72
  )
61
73
  from .stage_fix_carry import derive_stage_fix_carry
@@ -67,13 +79,11 @@ from .clarification_items import (
67
79
  scan_approval_gate,
68
80
  )
69
81
  from .error_report import prior_run_error_digest
70
- from .incremental_scope import ReverifyScopeError, parse_user_reverify_scope
82
+ from .incremental_scope import parse_user_reverify_scope
71
83
  from .implementation_direction import (
72
84
  DirectionSelectionError,
73
85
  SelectedDirection,
74
86
  lexical_absolute_path,
75
- resolve_selected_direction,
76
- validate_selected_direction_plan,
77
87
  validate_task_artifact_path,
78
88
  write_selected_direction_snapshot,
79
89
  )
@@ -205,7 +215,9 @@ from .session import (
205
215
  resolve_inproc_lead_session_id,
206
216
  write_claude_resume_command_file,
207
217
  )
208
- from .pr_template import PrTemplateError, resolve_pr_template_path
218
+ from .phases.release_handoff.entry import (
219
+ materialize_handoff_input, resolve_pr_template,
220
+ )
209
221
  from .worker_artifacts import worker_provider_id
210
222
  from .workers import (
211
223
  normalize_workers,
@@ -213,7 +225,7 @@ from .workers import (
213
225
  resolve_profile_workers,
214
226
  validate_workers_against_profile,
215
227
  )
216
- from .workflow import compute_workflow_state, load_phase_forbidden
228
+ from .workflow import compute_workflow_state, load_phase_boundary
217
229
  from .locks import worktree_provision_mutex
218
230
  from . import stage_targets as _stage_targets
219
231
  from .phases.catalog import PhaseAssetError, profile_markdown, resolve_include
@@ -225,8 +237,7 @@ from .phases.final_verification.entry import (
225
237
  verification_target_request,
226
238
  write_verification_target_snapshot,
227
239
  )
228
- from .plan_run_root import plan_run_root_from_approved_plan
229
- from . import implementation_stage as _implementation_stage
240
+ from .phases.implementation import entry as _implementation_stage
230
241
  from .work_categories import resolve_work_category
231
242
  from .worktree import (
232
243
  WorktreeProvision,
@@ -236,7 +247,6 @@ from .brief_frontmatter import (
236
247
  has_reporter_confirmation_contract,
237
248
  read_brief_frontmatter,
238
249
  )
239
- from .scope_provenance import brief_end_state_id_sequence
240
250
 
241
251
  # Frontmatter approval-flag matcher.
242
252
  #
@@ -517,6 +527,9 @@ def _set_data_json_approved_true_if_present(path: Path) -> bool:
517
527
  # PrepareError 는 stage_targets(저수준)에 정의돼 final-verification 과 container
518
528
  # 가 whole-task 통합 실패를 동일 타입으로 받는다. run.py 는 재노출만 한다.
519
529
  PrepareError = _stage_targets.PrepareError
530
+ from .phases.implementation_option_selection.entry import (
531
+ validate_analyser_roster, validate_brief_requirements,
532
+ )
520
533
 
521
534
 
522
535
  def _normalize_lead_runtime(value: str) -> str:
@@ -696,17 +709,7 @@ def _validate_stage_structure(plan_path: str) -> None:
696
709
  RUN_STEP_BUDGET = _stage_targets.RUN_STEP_BUDGET
697
710
 
698
711
 
699
- def _parse_stage_map_into_ctx(plan_path: str) -> list:
700
- """Parse the approved plan into context records for execution consumers."""
701
- from .stage_map import StageMapError, parse_stage_map_file, stage_map_records
702
-
703
- try:
704
- return stage_map_records(parse_stage_map_file(Path(plan_path)))
705
- except StageMapError as exc:
706
- raise PrepareError(
707
- "approved-plan 의 Stage Map 을 신뢰할 수 없어 거부합니다 "
708
- f"({plan_path}): {exc.reason}. plan 의 Stage Map 을 점검하세요."
709
- ) from exc
712
+ from .stage_map import parse_stage_map_for_prepare as _parse_stage_map_into_ctx
710
713
 
711
714
 
712
715
  def _apply_cli_approval(path: str) -> None:
@@ -1352,9 +1355,7 @@ def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _Resolv
1352
1355
  raise PrepareError(
1353
1356
  f"required okstra template or lead contract missing: {required}.{_INSTALL_HINT}"
1354
1357
  )
1355
- host_rules_file = (
1356
- workspace_root / "prompts" / "host-orchestration" / f"{inp.task_type}.md"
1357
- )
1358
+ host_rules_file = host_rules(workspace_root, inp.task_type)
1358
1359
  return _ResolvedAssets(
1359
1360
  profile_file=profile_file,
1360
1361
  prompt_template=prompt_template,
@@ -1434,17 +1435,12 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
1434
1435
  if inp.task_type == "release-handoff":
1435
1436
  # brief 는 entry phase 의 입력물 — release-handoff 는 검증 보고서를
1436
1437
  # 인용하는 input 문서를 prepare 가 직접 생성해 brief 자리에 채운다
1437
- # (_materialize_release_handoff_input, 이 검증 직후 실행).
1438
+ # (materialize_handoff_input, 이 검증 직후 실행).
1438
1439
  pass
1439
1440
  elif not inp.brief_path.is_file():
1440
1441
  raise PrepareError(f"task brief not found: {inp.brief_path}")
1441
- elif inp.task_type == "implementation-option-selection" and not brief_end_state_id_sequence(
1442
- inp.brief_path
1443
- ):
1444
- raise PrepareError(
1445
- "regenerate the brief with stable end-state IDs before starting "
1446
- "implementation-option-selection"
1447
- )
1442
+ elif inp.task_type == "implementation-option-selection":
1443
+ validate_brief_requirements(inp.brief_path)
1448
1444
  ctx_stage_map: list = []
1449
1445
  # implementation 과 final-verification 은 둘 다 승인된 plan 의 Stage Map 을
1450
1446
  # 입력으로 받는다(전자는 실행 scope, 후자는 검증 scope).
@@ -1491,143 +1487,6 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
1491
1487
  return ctx_stage_map
1492
1488
 
1493
1489
 
1494
- _PLANNING_REPORT_RE = re.compile(
1495
- r"^final-report-implementation-planning-\d{3,}\.(?:md|data\.json)$"
1496
- )
1497
-
1498
-
1499
- def _resolve_planning_input_path(
1500
- path_value: str, project_root: Path
1501
- ) -> Path | None:
1502
- """Resolve cwd-first planning inputs without following symbolic links."""
1503
- raw_path = Path(path_value).expanduser()
1504
- cwd_candidate = lexical_absolute_path(raw_path)
1505
- if cwd_candidate.is_file():
1506
- return cwd_candidate
1507
- if not raw_path.is_absolute():
1508
- project_candidate = lexical_absolute_path(project_root / raw_path)
1509
- if project_candidate.is_file():
1510
- return project_candidate
1511
- return None
1512
-
1513
-
1514
- def _is_existing_planning_report(
1515
- path_value: str, project_root: Path, inp: PrepareInputs
1516
- ) -> bool:
1517
- from .paths import task_dir
1518
-
1519
- try:
1520
- report = lexical_absolute_path(Path(path_value))
1521
- task_root = lexical_absolute_path(
1522
- task_dir(project_root, inp.task_group, inp.task_id)
1523
- )
1524
- relative = report.relative_to(task_root)
1525
- except ValueError:
1526
- return False
1527
- has_planning_layout = (
1528
- _PLANNING_REPORT_RE.fullmatch(report.name) is not None
1529
- and relative.parts[:3] == (
1530
- "runs",
1531
- "implementation-planning",
1532
- "reports",
1533
- )
1534
- and len(relative.parts) == 4
1535
- )
1536
- if not has_planning_layout:
1537
- return False
1538
- try:
1539
- validate_task_artifact_path(report, task_root, "planning report")
1540
- except DirectionSelectionError:
1541
- return False
1542
- return True
1543
-
1544
-
1545
- def _validate_planning_entry_inputs(
1546
- project_root: Path, inp: PrepareInputs
1547
- ) -> None:
1548
- if inp.task_type != "implementation-planning":
1549
- if inp.selected_direction_path:
1550
- raise PrepareError(
1551
- "--selected-direction is only meaningful with --task-type "
1552
- f"implementation-planning; got {inp.task_type}"
1553
- )
1554
- return
1555
- if inp.selected_direction_path and inp.clarification_response_path:
1556
- raise PrepareError(
1557
- "implementation-planning accepts either --selected-direction for a "
1558
- "new plan or --clarification-response for a rerun, not both"
1559
- )
1560
- if inp.selected_direction_path:
1561
- return
1562
- if inp.clarification_response_path:
1563
- if not _is_existing_planning_report(
1564
- inp.clarification_response_path, project_root, inp
1565
- ):
1566
- raise PrepareError(
1567
- "implementation-planning rerun --clarification-response must point "
1568
- "to an existing implementation-planning report for the same task"
1569
- )
1570
- return
1571
- raise PrepareError(
1572
- "implementation-planning requires --selected-direction for a new plan or "
1573
- "--clarification-response to an existing implementation-planning report"
1574
- )
1575
-
1576
-
1577
- def _resolve_planning_direction(inp: PrepareInputs) -> SelectedDirection | None:
1578
- if inp.task_type != "implementation-planning" or not inp.selected_direction_path:
1579
- return None
1580
- from .paths import task_dir
1581
-
1582
- try:
1583
- report = lexical_absolute_path(Path(inp.selected_direction_path))
1584
- expected_task_root = lexical_absolute_path(
1585
- task_dir(Path(inp.project_root), inp.task_group, inp.task_id)
1586
- )
1587
- report.relative_to(expected_task_root)
1588
- except ValueError as exc:
1589
- raise PrepareError(
1590
- "selected direction report must belong to the same task"
1591
- ) from exc
1592
- try:
1593
- validate_task_artifact_path(
1594
- report, expected_task_root, "selected direction report"
1595
- )
1596
- return resolve_selected_direction(
1597
- report,
1598
- expected_task_key=(
1599
- f"{inp.project_id}:{inp.task_group}:{inp.task_id}"
1600
- ),
1601
- )
1602
- except DirectionSelectionError as exc:
1603
- raise PrepareError(str(exc)) from exc
1604
-
1605
-
1606
- def _validate_reverify_scope(inp: PrepareInputs) -> None:
1607
- """A pinned re-verification scope is only actionable on a planning re-run.
1608
-
1609
- Every other phase renders the tokens too (the template always reads them),
1610
- but nothing consumes them there — so a value outside the one phase that
1611
- acts on it is a caller mistake, not a preference to honour silently.
1612
- """
1613
- if not (inp.reverify_scope or "").strip():
1614
- return
1615
- if inp.task_type != "implementation-planning":
1616
- raise PrepareError(
1617
- "--reverify-scope is only meaningful with --task-type "
1618
- f"implementation-planning; got {inp.task_type}"
1619
- )
1620
- if not inp.clarification_response_path:
1621
- raise PrepareError(
1622
- "--reverify-scope needs --clarification-response: there is no prior "
1623
- "report to narrow re-verification against"
1624
- )
1625
- try:
1626
- parse_user_reverify_scope(inp.reverify_scope)
1627
- except ReverifyScopeError as exc:
1628
- raise PrepareError(str(exc)) from exc
1629
-
1630
-
1631
1490
  def _implementation_plan_contract(
1632
1491
  inp: PrepareInputs,
1633
1492
  ) -> tuple[str, Path, dict | None]:
@@ -1773,13 +1632,12 @@ def _validate_analysis_prepare_inputs(project_root: Path, inp: PrepareInputs) ->
1773
1632
  )
1774
1633
  return
1775
1634
  if inp.task_type == "project-analysis":
1776
- if inp.analysis_target:
1777
- raise PrepareError("project-analysis does not accept an analysis target")
1778
- if inp.evidence_inputs_raw:
1779
- raise PrepareError("project-analysis does not accept evidence inputs")
1635
+ from .phases.project_analysis.entry import validate_project_analysis_inputs
1636
+
1637
+ validate_project_analysis_inputs(inp.analysis_target, inp.evidence_inputs_raw)
1780
1638
  return
1781
- if inp.task_type == "feature-analysis" and not inp.analysis_target.strip():
1782
- raise PrepareError("feature-analysis requires --analysis-target")
1639
+ if inp.task_type == "feature-analysis":
1640
+ validate_feature_target_input(inp.analysis_target)
1783
1641
  if inp.task_type != "feature-analysis" and inp.analysis_target:
1784
1642
  raise PrepareError(f"{inp.task_type} does not accept an analysis target")
1785
1643
  paths = parse_evidence_paths(inp.evidence_inputs_raw, project_root)
@@ -1794,127 +1652,10 @@ def _validate_analysis_prepare_inputs(project_root: Path, inp: PrepareInputs) ->
1794
1652
  raise PrepareError(str(exc)) from exc
1795
1653
 
1796
1654
 
1797
- def _collect_handoff_source_report_rows(
1798
- rows: list, nums: list,
1799
- ) -> list:
1800
- """선택 stage 들의 마지막 verified 행에서 인용 표 행을 만든다 (last-wins)."""
1801
- last_verified: dict = {}
1802
- for r in rows:
1803
- if r.get("status") == "verified" and isinstance(r.get("stage"), int):
1804
- last_verified[r["stage"]] = r
1805
- return [(str(n), Path(last_verified[n]["report_path"])) for n in nums]
1806
-
1807
-
1808
- def _render_handoff_sources(sources: list, project_root: Path) -> tuple[str, str]:
1809
- """인용 표와 미해결 조건은 같은 원본 판정에서 렌더한다."""
1810
- from .release_gate import release_handoff_allowed, verdict_token
1811
-
1812
- rows, conditions = [], []
1813
- for stage, report in sources:
1814
- data = load_owned_object(report, artifact="handoff verification report")
1815
- if not release_handoff_allowed(data):
1816
- raise PrepareError(f"verification no longer release-ready: {report}")
1817
- relative = relative_to_project_root(report, project_root) or str(report)
1818
- rows.append(f"| {stage} | {relative} | `{verdict_token(data)}` |")
1819
- if verdict_token(data) == "conditional-accept":
1820
- for condition in data["finalVerdict"]["conditionalAcceptanceConditions"]:
1821
- conditions.append(
1822
- f"- Stage {stage}, `{condition.get('id', '')}`\n"
1823
- f" - Condition: {condition.get('condition', '')}\n"
1824
- f" - Evidence required: {condition.get('evidenceRequired', '')}\n"
1825
- f" - blocksReleaseHandoff: {json.dumps(condition['blocksReleaseHandoff'])}"
1826
- )
1827
- section = "## Unresolved Conditional Acceptance Conditions\n\n" + "\n".join(conditions) if conditions else ""
1828
- return "\n".join(rows), section
1829
1655
 
1830
1656
 
1831
- def _materialize_release_handoff_input(
1832
- workspace_root: Path, project_root: Path, inp: "PrepareInputs",
1833
- ) -> dict:
1834
- """release-handoff 의 입력 문서를 검증 보고서 인용으로 자동 생성한다.
1835
-
1836
- brief 는 entry phase 의 입력물이라 release-handoff 에는 없다 — 이 run 의
1837
- `--task-brief` 자리는 여기서 생성된 input 문서가 채운다. stage 자격은
1838
- okstra_ctl.handoff 의 SSOT 판정을 prepare 에서도 그대로 강제한다.
1839
- 반환: ctx 에 올릴 {"HANDOFF_STAGES": ...}."""
1840
- from .consumers import read_consumers
1841
- from .handoff import HandoffError, _require_eligible, compute_eligibility
1842
- from .paths import task_dir
1843
- from .render import render_template_with_ctx
1844
- from .run_context import _now_task_date
1845
1657
 
1846
- # Path("") 는 "." 로 정규화된다 — 빈 brief 센티널은 둘 다로 들어올 수 있다.
1847
- if str(inp.brief_path).strip() not in ("", "."):
1848
- raise PrepareError(
1849
- "--task-brief is not accepted for --task-type release-handoff — "
1850
- "the input document is generated from the cited final-verification "
1851
- "reports (pick stages via --stages, or leave it empty to take "
1852
- "every eligible stage)"
1853
- )
1854
- if not inp.approved_plan_path:
1855
- raise PrepareError(
1856
- "release-handoff requires --approved-plan "
1857
- "<implementation-planning final-report> — the Stage Map and the "
1858
- "consumers ledger drive stage eligibility"
1859
- )
1860
- plan = Path(inp.approved_plan_path)
1861
- if not plan.is_file():
1862
- raise PrepareError(f"approved plan not found: {plan}")
1863
- stage_map = _parse_stage_map_into_ctx(str(plan))
1864
- rows = read_consumers(plan_run_root_from_approved_plan(plan))
1865
1658
 
1866
- if inp.stages:
1867
- try:
1868
- nums = sorted({int(x) for x in inp.stages.split(",") if x.strip()})
1869
- except ValueError:
1870
- raise PrepareError(
1871
- f"--stages must be a comma-separated int list, got {inp.stages!r}"
1872
- )
1873
- if not nums:
1874
- raise PrepareError("--stages must select at least one stage")
1875
- try:
1876
- _require_eligible(stage_map, rows, nums)
1877
- except HandoffError as exc:
1878
- raise PrepareError(str(exc)) from exc
1879
- else:
1880
- eligibility = compute_eligibility(stage_map, rows)
1881
- nums = [e["stage"] for e in eligibility if e["eligible"]]
1882
- if not nums:
1883
- blocked = "; ".join(
1884
- f"stage {e['stage']}: {', '.join(e['reasons'])}"
1885
- for e in eligibility) or "(no stage in the Stage Map)"
1886
- raise PrepareError(
1887
- "release-handoff has no eligible stage — every stage must "
1888
- "carry a release-ready single-stage final-verification of its "
1889
- f"recorded implementation commit. {blocked}"
1890
- )
1891
- stages_csv = ",".join(str(n) for n in nums)
1892
- report_rows = _collect_handoff_source_report_rows(rows, nums)
1893
-
1894
- source_rows, source_conditions = _render_handoff_sources(report_rows, project_root)
1895
-
1896
- template = (workspace_root / "templates" / "reports"
1897
- / "release-handoff-input.template.md")
1898
- if not template.is_file():
1899
- raise PrepareError(
1900
- f"release-handoff input template missing: {template}.{_INSTALL_HINT}"
1901
- )
1902
- out = (task_dir(project_root, inp.task_group, inp.task_id)
1903
- / "release-handoff-input.md")
1904
- out.parent.mkdir(parents=True, exist_ok=True)
1905
- render_template_with_ctx(str(template), str(out), {
1906
- "TASK_KEY": f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
1907
- "TASK_ID": inp.task_id,
1908
- "TASK_GROUP": inp.task_group,
1909
- "PROJECT_ID": inp.project_id,
1910
- "TASK_TYPE": inp.task_type,
1911
- "TASK_DATE": _now_task_date(),
1912
- "HANDOFF_STAGES": stages_csv,
1913
- "HANDOFF_SOURCE_REPORTS": source_rows,
1914
- "HANDOFF_CONDITIONS": source_conditions,
1915
- })
1916
- inp.brief_path = out
1917
- return {"HANDOFF_STAGES": stages_csv}
1918
1659
 
1919
1660
 
1920
1661
  QA_COMMAND_EXECUTING_TASK_TYPES = ("implementation", "final-verification")
@@ -2659,28 +2400,11 @@ def _read_model_defaults(path: Path) -> dict[str, tuple[str, ...]]:
2659
2400
  return defaults
2660
2401
 
2661
2402
 
2662
- def _validate_option_selection_roster(
2663
- task_type: str, workers: Sequence[str]
2664
- ) -> None:
2665
- """Require independent analysis from three workers before option selection."""
2666
- if task_type != "implementation-option-selection":
2667
- return
2668
- analyser_count = sum(worker != "report-writer" for worker in workers)
2669
- if analyser_count < 3:
2670
- raise PrepareError(
2671
- "implementation-option-selection requires at least 3 analyser workers"
2672
- )
2403
+ def _validate_option_selection_roster(task_type: str, workers: Sequence[str]) -> None:
2404
+ if task_type == "implementation-option-selection":
2405
+ validate_analyser_roster(workers)
2673
2406
 
2674
2407
 
2675
- def _resolve_pr_template(inp: PrepareInputs) -> tuple[str, str]:
2676
- """release-handoff 전용 PR 본문 템플릿 경로 + 출처를 해소한다 (그 외엔 빈 값)."""
2677
- if inp.task_type != "release-handoff":
2678
- return "", ""
2679
- try:
2680
- resolved_tpl = resolve_pr_template_path(Path(inp.project_root), inp.pr_template_path)
2681
- except PrTemplateError as exc:
2682
- raise PrepareError(f"PR template resolution failed: {exc}") from exc
2683
- return str(resolved_tpl.path), resolved_tpl.source
2684
2408
 
2685
2409
 
2686
2410
  @dataclass
@@ -2774,7 +2498,7 @@ def recommended_role_models(
2774
2498
  codex 이고 다른 provider 요청은 거부된다(`resolve_lead_provider`). 그래서
2775
2499
  `lead_provider` 를 받아 그 provider 의 기본값으로 해소한다. 받지 않으면
2776
2500
  role 별 레거시 기본값(claude 계열)으로 떨어지는데, 그 값을 codex 호스트
2777
- 화면에 그대로 쓰면 안내는 `opus` 인데 prepare 는 `gpt-6-sol` 을 배정한다.
2501
+ 화면에 그대로 쓰면 안내는 `opus` 인데 prepare 는 `gpt-6.1-sol` 을 배정한다.
2778
2502
  """
2779
2503
  lead_provider_id = lead_provider or "claude"
2780
2504
  report_writer_provider_id = report_writer_provider or "claude"
@@ -3562,6 +3286,9 @@ def _write_instruction_set_sources(
3562
3286
  profile_rendered = profile_content
3563
3287
  if inp.task_type == "implementation":
3564
3288
  profile_rendered += "\n\n{{DESIGN_PREP_CONTEXT}}\n\n{{FIX_RUN_CONTEXT}}"
3289
+ profile_rendered = _implementation_stage.resolve_instruction_paths(
3290
+ profile_rendered, Path(ctx["WORKSPACE_ROOT"])
3291
+ )
3565
3292
  profile_tokens = (
3566
3293
  "EXECUTOR_WORKER_ID",
3567
3294
  "EXECUTOR_PROVIDER",
@@ -3584,13 +3311,14 @@ def _write_instruction_set_sources(
3584
3311
  profile_rendered = profile_rendered.replace("{{" + key + "}}", ctx.get(key, ""))
3585
3312
  (instruction_set / "analysis-profile.md").write_text(profile_rendered, encoding="utf-8")
3586
3313
  if inp.task_type == "implementation":
3587
- executor_source = (
3588
- Path(ctx["WORKSPACE_ROOT"])
3589
- / "prompts"
3590
- / "profiles"
3591
- / "_implementation-executor.md"
3314
+ from .phases.catalog import profile_sidecar
3315
+
3316
+ executor_source = profile_sidecar(
3317
+ Path(ctx["WORKSPACE_ROOT"]), "prompts/profiles/_implementation-executor.md"
3318
+ )
3319
+ executor_rendered = _implementation_stage.resolve_instruction_paths(
3320
+ executor_source.read_text(encoding="utf-8"), Path(ctx["WORKSPACE_ROOT"])
3592
3321
  )
3593
- executor_rendered = executor_source.read_text(encoding="utf-8")
3594
3322
  for key in profile_tokens:
3595
3323
  executor_rendered = executor_rendered.replace(
3596
3324
  "{{" + key + "}}", ctx.get(key, "")
@@ -3700,17 +3428,10 @@ def _write_instruction_set_sources(
3700
3428
  ),
3701
3429
  )
3702
3430
  if inp.task_type == "technical-verification":
3703
- verification_path = write_technical_verification_input(
3704
- json.loads(ctx["TECHNICAL_VERIFICATION_INPUT_JSON"]),
3431
+ packet += materialize_technical_input_packet(
3432
+ ctx["TECHNICAL_VERIFICATION_INPUT_JSON"],
3705
3433
  Path(ctx["RUN_DIR"]), ctx["RUN_MANIFESTS_SEQ"],
3706
3434
  )
3707
- packet += (
3708
- "\n## Technical Verification Input\n\n"
3709
- f"- Frozen input: `{verification_path}`\n"
3710
- f"- Experiment copies only: `{ctx['RUN_DIR']}/experiments/{ctx['RUN_MANIFESTS_SEQ']}/<worker-id>/`\n"
3711
- "- Read the input before planning experiments; copy each fact identity verbatim into results.\n"
3712
- + "\n```json\n" + ctx["TECHNICAL_VERIFICATION_INPUT_JSON"] + "\n```\n"
3713
- )
3714
3435
  if inp.task_type in ANALYSIS_TASK_TYPES:
3715
3436
  packet += (
3716
3437
  "\n## Analysis Evidence\n\n"
@@ -4021,7 +3742,7 @@ def _record_fix_cycle_events(inp: PrepareInputs, ctx: dict) -> str:
4021
3742
 
4022
3743
  def _finalize_status_and_render_manifests(
4023
3744
  inp: PrepareInputs, ctx: dict, task_index_template: Path,
4024
- forbidden_by_phase: dict[str, str],
3745
+ boundary: dict[str, str],
4025
3746
  ) -> None:
4026
3747
  """최종 task/run status 를 확정하고 workflow state 를 재계산한 뒤 team-state,
4027
3748
  task-manifest, task-index, run-manifest, timeline, discovery 산출물을 렌더한다."""
@@ -4038,7 +3759,7 @@ def _finalize_status_and_render_manifests(
4038
3759
  current_run_status=ctx["CURRENT_RUN_STATUS"],
4039
3760
  current_task_status=ctx["CURRENT_TASK_STATUS"],
4040
3761
  render_only=inp.render_only,
4041
- forbidden_by_phase=forbidden_by_phase,
3762
+ boundary=boundary,
4042
3763
  work_category=inp.work_category,
4043
3764
  ))
4044
3765
  render_team_state(ctx["TEAM_STATE_PATH"], ctx)
@@ -4329,7 +4050,7 @@ def _reconcile_prior_runs(ctx: dict) -> None:
4329
4050
 
4330
4051
 
4331
4052
  def _initial_workflow_ctx(
4332
- inp: PrepareInputs, forbidden_by_phase: dict
4053
+ inp: PrepareInputs, boundary: dict
4333
4054
  ) -> dict[str, str]:
4334
4055
  """Workflow tokens for a run that has been prepared but not started.
4335
4056
 
@@ -4346,7 +4067,7 @@ def _initial_workflow_ctx(
4346
4067
  current_run_status=run_status,
4347
4068
  current_task_status=task_status,
4348
4069
  render_only=inp.render_only,
4349
- forbidden_by_phase=forbidden_by_phase,
4070
+ boundary=boundary,
4350
4071
  ),
4351
4072
  }
4352
4073
 
@@ -4700,16 +4421,11 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4700
4421
  technical_input = None
4701
4422
  from .paths import task_dir
4702
4423
  if inp.task_type == "technical-verification":
4703
- if not inp.clarification_response_path:
4704
- raise PrepareError("technical-verification requires --clarification-response pointing to this task's option-selection record")
4705
- try:
4706
- technical_input = resolve_technical_verification_input(
4707
- Path(inp.clarification_response_path), project_root,
4708
- task_dir(project_root, inp.task_group, inp.task_id),
4709
- f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
4710
- )
4711
- except ValueError as exc:
4712
- raise PrepareError(str(exc)) from exc
4424
+ technical_input = prepare_technical_verification_input(
4425
+ inp.clarification_response_path, project_root,
4426
+ task_dir(project_root, inp.task_group, inp.task_id),
4427
+ f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
4428
+ )
4713
4429
  if inp.task_type == "implementation":
4714
4430
  ctx_stage_map = _prepare_implementation_approved_plan(inp)
4715
4431
 
@@ -4717,11 +4433,13 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4717
4433
  # 이후의 모든 brief 소비 경로(material/instruction-set 복사)는 그대로 동작한다.
4718
4434
  handoff_tokens = {"HANDOFF_STAGES": ""}
4719
4435
  if inp.task_type == "release-handoff":
4720
- handoff_tokens = _materialize_release_handoff_input(
4721
- workspace_root, project_root, inp)
4436
+ handoff_tokens = materialize_handoff_input(
4437
+ workspace_root, project_root, inp, install_hint=_INSTALL_HINT)
4722
4438
 
4723
4439
  workers, selected_reviewers = _resolve_roster(inp, profile_file)
4724
- pr_template_path_str, pr_template_source = _resolve_pr_template(inp)
4440
+ pr_template_path_str, pr_template_source = (
4441
+ resolve_pr_template(inp) if inp.task_type == "release-handoff" else ("", "")
4442
+ )
4725
4443
 
4726
4444
  assignment_context, assignment_plan = _resolve_prepare_assignments(
4727
4445
  inp=inp,
@@ -4882,7 +4600,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4882
4600
  if inp.approved_plan_path else ""
4883
4601
  )
4884
4602
 
4885
- forbidden_by_phase = load_phase_forbidden(workspace_root)
4603
+ boundary = load_phase_boundary(workspace_root, inp.task_type)
4886
4604
 
4887
4605
  # ---- assemble full ctx (the values render functions expect) ----
4888
4606
  ctx.update({
@@ -4908,7 +4626,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4908
4626
  "BRIEF_RELATIVE_PATH": brief_relative,
4909
4627
  **_model_ctx(models),
4910
4628
  **_related_tasks_ctx(ctx, inp),
4911
- **_initial_workflow_ctx(inp, forbidden_by_phase),
4629
+ **_initial_workflow_ctx(inp, boundary),
4912
4630
  "VALIDATION_STATUS": "not-run",
4913
4631
  "VALIDATION_UPDATED_AT": "",
4914
4632
  "VALIDATION_FAILURES_JSON": "[]",
@@ -4975,7 +4693,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4975
4693
 
4976
4694
  # ---- final status + manifest/discovery renders ----
4977
4695
  _finalize_status_and_render_manifests(
4978
- inp, ctx, task_index_template, forbidden_by_phase
4696
+ inp, ctx, task_index_template, boundary
4979
4697
  )
4980
4698
  persist_static_execution_identity(
4981
4699
  run_manifest_path=Path(ctx["RUN_MANIFEST_PATH"]),