okstra 0.205.1 → 0.206.1

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 (233) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/lifecycle/install.mjs +2 -1
  3. package/dist/commands/lifecycle/install.mjs.map +1 -1
  4. package/docs/architecture.md +16 -16
  5. package/docs/cli.md +4 -4
  6. package/docs/contributor-change-matrix.md +3 -2
  7. package/docs/performance-improvement-plan-v2.md +1 -1
  8. package/docs/project-structure-overview.md +45 -21
  9. package/package.json +2 -3
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/okstra-spawn-followups.py +2 -2
  12. package/runtime/prompts/launch.template.md +1 -1
  13. package/runtime/prompts/lead/context-loader.md +1 -1
  14. package/runtime/prompts/lead/convergence.md +3 -3
  15. package/runtime/prompts/lead/okstra-lead-contract.md +14 -55
  16. package/runtime/prompts/lead/phase-routing.md +82 -0
  17. package/runtime/prompts/lead/report-writer.md +2 -2
  18. package/runtime/prompts/lead/team-contract.md +1 -1
  19. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  20. package/runtime/prompts/profiles/_common-contract.md +1 -1
  21. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  22. package/runtime/prompts/profiles/forbidden-actions.json +0 -94
  23. package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
  24. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +10 -1
  25. package/runtime/python/okstra_ctl/analysis_inputs.py +0 -39
  26. package/runtime/python/okstra_ctl/analysis_scope.py +31 -0
  27. package/runtime/python/okstra_ctl/asset_roots.py +19 -0
  28. package/runtime/python/okstra_ctl/consumers.py +12 -0
  29. package/runtime/python/okstra_ctl/contract_graph.py +75 -10
  30. package/runtime/python/okstra_ctl/dispatch_state.py +22 -0
  31. package/runtime/python/okstra_ctl/doctor.py +15 -4
  32. package/runtime/python/okstra_ctl/execution_mutation_audit.py +27 -1
  33. package/runtime/python/okstra_ctl/handoff.py +11 -466
  34. package/runtime/python/okstra_ctl/handoff_error.py +5 -0
  35. package/runtime/python/okstra_ctl/implementation_direction.py +9 -485
  36. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
  37. package/runtime/python/okstra_ctl/next_phase.py +2 -2
  38. package/runtime/python/okstra_ctl/option_comparison.py +3 -165
  39. package/runtime/python/okstra_ctl/option_votes.py +3 -191
  40. package/runtime/python/okstra_ctl/paths.py +22 -6
  41. package/runtime/python/okstra_ctl/phases/__init__.py +4 -0
  42. package/runtime/python/okstra_ctl/phases/catalog.py +260 -0
  43. package/runtime/python/okstra_ctl/phases/change_impact_analysis/boundary.json +11 -0
  44. package/runtime/python/okstra_ctl/phases/change_impact_analysis/entry.py +39 -0
  45. package/runtime/python/okstra_ctl/{report_html/view_models/change_impact_analysis.py → phases/change_impact_analysis/report.py} +3 -3
  46. package/runtime/python/okstra_ctl/phases/change_impact_analysis/spec.md +26 -0
  47. package/runtime/python/okstra_ctl/phases/change_impact_analysis/validation.py +23 -0
  48. package/runtime/python/okstra_ctl/phases/error_analysis/__init__.py +1 -0
  49. package/runtime/python/okstra_ctl/phases/error_analysis/boundary.json +9 -0
  50. package/runtime/{prompts/profiles/error-analysis.md → python/okstra_ctl/phases/error_analysis/profile.md} +2 -2
  51. package/runtime/python/okstra_ctl/{report_html/view_models/error_analysis.py → phases/error_analysis/report.py} +9 -8
  52. package/runtime/{templates/reports → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis-input.template.md +1 -1
  53. package/{docs/task-process/error-analysis.md → runtime/python/okstra_ctl/phases/error_analysis/spec.md} +22 -7
  54. package/runtime/python/okstra_ctl/phases/error_analysis/validation.py +241 -0
  55. package/runtime/python/okstra_ctl/phases/feature_analysis/__init__.py +1 -0
  56. package/runtime/python/okstra_ctl/phases/feature_analysis/boundary.json +8 -0
  57. package/runtime/python/okstra_ctl/phases/feature_analysis/entry.py +63 -0
  58. package/runtime/python/okstra_ctl/{report_html/view_models/feature_analysis.py → phases/feature_analysis/report.py} +12 -5
  59. package/runtime/python/okstra_ctl/phases/feature_analysis/spec.md +22 -0
  60. package/runtime/python/okstra_ctl/phases/feature_analysis/validation.py +27 -0
  61. package/runtime/python/okstra_ctl/phases/feature_analysis/wizard.py +95 -0
  62. package/runtime/python/okstra_ctl/phases/final_verification/__init__.py +4 -0
  63. package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
  64. package/runtime/python/okstra_ctl/phases/final_verification/entry.py +166 -0
  65. package/runtime/{prompts/profiles/final-verification.md → python/okstra_ctl/phases/final_verification/profile.md} +5 -5
  66. package/runtime/python/okstra_ctl/{report_html/view_models/final_verification.py → phases/final_verification/report.py} +12 -3
  67. package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
  68. package/{docs/task-process/final-verification.md → runtime/python/okstra_ctl/phases/final_verification/spec.md} +42 -25
  69. package/runtime/python/okstra_ctl/phases/final_verification/target.py +296 -0
  70. package/runtime/python/okstra_ctl/phases/final_verification/validation.py +190 -0
  71. package/runtime/python/okstra_ctl/phases/final_verification/wizard.py +38 -0
  72. package/runtime/python/okstra_ctl/phases/implementation/__init__.py +1 -0
  73. package/runtime/python/okstra_ctl/phases/implementation/boundary.json +17 -0
  74. package/runtime/python/okstra_ctl/{implementation_stage.py → phases/implementation/entry.py} +22 -10
  75. package/runtime/{prompts/host-orchestration/implementation.md → python/okstra_ctl/phases/implementation/host-rules.md} +1 -1
  76. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-deliverable.md +1 -1
  77. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-executor.md +4 -3
  78. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-verifier.md +4 -4
  79. package/runtime/{prompts/profiles/implementation.md → python/okstra_ctl/phases/implementation/profile.md} +5 -5
  80. package/runtime/python/okstra_ctl/{report_html/view_models/implementation.py → phases/implementation/report.py} +3 -3
  81. package/runtime/{templates/reports → python/okstra_ctl/phases/implementation/report_assets}/implementation-input.template.md +1 -1
  82. package/{docs/task-process/implementation.md → runtime/python/okstra_ctl/phases/implementation/spec.md} +20 -8
  83. package/runtime/python/okstra_ctl/phases/implementation/validation.py +205 -0
  84. package/runtime/python/okstra_ctl/phases/implementation/wizard.py +39 -0
  85. package/runtime/python/okstra_ctl/phases/implementation_option_selection/__init__.py +1 -0
  86. package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +80 -0
  87. package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +10 -0
  88. package/runtime/python/okstra_ctl/phases/implementation_option_selection/comparison.py +168 -0
  89. package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +27 -0
  90. package/runtime/{prompts/profiles/implementation-option-selection.md → python/okstra_ctl/phases/implementation_option_selection/profile.md} +2 -2
  91. package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
  92. package/{docs/task-process/implementation-option-selection.md → runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md} +20 -7
  93. package/runtime/python/okstra_ctl/{implementation_options.py → phases/implementation_option_selection/validation.py} +3 -3
  94. package/runtime/python/okstra_ctl/phases/implementation_option_selection/votes.py +194 -0
  95. package/runtime/python/okstra_ctl/phases/implementation_planning/__init__.py +1 -0
  96. package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +2345 -0
  97. package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +12 -0
  98. package/runtime/python/okstra_ctl/phases/implementation_planning/entry.py +161 -0
  99. package/runtime/python/okstra_ctl/phases/implementation_planning/guidance.py +178 -0
  100. package/runtime/{prompts/lead → python/okstra_ctl/phases/implementation_planning/instructions}/plan-body-verification.md +61 -51
  101. package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +3295 -0
  102. package/runtime/{prompts/profiles/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/profile.md} +74 -25
  103. package/runtime/python/okstra_ctl/phases/implementation_planning/report.py +237 -0
  104. package/runtime/{templates/reports → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning-input.template.md +2 -2
  105. package/{docs/task-process/implementation-planning.md → runtime/python/okstra_ctl/phases/implementation_planning/spec.md} +30 -6
  106. package/runtime/python/okstra_ctl/phases/implementation_planning/validation.py +597 -0
  107. package/runtime/python/okstra_ctl/phases/implementation_planning/wizard.py +166 -0
  108. package/runtime/python/okstra_ctl/phases/improvement_discovery/boundary.json +12 -0
  109. package/runtime/python/okstra_ctl/{improvement_lenses.py → phases/improvement_discovery/lenses.py} +1 -6
  110. package/runtime/{prompts/profiles/improvement-discovery.md → python/okstra_ctl/phases/improvement_discovery/profile.md} +5 -5
  111. package/runtime/python/okstra_ctl/{report_html/view_models/improvement_discovery.py → phases/improvement_discovery/report.py} +3 -3
  112. package/runtime/{templates/reports → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery-input.template.md +1 -2
  113. package/runtime/python/okstra_ctl/phases/improvement_discovery/spec.md +29 -0
  114. package/runtime/{validators/validate_improvement_report.py → python/okstra_ctl/phases/improvement_discovery/validation.py} +5 -14
  115. package/runtime/python/okstra_ctl/phases/project_analysis/__init__.py +1 -0
  116. package/runtime/python/okstra_ctl/phases/project_analysis/boundary.json +8 -0
  117. package/runtime/python/okstra_ctl/phases/project_analysis/entry.py +11 -0
  118. package/runtime/python/okstra_ctl/{report_html/view_models/project_analysis.py → phases/project_analysis/report.py} +3 -3
  119. package/runtime/python/okstra_ctl/phases/project_analysis/spec.md +33 -0
  120. package/runtime/python/okstra_ctl/phases/project_analysis/validation.py +55 -0
  121. package/runtime/python/okstra_ctl/phases/release_handoff/__init__.py +1 -0
  122. package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +17 -0
  123. package/runtime/python/okstra_ctl/phases/release_handoff/entry.py +147 -0
  124. package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +446 -0
  125. package/runtime/{prompts/profiles/release-handoff.md → python/okstra_ctl/phases/release_handoff/profile.md} +3 -3
  126. package/runtime/python/okstra_ctl/{report_html/view_models/release_handoff.py → phases/release_handoff/report.py} +3 -3
  127. package/runtime/{templates/reports → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff-input.template.md +1 -1
  128. package/{docs/task-process/release-handoff.md → runtime/python/okstra_ctl/phases/release_handoff/spec.md} +22 -9
  129. package/runtime/python/okstra_ctl/phases/release_handoff/wizard.py +84 -0
  130. package/runtime/python/okstra_ctl/phases/requirements_discovery/__init__.py +1 -0
  131. package/runtime/python/okstra_ctl/phases/requirements_discovery/boundary.json +9 -0
  132. package/runtime/{prompts/profiles/requirements-discovery.md → python/okstra_ctl/phases/requirements_discovery/profile.md} +2 -3
  133. package/runtime/python/okstra_ctl/{report_html/view_models/requirements_discovery.py → phases/requirements_discovery/report.py} +3 -3
  134. package/{docs/task-process/requirements-discovery.md → runtime/python/okstra_ctl/phases/requirements_discovery/spec.md} +25 -6
  135. package/runtime/{validators/validate_fanout.py → python/okstra_ctl/phases/requirements_discovery/validation.py} +11 -12
  136. package/runtime/python/okstra_ctl/phases/technical_verification/__init__.py +1 -0
  137. package/runtime/python/okstra_ctl/phases/technical_verification/boundary.json +9 -0
  138. package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +100 -0
  139. package/runtime/{prompts/profiles/technical-verification.md → python/okstra_ctl/phases/technical_verification/profile.md} +1 -1
  140. package/runtime/python/okstra_ctl/{report_html/view_models/technical_verification.py → phases/technical_verification/report.py} +2 -2
  141. package/runtime/python/okstra_ctl/phases/technical_verification/spec.md +37 -0
  142. package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +90 -0
  143. package/runtime/python/okstra_ctl/plan_approval.py +70 -0
  144. package/runtime/python/okstra_ctl/plan_items_cli.py +2 -2130
  145. package/runtime/python/okstra_ctl/profile_show.py +9 -3
  146. package/runtime/python/okstra_ctl/render.py +9 -2
  147. package/runtime/python/okstra_ctl/render_final_report.py +3 -2
  148. package/runtime/python/okstra_ctl/report_assembly.py +14 -93
  149. package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
  150. package/runtime/python/okstra_ctl/report_html/render.py +3 -2
  151. package/runtime/python/okstra_ctl/report_html/router.py +9 -33
  152. package/runtime/python/okstra_ctl/report_projections.py +1 -36
  153. package/runtime/python/okstra_ctl/report_routing.py +23 -0
  154. package/runtime/python/okstra_ctl/report_synthesis_packet.py +4 -73
  155. package/runtime/python/okstra_ctl/report_template_loader.py +35 -0
  156. package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
  157. package/runtime/python/okstra_ctl/report_views.py +18 -2
  158. package/runtime/python/okstra_ctl/run.py +113 -503
  159. package/runtime/python/okstra_ctl/stage_map.py +13 -0
  160. package/runtime/python/okstra_ctl/stage_targets.py +9 -286
  161. package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
  162. package/runtime/python/okstra_ctl/user_response.py +199 -4
  163. package/runtime/python/okstra_ctl/verification_target.py +1 -1
  164. package/runtime/python/okstra_ctl/wizard/__init__.py +31 -31
  165. package/runtime/python/okstra_ctl/wizard/api.py +18 -0
  166. package/runtime/python/okstra_ctl/wizard/outcome.py +3 -12
  167. package/runtime/python/okstra_ctl/wizard/registry.py +20 -12
  168. package/runtime/python/okstra_ctl/wizard/state.py +6 -2
  169. package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
  170. package/runtime/python/okstra_ctl/wizard/steps_plan.py +27 -278
  171. package/runtime/python/okstra_ctl/wizard/steps_roles.py +2 -1
  172. package/runtime/python/okstra_ctl/work_categories.py +1 -1
  173. package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
  174. package/runtime/python/okstra_ctl/workflow.py +26 -143
  175. package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
  176. package/runtime/skills/okstra-run/SKILL.md +1 -1
  177. package/runtime/skills/okstra-user-response/SKILL.md +23 -4
  178. package/runtime/templates/reports/quick-input.template.md +1 -1
  179. package/runtime/templates/reports/task-brief.template.md +1 -1
  180. package/runtime/validators/validate-brief.py +2 -2
  181. package/runtime/validators/validate-run.py +287 -4091
  182. package/runtime/validators/validate_analysis_report.py +14 -126
  183. package/docs/task-process/README.md +0 -82
  184. package/docs/task-process/common-flow.md +0 -173
  185. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +0 -147
  186. package/runtime/python/okstra_ctl/technical_verification.py +0 -195
  187. /package/runtime/{prompts/profiles/change-impact-analysis.json → python/okstra_ctl/phases/change_impact_analysis/profile.json} +0 -0
  188. /package/runtime/{prompts/profiles/change-impact-analysis.md → python/okstra_ctl/phases/change_impact_analysis/profile.md} +0 -0
  189. /package/runtime/{templates/reports → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis-input.template.md +0 -0
  190. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.html +0 -0
  191. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.md +0 -0
  192. /package/runtime/{prompts/profiles/error-analysis.json → python/okstra_ctl/phases/error_analysis/profile.json} +0 -0
  193. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.html +0 -0
  194. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.md +0 -0
  195. /package/runtime/{prompts/profiles/feature-analysis.json → python/okstra_ctl/phases/feature_analysis/profile.json} +0 -0
  196. /package/runtime/{prompts/profiles/feature-analysis.md → python/okstra_ctl/phases/feature_analysis/profile.md} +0 -0
  197. /package/runtime/{templates/reports → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis-input.template.md +0 -0
  198. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.html +0 -0
  199. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.md +0 -0
  200. /package/runtime/{prompts/profiles/final-verification.json → python/okstra_ctl/phases/final_verification/profile.json} +0 -0
  201. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.template.html +0 -0
  202. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.template.md +0 -0
  203. /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-diff-review.md +0 -0
  204. /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-self-check.md +0 -0
  205. /package/runtime/{prompts/profiles/implementation.json → python/okstra_ctl/phases/implementation/profile.json} +0 -0
  206. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.html +0 -0
  207. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.md +0 -0
  208. /package/runtime/{prompts/profiles/implementation-option-selection.json → python/okstra_ctl/phases/implementation_option_selection/profile.json} +0 -0
  209. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.html +0 -0
  210. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.md +0 -0
  211. /package/runtime/{prompts/host-orchestration/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/host-rules.md} +0 -0
  212. /package/runtime/{prompts/profiles/implementation-planning.json → python/okstra_ctl/phases/implementation_planning/profile.json} +0 -0
  213. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.html +0 -0
  214. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.md +0 -0
  215. /package/runtime/{prompts/profiles/improvement-discovery.json → python/okstra_ctl/phases/improvement_discovery/profile.json} +0 -0
  216. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.html +0 -0
  217. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.md +0 -0
  218. /package/runtime/{prompts/profiles/project-analysis.json → python/okstra_ctl/phases/project_analysis/profile.json} +0 -0
  219. /package/runtime/{prompts/profiles/project-analysis.md → python/okstra_ctl/phases/project_analysis/profile.md} +0 -0
  220. /package/runtime/{templates/reports → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis-input.template.md +0 -0
  221. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.html +0 -0
  222. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.md +0 -0
  223. /package/runtime/{prompts/profiles/release-handoff.json → python/okstra_ctl/phases/release_handoff/profile.json} +0 -0
  224. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.html +0 -0
  225. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.md +0 -0
  226. /package/runtime/python/okstra_ctl/{fanout.py → phases/requirements_discovery/fanout.py} +0 -0
  227. /package/runtime/{prompts/profiles/requirements-discovery.json → python/okstra_ctl/phases/requirements_discovery/profile.json} +0 -0
  228. /package/runtime/{templates/reports → python/okstra_ctl/phases/requirements_discovery/report_assets}/fan-out-unit.template.md +0 -0
  229. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.html +0 -0
  230. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.md +0 -0
  231. /package/runtime/{prompts/profiles/technical-verification.json → python/okstra_ctl/phases/technical_verification/profile.json} +0 -0
  232. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.html +0 -0
  233. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.md +0 -0
@@ -14,12 +14,17 @@ 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
- import hashlib
23
28
  from argparse import (
24
29
  Action,
25
30
  ArgumentError,
@@ -41,6 +46,13 @@ from typing import Mapping, Sequence
41
46
 
42
47
  from okstra_project import project_json_path, upsert_project_json
43
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
+ )
44
56
  from . import fix_cycles
45
57
  from .analysis_packet import build_analysis_packet
46
58
  from . import group_context
@@ -56,7 +68,6 @@ from .analysis_inputs import (
56
68
  load_candidate_map,
57
69
  parse_evidence_paths,
58
70
  resolve_analysis_head,
59
- resolve_analysis_target,
60
71
  resolve_evidence_inputs,
61
72
  )
62
73
  from .stage_fix_carry import derive_stage_fix_carry
@@ -68,13 +79,11 @@ from .clarification_items import (
68
79
  scan_approval_gate,
69
80
  )
70
81
  from .error_report import prior_run_error_digest
71
- from .incremental_scope import ReverifyScopeError, parse_user_reverify_scope
82
+ from .incremental_scope import parse_user_reverify_scope
72
83
  from .implementation_direction import (
73
84
  DirectionSelectionError,
74
85
  SelectedDirection,
75
86
  lexical_absolute_path,
76
- resolve_selected_direction,
77
- validate_selected_direction_plan,
78
87
  validate_task_artifact_path,
79
88
  write_selected_direction_snapshot,
80
89
  )
@@ -206,7 +215,9 @@ from .session import (
206
215
  resolve_inproc_lead_session_id,
207
216
  write_claude_resume_command_file,
208
217
  )
209
- from .pr_template import PrTemplateError, resolve_pr_template_path
218
+ from .phases.release_handoff.entry import (
219
+ materialize_handoff_input, resolve_pr_template,
220
+ )
210
221
  from .worker_artifacts import worker_provider_id
211
222
  from .workers import (
212
223
  normalize_workers,
@@ -214,11 +225,19 @@ from .workers import (
214
225
  resolve_profile_workers,
215
226
  validate_workers_against_profile,
216
227
  )
217
- from .workflow import compute_workflow_state, load_phase_forbidden
228
+ from .workflow import compute_workflow_state, load_phase_boundary
218
229
  from .locks import worktree_provision_mutex
219
230
  from . import stage_targets as _stage_targets
220
- from .plan_run_root import plan_run_root_from_approved_plan
221
- from . import implementation_stage as _implementation_stage
231
+ from .phases.catalog import PhaseAssetError, profile_markdown, resolve_include
232
+ from .phases.final_verification.target import acquire_final_verification_target
233
+ from .phases.final_verification.entry import (
234
+ apply_verification_target,
235
+ reject_prepare_flags,
236
+ single_stage_worktree,
237
+ verification_target_request,
238
+ write_verification_target_snapshot,
239
+ )
240
+ from .phases.implementation import entry as _implementation_stage
222
241
  from .work_categories import resolve_work_category
223
242
  from .worktree import (
224
243
  WorktreeProvision,
@@ -228,7 +247,6 @@ from .brief_frontmatter import (
228
247
  has_reporter_confirmation_contract,
229
248
  read_brief_frontmatter,
230
249
  )
231
- from .scope_provenance import brief_end_state_id_sequence
232
250
 
233
251
  # Frontmatter approval-flag matcher.
234
252
  #
@@ -509,6 +527,9 @@ def _set_data_json_approved_true_if_present(path: Path) -> bool:
509
527
  # PrepareError 는 stage_targets(저수준)에 정의돼 final-verification 과 container
510
528
  # 가 whole-task 통합 실패를 동일 타입으로 받는다. run.py 는 재노출만 한다.
511
529
  PrepareError = _stage_targets.PrepareError
530
+ from .phases.implementation_option_selection.entry import (
531
+ validate_analyser_roster, validate_brief_requirements,
532
+ )
512
533
 
513
534
 
514
535
  def _normalize_lead_runtime(value: str) -> str:
@@ -688,17 +709,7 @@ def _validate_stage_structure(plan_path: str) -> None:
688
709
  RUN_STEP_BUDGET = _stage_targets.RUN_STEP_BUDGET
689
710
 
690
711
 
691
- def _parse_stage_map_into_ctx(plan_path: str) -> list:
692
- """Parse the approved plan into context records for execution consumers."""
693
- from .stage_map import StageMapError, parse_stage_map_file, stage_map_records
694
-
695
- try:
696
- return stage_map_records(parse_stage_map_file(Path(plan_path)))
697
- except StageMapError as exc:
698
- raise PrepareError(
699
- "approved-plan 의 Stage Map 을 신뢰할 수 없어 거부합니다 "
700
- f"({plan_path}): {exc.reason}. plan 의 Stage Map 을 점검하세요."
701
- ) from exc
712
+ from .stage_map import parse_stage_map_for_prepare as _parse_stage_map_into_ctx
702
713
 
703
714
 
704
715
  def _apply_cli_approval(path: str) -> None:
@@ -1215,8 +1226,11 @@ _HTML_COMMENT = re.compile(r"<!--.*?-->", re.DOTALL)
1215
1226
  def _expand_profile_includes(profile_path: Path, _depth: int = 0) -> str:
1216
1227
  """Resolve `{{INCLUDE:<name>}}` directives in a profile file.
1217
1228
 
1218
- Includes are resolved relative to the profile's directory. A maximum
1219
- recursion depth of 4 prevents accidental cycles; the included file
1229
+ `phases.catalog.resolve_include` picks the search root: an unmigrated
1230
+ profile resolves beside itself, and a phase package's `profile.md`
1231
+ resolves through the logical paths of the asset root that holds it.
1232
+ Recursion stops at depth 4
1233
+ so a cycle fails instead of looping; the included file
1220
1234
  contents replace the directive line in-place. Missing include targets
1221
1235
  raise PrepareError so a bad reference fails fast instead of silently
1222
1236
  leaving a `{{INCLUDE:...}}` token in the rendered profile.
@@ -1235,7 +1249,10 @@ def _expand_profile_includes(profile_path: Path, _depth: int = 0) -> str:
1235
1249
 
1236
1250
  def _sub(match: "re.Match[str]") -> str:
1237
1251
  target_name = match.group(1).strip()
1238
- target = profile_path.parent / target_name
1252
+ try:
1253
+ target = resolve_include(profile_path, target_name)
1254
+ except PhaseAssetError as exc:
1255
+ raise PrepareError(str(exc)) from exc
1239
1256
  if not target.is_file():
1240
1257
  raise PrepareError(
1241
1258
  f"profile include target missing: {target} (referenced from {profile_path})"
@@ -1289,7 +1306,10 @@ class _ResolvedAssets:
1289
1306
 
1290
1307
  def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _ResolvedAssets:
1291
1308
  """task-type 별 profile + 공통 템플릿/스킬/validator 경로를 해소하고 존재를 확인한다."""
1292
- profile_file = workspace_root / "prompts" / "profiles" / f"{inp.task_type}.md"
1309
+ try:
1310
+ profile_file = profile_markdown(workspace_root, inp.task_type)
1311
+ except PhaseAssetError as exc:
1312
+ raise PrepareError(str(exc)) from exc
1293
1313
  if not profile_file.is_file():
1294
1314
  raise PrepareError(
1295
1315
  f"analysis profile file not found for task-type {inp.task_type}: "
@@ -1335,9 +1355,7 @@ def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _Resolv
1335
1355
  raise PrepareError(
1336
1356
  f"required okstra template or lead contract missing: {required}.{_INSTALL_HINT}"
1337
1357
  )
1338
- host_rules_file = (
1339
- workspace_root / "prompts" / "host-orchestration" / f"{inp.task_type}.md"
1340
- )
1358
+ host_rules_file = host_rules(workspace_root, inp.task_type)
1341
1359
  return _ResolvedAssets(
1342
1360
  profile_file=profile_file,
1343
1361
  prompt_template=prompt_template,
@@ -1417,17 +1435,12 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
1417
1435
  if inp.task_type == "release-handoff":
1418
1436
  # brief 는 entry phase 의 입력물 — release-handoff 는 검증 보고서를
1419
1437
  # 인용하는 input 문서를 prepare 가 직접 생성해 brief 자리에 채운다
1420
- # (_materialize_release_handoff_input, 이 검증 직후 실행).
1438
+ # (materialize_handoff_input, 이 검증 직후 실행).
1421
1439
  pass
1422
1440
  elif not inp.brief_path.is_file():
1423
1441
  raise PrepareError(f"task brief not found: {inp.brief_path}")
1424
- elif inp.task_type == "implementation-option-selection" and not brief_end_state_id_sequence(
1425
- inp.brief_path
1426
- ):
1427
- raise PrepareError(
1428
- "regenerate the brief with stable end-state IDs before starting "
1429
- "implementation-option-selection"
1430
- )
1442
+ elif inp.task_type == "implementation-option-selection":
1443
+ validate_brief_requirements(inp.brief_path)
1431
1444
  ctx_stage_map: list = []
1432
1445
  # implementation 과 final-verification 은 둘 다 승인된 plan 의 Stage Map 을
1433
1446
  # 입력으로 받는다(전자는 실행 scope, 후자는 검증 scope).
@@ -1438,18 +1451,10 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
1438
1451
  "--approved-plan <path-to-final-report.md>"
1439
1452
  )
1440
1453
  if inp.task_type == "final-verification":
1441
- # final-verification 에서 --approve / --implementation-option 은
1442
- # 의미가 없다 (승인은 implementation 진입 시 이미 끝났다).
1443
- if inp.approve_plan_ack:
1444
- raise PrepareError(
1445
- "--approve is only meaningful with --task-type implementation "
1446
- "and --approved-plan <path>"
1447
- )
1448
- if inp.implementation_option:
1449
- raise PrepareError(
1450
- "--implementation-option is only meaningful with --task-type "
1451
- "implementation and --approved-plan <path>"
1452
- )
1454
+ reject_prepare_flags(
1455
+ approve_plan_ack=inp.approve_plan_ack,
1456
+ implementation_option=inp.implementation_option,
1457
+ )
1453
1458
  ctx_stage_map = _parse_stage_map_into_ctx(inp.approved_plan_path)
1454
1459
  else:
1455
1460
  if inp.approve_plan_ack:
@@ -1482,143 +1487,6 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
1482
1487
  return ctx_stage_map
1483
1488
 
1484
1489
 
1485
- _PLANNING_REPORT_RE = re.compile(
1486
- r"^final-report-implementation-planning-\d{3,}\.(?:md|data\.json)$"
1487
- )
1488
-
1489
-
1490
- def _resolve_planning_input_path(
1491
- path_value: str, project_root: Path
1492
- ) -> Path | None:
1493
- """Resolve cwd-first planning inputs without following symbolic links."""
1494
- raw_path = Path(path_value).expanduser()
1495
- cwd_candidate = lexical_absolute_path(raw_path)
1496
- if cwd_candidate.is_file():
1497
- return cwd_candidate
1498
- if not raw_path.is_absolute():
1499
- project_candidate = lexical_absolute_path(project_root / raw_path)
1500
- if project_candidate.is_file():
1501
- return project_candidate
1502
- return None
1503
-
1504
-
1505
- def _is_existing_planning_report(
1506
- path_value: str, project_root: Path, inp: PrepareInputs
1507
- ) -> bool:
1508
- from .paths import task_dir
1509
-
1510
- try:
1511
- report = lexical_absolute_path(Path(path_value))
1512
- task_root = lexical_absolute_path(
1513
- task_dir(project_root, inp.task_group, inp.task_id)
1514
- )
1515
- relative = report.relative_to(task_root)
1516
- except ValueError:
1517
- return False
1518
- has_planning_layout = (
1519
- _PLANNING_REPORT_RE.fullmatch(report.name) is not None
1520
- and relative.parts[:3] == (
1521
- "runs",
1522
- "implementation-planning",
1523
- "reports",
1524
- )
1525
- and len(relative.parts) == 4
1526
- )
1527
- if not has_planning_layout:
1528
- return False
1529
- try:
1530
- validate_task_artifact_path(report, task_root, "planning report")
1531
- except DirectionSelectionError:
1532
- return False
1533
- return True
1534
-
1535
-
1536
- def _validate_planning_entry_inputs(
1537
- project_root: Path, inp: PrepareInputs
1538
- ) -> None:
1539
- if inp.task_type != "implementation-planning":
1540
- if inp.selected_direction_path:
1541
- raise PrepareError(
1542
- "--selected-direction is only meaningful with --task-type "
1543
- f"implementation-planning; got {inp.task_type}"
1544
- )
1545
- return
1546
- if inp.selected_direction_path and inp.clarification_response_path:
1547
- raise PrepareError(
1548
- "implementation-planning accepts either --selected-direction for a "
1549
- "new plan or --clarification-response for a rerun, not both"
1550
- )
1551
- if inp.selected_direction_path:
1552
- return
1553
- if inp.clarification_response_path:
1554
- if not _is_existing_planning_report(
1555
- inp.clarification_response_path, project_root, inp
1556
- ):
1557
- raise PrepareError(
1558
- "implementation-planning rerun --clarification-response must point "
1559
- "to an existing implementation-planning report for the same task"
1560
- )
1561
- return
1562
- raise PrepareError(
1563
- "implementation-planning requires --selected-direction for a new plan or "
1564
- "--clarification-response to an existing implementation-planning report"
1565
- )
1566
-
1567
-
1568
- def _resolve_planning_direction(inp: PrepareInputs) -> SelectedDirection | None:
1569
- if inp.task_type != "implementation-planning" or not inp.selected_direction_path:
1570
- return None
1571
- from .paths import task_dir
1572
-
1573
- try:
1574
- report = lexical_absolute_path(Path(inp.selected_direction_path))
1575
- expected_task_root = lexical_absolute_path(
1576
- task_dir(Path(inp.project_root), inp.task_group, inp.task_id)
1577
- )
1578
- report.relative_to(expected_task_root)
1579
- except ValueError as exc:
1580
- raise PrepareError(
1581
- "selected direction report must belong to the same task"
1582
- ) from exc
1583
- try:
1584
- validate_task_artifact_path(
1585
- report, expected_task_root, "selected direction report"
1586
- )
1587
- return resolve_selected_direction(
1588
- report,
1589
- expected_task_key=(
1590
- f"{inp.project_id}:{inp.task_group}:{inp.task_id}"
1591
- ),
1592
- )
1593
- except DirectionSelectionError as exc:
1594
- raise PrepareError(str(exc)) from exc
1595
-
1596
-
1597
- def _validate_reverify_scope(inp: PrepareInputs) -> None:
1598
- """A pinned re-verification scope is only actionable on a planning re-run.
1599
-
1600
- Every other phase renders the tokens too (the template always reads them),
1601
- but nothing consumes them there — so a value outside the one phase that
1602
- acts on it is a caller mistake, not a preference to honour silently.
1603
- """
1604
- if not (inp.reverify_scope or "").strip():
1605
- return
1606
- if inp.task_type != "implementation-planning":
1607
- raise PrepareError(
1608
- "--reverify-scope is only meaningful with --task-type "
1609
- f"implementation-planning; got {inp.task_type}"
1610
- )
1611
- if not inp.clarification_response_path:
1612
- raise PrepareError(
1613
- "--reverify-scope needs --clarification-response: there is no prior "
1614
- "report to narrow re-verification against"
1615
- )
1616
- try:
1617
- parse_user_reverify_scope(inp.reverify_scope)
1618
- except ReverifyScopeError as exc:
1619
- raise PrepareError(str(exc)) from exc
1620
-
1621
-
1622
1490
  def _implementation_plan_contract(
1623
1491
  inp: PrepareInputs,
1624
1492
  ) -> tuple[str, Path, dict | None]:
@@ -1764,13 +1632,12 @@ def _validate_analysis_prepare_inputs(project_root: Path, inp: PrepareInputs) ->
1764
1632
  )
1765
1633
  return
1766
1634
  if inp.task_type == "project-analysis":
1767
- if inp.analysis_target:
1768
- raise PrepareError("project-analysis does not accept an analysis target")
1769
- if inp.evidence_inputs_raw:
1770
- 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)
1771
1638
  return
1772
- if inp.task_type == "feature-analysis" and not inp.analysis_target.strip():
1773
- raise PrepareError("feature-analysis requires --analysis-target")
1639
+ if inp.task_type == "feature-analysis":
1640
+ validate_feature_target_input(inp.analysis_target)
1774
1641
  if inp.task_type != "feature-analysis" and inp.analysis_target:
1775
1642
  raise PrepareError(f"{inp.task_type} does not accept an analysis target")
1776
1643
  paths = parse_evidence_paths(inp.evidence_inputs_raw, project_root)
@@ -1785,127 +1652,10 @@ def _validate_analysis_prepare_inputs(project_root: Path, inp: PrepareInputs) ->
1785
1652
  raise PrepareError(str(exc)) from exc
1786
1653
 
1787
1654
 
1788
- def _collect_handoff_source_report_rows(
1789
- rows: list, nums: list,
1790
- ) -> list:
1791
- """선택 stage 들의 마지막 verified 행에서 인용 표 행을 만든다 (last-wins)."""
1792
- last_verified: dict = {}
1793
- for r in rows:
1794
- if r.get("status") == "verified" and isinstance(r.get("stage"), int):
1795
- last_verified[r["stage"]] = r
1796
- return [(str(n), Path(last_verified[n]["report_path"])) for n in nums]
1797
-
1798
-
1799
- def _render_handoff_sources(sources: list, project_root: Path) -> tuple[str, str]:
1800
- """인용 표와 미해결 조건은 같은 원본 판정에서 렌더한다."""
1801
- from .release_gate import release_handoff_allowed, verdict_token
1802
-
1803
- rows, conditions = [], []
1804
- for stage, report in sources:
1805
- data = load_owned_object(report, artifact="handoff verification report")
1806
- if not release_handoff_allowed(data):
1807
- raise PrepareError(f"verification no longer release-ready: {report}")
1808
- relative = relative_to_project_root(report, project_root) or str(report)
1809
- rows.append(f"| {stage} | {relative} | `{verdict_token(data)}` |")
1810
- if verdict_token(data) == "conditional-accept":
1811
- for condition in data["finalVerdict"]["conditionalAcceptanceConditions"]:
1812
- conditions.append(
1813
- f"- Stage {stage}, `{condition.get('id', '')}`\n"
1814
- f" - Condition: {condition.get('condition', '')}\n"
1815
- f" - Evidence required: {condition.get('evidenceRequired', '')}\n"
1816
- f" - blocksReleaseHandoff: {json.dumps(condition['blocksReleaseHandoff'])}"
1817
- )
1818
- section = "## Unresolved Conditional Acceptance Conditions\n\n" + "\n".join(conditions) if conditions else ""
1819
- return "\n".join(rows), section
1820
1655
 
1821
1656
 
1822
- def _materialize_release_handoff_input(
1823
- workspace_root: Path, project_root: Path, inp: "PrepareInputs",
1824
- ) -> dict:
1825
- """release-handoff 의 입력 문서를 검증 보고서 인용으로 자동 생성한다.
1826
-
1827
- brief 는 entry phase 의 입력물이라 release-handoff 에는 없다 — 이 run 의
1828
- `--task-brief` 자리는 여기서 생성된 input 문서가 채운다. stage 자격은
1829
- okstra_ctl.handoff 의 SSOT 판정을 prepare 에서도 그대로 강제한다.
1830
- 반환: ctx 에 올릴 {"HANDOFF_STAGES": ...}."""
1831
- from .consumers import read_consumers
1832
- from .handoff import HandoffError, _require_eligible, compute_eligibility
1833
- from .paths import task_dir
1834
- from .render import render_template_with_ctx
1835
- from .run_context import _now_task_date
1836
1657
 
1837
- # Path("") 는 "." 로 정규화된다 — 빈 brief 센티널은 둘 다로 들어올 수 있다.
1838
- if str(inp.brief_path).strip() not in ("", "."):
1839
- raise PrepareError(
1840
- "--task-brief is not accepted for --task-type release-handoff — "
1841
- "the input document is generated from the cited final-verification "
1842
- "reports (pick stages via --stages, or leave it empty to take "
1843
- "every eligible stage)"
1844
- )
1845
- if not inp.approved_plan_path:
1846
- raise PrepareError(
1847
- "release-handoff requires --approved-plan "
1848
- "<implementation-planning final-report> — the Stage Map and the "
1849
- "consumers ledger drive stage eligibility"
1850
- )
1851
- plan = Path(inp.approved_plan_path)
1852
- if not plan.is_file():
1853
- raise PrepareError(f"approved plan not found: {plan}")
1854
- stage_map = _parse_stage_map_into_ctx(str(plan))
1855
- rows = read_consumers(plan_run_root_from_approved_plan(plan))
1856
1658
 
1857
- if inp.stages:
1858
- try:
1859
- nums = sorted({int(x) for x in inp.stages.split(",") if x.strip()})
1860
- except ValueError:
1861
- raise PrepareError(
1862
- f"--stages must be a comma-separated int list, got {inp.stages!r}"
1863
- )
1864
- if not nums:
1865
- raise PrepareError("--stages must select at least one stage")
1866
- try:
1867
- _require_eligible(stage_map, rows, nums)
1868
- except HandoffError as exc:
1869
- raise PrepareError(str(exc)) from exc
1870
- else:
1871
- eligibility = compute_eligibility(stage_map, rows)
1872
- nums = [e["stage"] for e in eligibility if e["eligible"]]
1873
- if not nums:
1874
- blocked = "; ".join(
1875
- f"stage {e['stage']}: {', '.join(e['reasons'])}"
1876
- for e in eligibility) or "(no stage in the Stage Map)"
1877
- raise PrepareError(
1878
- "release-handoff has no eligible stage — every stage must "
1879
- "carry a release-ready single-stage final-verification of its "
1880
- f"recorded implementation commit. {blocked}"
1881
- )
1882
- stages_csv = ",".join(str(n) for n in nums)
1883
- report_rows = _collect_handoff_source_report_rows(rows, nums)
1884
-
1885
- source_rows, source_conditions = _render_handoff_sources(report_rows, project_root)
1886
-
1887
- template = (workspace_root / "templates" / "reports"
1888
- / "release-handoff-input.template.md")
1889
- if not template.is_file():
1890
- raise PrepareError(
1891
- f"release-handoff input template missing: {template}.{_INSTALL_HINT}"
1892
- )
1893
- out = (task_dir(project_root, inp.task_group, inp.task_id)
1894
- / "release-handoff-input.md")
1895
- out.parent.mkdir(parents=True, exist_ok=True)
1896
- render_template_with_ctx(str(template), str(out), {
1897
- "TASK_KEY": f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
1898
- "TASK_ID": inp.task_id,
1899
- "TASK_GROUP": inp.task_group,
1900
- "PROJECT_ID": inp.project_id,
1901
- "TASK_TYPE": inp.task_type,
1902
- "TASK_DATE": _now_task_date(),
1903
- "HANDOFF_STAGES": stages_csv,
1904
- "HANDOFF_SOURCE_REPORTS": source_rows,
1905
- "HANDOFF_CONDITIONS": source_conditions,
1906
- })
1907
- inp.brief_path = out
1908
- return {"HANDOFF_STAGES": stages_csv}
1909
1659
 
1910
1660
 
1911
1661
  QA_COMMAND_EXECUTING_TASK_TYPES = ("implementation", "final-verification")
@@ -2650,28 +2400,11 @@ def _read_model_defaults(path: Path) -> dict[str, tuple[str, ...]]:
2650
2400
  return defaults
2651
2401
 
2652
2402
 
2653
- def _validate_option_selection_roster(
2654
- task_type: str, workers: Sequence[str]
2655
- ) -> None:
2656
- """Require independent analysis from three workers before option selection."""
2657
- if task_type != "implementation-option-selection":
2658
- return
2659
- analyser_count = sum(worker != "report-writer" for worker in workers)
2660
- if analyser_count < 3:
2661
- raise PrepareError(
2662
- "implementation-option-selection requires at least 3 analyser workers"
2663
- )
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)
2664
2406
 
2665
2407
 
2666
- def _resolve_pr_template(inp: PrepareInputs) -> tuple[str, str]:
2667
- """release-handoff 전용 PR 본문 템플릿 경로 + 출처를 해소한다 (그 외엔 빈 값)."""
2668
- if inp.task_type != "release-handoff":
2669
- return "", ""
2670
- try:
2671
- resolved_tpl = resolve_pr_template_path(Path(inp.project_root), inp.pr_template_path)
2672
- except PrTemplateError as exc:
2673
- raise PrepareError(f"PR template resolution failed: {exc}") from exc
2674
- return str(resolved_tpl.path), resolved_tpl.source
2675
2408
 
2676
2409
 
2677
2410
  @dataclass
@@ -3460,139 +3193,6 @@ def _git_out(cwd, *args) -> str:
3460
3193
  return r.stdout.strip() if r.returncode == 0 else ""
3461
3194
 
3462
3195
 
3463
- def _single_stage_final_verification_worktree(inp: "PrepareInputs") -> WorktreeProvision:
3464
- """Placeholder until the selected stage registry row is resolved."""
3465
- return WorktreeProvision(
3466
- status="deferred-final-verification",
3467
- note=(
3468
- "final-verification single-stage uses the selected implementation "
3469
- "stage worktree from the registry"
3470
- ),
3471
- )
3472
-
3473
-
3474
- def _format_integration(integ) -> str:
3475
- """stage 자동 통합 결과를 사람이 읽을 한국어 마크다운 블록으로 만든다."""
3476
- def _csv(xs):
3477
- return ", ".join(str(x) for x in xs) if xs else "없음"
3478
-
3479
- skipped = "; ".join(f"stage {n}({why})" for n, why in integ.teardown_skipped)
3480
- lines = [
3481
- "- **Stage 자동 통합 결과** (whole-task final-verification):",
3482
- "- **머지된 stage:** " + _csv(integ.merged),
3483
- "- **이미 머지됨:** " + _csv(integ.already_merged),
3484
- "- **정리(teardown)된 stage:** " + _csv(integ.torn_down),
3485
- "- **정리 보류:** " + (skipped or "없음"),
3486
- ]
3487
- for w in integ.warnings:
3488
- lines.append("- **경고:** " + w)
3489
- return "\n".join(lines)
3490
-
3491
-
3492
- def _final_verification_target_request(
3493
- inp: "PrepareInputs",
3494
- ctx_stage_map: list[dict],
3495
- ) -> _stage_targets.FinalVerificationTargetRequest:
3496
- stage = (
3497
- int(inp.stage)
3498
- if inp.stage and inp.stage != "auto"
3499
- else None
3500
- )
3501
- return _stage_targets.FinalVerificationTargetRequest(
3502
- project_root=Path(inp.project_root),
3503
- project_id=inp.project_id,
3504
- task_group=inp.task_group,
3505
- task_id=inp.task_id,
3506
- work_category=inp.work_category,
3507
- approved_plan_path=Path(inp.approved_plan_path),
3508
- stage=stage,
3509
- stage_map=tuple(ctx_stage_map),
3510
- )
3511
-
3512
-
3513
- def _apply_final_verification_target(
3514
- ctx: dict,
3515
- acquisition: _stage_targets.FinalVerificationTargetAcquisition,
3516
- ) -> None:
3517
- """Translate acquired domain facts into render-context fields."""
3518
- target = acquisition.target
3519
- if target.scope == "single-stage":
3520
- stage = target.stages[0]
3521
- ctx["EXECUTOR_WORKTREE_PATH"] = target.worktree_path
3522
- ctx["EXECUTOR_WORKTREE_BRANCH"] = acquisition.worktree_branch
3523
- ctx["EXECUTOR_WORKTREE_BASE_REF"] = target.base
3524
- ctx["EXECUTOR_WORKTREE_STATUS"] = "reused-stage"
3525
- ctx["EXECUTOR_WORKTREE_NOTE"] = (
3526
- f"final-verification uses implementation stage {stage} worktree"
3527
- )
3528
- if acquisition.integration_result is not None:
3529
- ctx["STAGE_INTEGRATION"] = _format_integration(
3530
- acquisition.integration_result
3531
- )
3532
- if acquisition.relocations:
3533
- moved = "\n".join(
3534
- f"- stage {row['stage']}: `{row['from']}` → `{row['to']}`"
3535
- for row in acquisition.relocations
3536
- )
3537
- ctx["STAGE_INTEGRATION"] = (
3538
- f"{ctx.get('STAGE_INTEGRATION', '')}\n"
3539
- "Nested stage worktrees moved out of the verification worktree "
3540
- "(registry updated; contents and branches unchanged):\n"
3541
- f"{moved}"
3542
- ).strip()
3543
- diff_stat = _git_out(target.worktree_path, "diff", "--stat",
3544
- f"{target.base}..{target.head}")
3545
- ctx["VERIFICATION_SCOPE"] = target.scope
3546
- ctx["VERIFICATION_WORKTREE_PATH"] = target.worktree_path
3547
- ctx["VERIFICATION_BASE_REF"] = target.base
3548
- ctx["VERIFICATION_HEAD_REF"] = target.head
3549
- ctx["VERIFICATION_TARGET"] = _format_verification_target(target, diff_stat)
3550
-
3551
-
3552
- def _format_verification_target(
3553
- target: _stage_targets.FinalVerificationTarget,
3554
- diff_stat: str,
3555
- ) -> str:
3556
- reports = "\n".join(
3557
- f" - stage {s}: `{rp or '(report_path unrecorded)'}`"
3558
- for s, rp in zip(target.stages, target.reports)
3559
- )
3560
- return (
3561
- f"- **Verification scope:** `{target.scope}`\n"
3562
- f"- **Worktree:** `{target.worktree_path}`\n"
3563
- f"- **Verification base ref:** `{target.base}`\n"
3564
- f"- **Verification head ref:** `{target.head}`\n"
3565
- f"- **Stages under verification:** {target.stages}\n"
3566
- f"- **Source implementation reports:**\n{reports}\n"
3567
- f"- **Verification diff stat:**\n```\n{diff_stat}\n```"
3568
- )
3569
-
3570
-
3571
- def write_verification_target_snapshot(
3572
- instruction_set: Path,
3573
- target_markdown: str,
3574
- ) -> tuple[Path, str]:
3575
- """Persist one normalized final-verification target and return its digest."""
3576
- normalized = target_markdown.replace("\r\n", "\n").replace("\r", "\n")
3577
- normalized = normalized.rstrip() + "\n"
3578
- digest = "sha256:" + hashlib.sha256(normalized.encode("utf-8")).hexdigest()
3579
- path = instruction_set / "verification-target.md"
3580
- # 계산 규칙을 값과 함께 적는다. 워커는 이 다이제스트를 프롬프트 앵커로도
3581
- # 받는데, 규칙을 모르면 파일 전체 바이트로 재고 반드시 불일치를 본다 —
3582
- # 실측(2026-08-26): 그렇게 잰 검증자가 스냅샷이 변조됐다고 보고했다.
3583
- # 다이제스트 줄은 줄머리 앵커로 잘리므로(`_TARGET_DIGEST_RE`) 그 뒤에
3584
- # 붙는 이 줄은 본문에 들어가지 않는다.
3585
- path.write_text(
3586
- normalized
3587
- + f"- **Verification target digest:** `{digest}`\n"
3588
- + "- **Digest rule:** `sha256` of the text above this line, with CRLF "
3589
- "and CR normalized to LF, trailing whitespace stripped, and exactly "
3590
- "one closing newline. This line and the digest line are not covered.\n",
3591
- encoding="utf-8",
3592
- )
3593
- return path, digest
3594
-
3595
-
3596
3196
  def _write_verification_target_artifact(
3597
3197
  inp: PrepareInputs,
3598
3198
  ctx: dict,
@@ -3686,6 +3286,9 @@ def _write_instruction_set_sources(
3686
3286
  profile_rendered = profile_content
3687
3287
  if inp.task_type == "implementation":
3688
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
+ )
3689
3292
  profile_tokens = (
3690
3293
  "EXECUTOR_WORKER_ID",
3691
3294
  "EXECUTOR_PROVIDER",
@@ -3708,13 +3311,14 @@ def _write_instruction_set_sources(
3708
3311
  profile_rendered = profile_rendered.replace("{{" + key + "}}", ctx.get(key, ""))
3709
3312
  (instruction_set / "analysis-profile.md").write_text(profile_rendered, encoding="utf-8")
3710
3313
  if inp.task_type == "implementation":
3711
- executor_source = (
3712
- Path(ctx["WORKSPACE_ROOT"])
3713
- / "prompts"
3714
- / "profiles"
3715
- / "_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"])
3716
3321
  )
3717
- executor_rendered = executor_source.read_text(encoding="utf-8")
3718
3322
  for key in profile_tokens:
3719
3323
  executor_rendered = executor_rendered.replace(
3720
3324
  "{{" + key + "}}", ctx.get(key, "")
@@ -3824,17 +3428,10 @@ def _write_instruction_set_sources(
3824
3428
  ),
3825
3429
  )
3826
3430
  if inp.task_type == "technical-verification":
3827
- verification_path = write_technical_verification_input(
3828
- json.loads(ctx["TECHNICAL_VERIFICATION_INPUT_JSON"]),
3431
+ packet += materialize_technical_input_packet(
3432
+ ctx["TECHNICAL_VERIFICATION_INPUT_JSON"],
3829
3433
  Path(ctx["RUN_DIR"]), ctx["RUN_MANIFESTS_SEQ"],
3830
3434
  )
3831
- packet += (
3832
- "\n## Technical Verification Input\n\n"
3833
- f"- Frozen input: `{verification_path}`\n"
3834
- f"- Experiment copies only: `{ctx['RUN_DIR']}/experiments/{ctx['RUN_MANIFESTS_SEQ']}/<worker-id>/`\n"
3835
- "- Read the input before planning experiments; copy each fact identity verbatim into results.\n"
3836
- + "\n```json\n" + ctx["TECHNICAL_VERIFICATION_INPUT_JSON"] + "\n```\n"
3837
- )
3838
3435
  if inp.task_type in ANALYSIS_TASK_TYPES:
3839
3436
  packet += (
3840
3437
  "\n## Analysis Evidence\n\n"
@@ -4145,7 +3742,7 @@ def _record_fix_cycle_events(inp: PrepareInputs, ctx: dict) -> str:
4145
3742
 
4146
3743
  def _finalize_status_and_render_manifests(
4147
3744
  inp: PrepareInputs, ctx: dict, task_index_template: Path,
4148
- forbidden_by_phase: dict[str, str],
3745
+ boundary: dict[str, str],
4149
3746
  ) -> None:
4150
3747
  """최종 task/run status 를 확정하고 workflow state 를 재계산한 뒤 team-state,
4151
3748
  task-manifest, task-index, run-manifest, timeline, discovery 산출물을 렌더한다."""
@@ -4162,7 +3759,7 @@ def _finalize_status_and_render_manifests(
4162
3759
  current_run_status=ctx["CURRENT_RUN_STATUS"],
4163
3760
  current_task_status=ctx["CURRENT_TASK_STATUS"],
4164
3761
  render_only=inp.render_only,
4165
- forbidden_by_phase=forbidden_by_phase,
3762
+ boundary=boundary,
4166
3763
  work_category=inp.work_category,
4167
3764
  ))
4168
3765
  render_team_state(ctx["TEAM_STATE_PATH"], ctx)
@@ -4453,7 +4050,7 @@ def _reconcile_prior_runs(ctx: dict) -> None:
4453
4050
 
4454
4051
 
4455
4052
  def _initial_workflow_ctx(
4456
- inp: PrepareInputs, forbidden_by_phase: dict
4053
+ inp: PrepareInputs, boundary: dict
4457
4054
  ) -> dict[str, str]:
4458
4055
  """Workflow tokens for a run that has been prepared but not started.
4459
4056
 
@@ -4470,7 +4067,7 @@ def _initial_workflow_ctx(
4470
4067
  current_run_status=run_status,
4471
4068
  current_task_status=task_status,
4472
4069
  render_only=inp.render_only,
4473
- forbidden_by_phase=forbidden_by_phase,
4070
+ boundary=boundary,
4474
4071
  ),
4475
4072
  }
4476
4073
 
@@ -4595,7 +4192,7 @@ def _provision_task_and_stage(
4595
4192
  okstra_home(), inp.project_id, task_group_segment, task_id_segment,
4596
4193
  ):
4597
4194
  if inp.task_type == "final-verification" and inp.stage and inp.stage != "auto":
4598
- worktree = _single_stage_final_verification_worktree(inp)
4195
+ worktree = single_stage_worktree()
4599
4196
  # Single-stage final-verification namespaces its run path under
4600
4197
  # runs/final-verification/stage-<N> (same isolation as
4601
4198
  # implementation) so concurrent per-stage verifications never
@@ -4824,16 +4421,11 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4824
4421
  technical_input = None
4825
4422
  from .paths import task_dir
4826
4423
  if inp.task_type == "technical-verification":
4827
- if not inp.clarification_response_path:
4828
- raise PrepareError("technical-verification requires --clarification-response pointing to this task's option-selection record")
4829
- try:
4830
- technical_input = resolve_technical_verification_input(
4831
- Path(inp.clarification_response_path), project_root,
4832
- task_dir(project_root, inp.task_group, inp.task_id),
4833
- f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
4834
- )
4835
- except ValueError as exc:
4836
- 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
+ )
4837
4429
  if inp.task_type == "implementation":
4838
4430
  ctx_stage_map = _prepare_implementation_approved_plan(inp)
4839
4431
 
@@ -4841,11 +4433,13 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4841
4433
  # 이후의 모든 brief 소비 경로(material/instruction-set 복사)는 그대로 동작한다.
4842
4434
  handoff_tokens = {"HANDOFF_STAGES": ""}
4843
4435
  if inp.task_type == "release-handoff":
4844
- handoff_tokens = _materialize_release_handoff_input(
4845
- workspace_root, project_root, inp)
4436
+ handoff_tokens = materialize_handoff_input(
4437
+ workspace_root, project_root, inp, install_hint=_INSTALL_HINT)
4846
4438
 
4847
4439
  workers, selected_reviewers = _resolve_roster(inp, profile_file)
4848
- 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
+ )
4849
4443
 
4850
4444
  assignment_context, assignment_plan = _resolve_prepare_assignments(
4851
4445
  inp=inp,
@@ -5006,7 +4600,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
5006
4600
  if inp.approved_plan_path else ""
5007
4601
  )
5008
4602
 
5009
- forbidden_by_phase = load_phase_forbidden(workspace_root)
4603
+ boundary = load_phase_boundary(workspace_root, inp.task_type)
5010
4604
 
5011
4605
  # ---- assemble full ctx (the values render functions expect) ----
5012
4606
  ctx.update({
@@ -5032,7 +4626,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
5032
4626
  "BRIEF_RELATIVE_PATH": brief_relative,
5033
4627
  **_model_ctx(models),
5034
4628
  **_related_tasks_ctx(ctx, inp),
5035
- **_initial_workflow_ctx(inp, forbidden_by_phase),
4629
+ **_initial_workflow_ctx(inp, boundary),
5036
4630
  "VALIDATION_STATUS": "not-run",
5037
4631
  "VALIDATION_UPDATED_AT": "",
5038
4632
  "VALIDATION_FAILURES_JSON": "[]",
@@ -5053,10 +4647,26 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
5053
4647
  ctx["CLAUDE_RESUME_COMMAND_RELATIVE_PATH"] = ""
5054
4648
  ctx["CLAUDE_RESUME_COMMAND_FILENAME"] = ""
5055
4649
  if inp.task_type == "final-verification":
5056
- acquisition = _stage_targets.acquire_final_verification_target(
5057
- _final_verification_target_request(inp, ctx_stage_map)
4650
+ acquisition = acquire_final_verification_target(
4651
+ verification_target_request(
4652
+ project_root=inp.project_root,
4653
+ project_id=inp.project_id,
4654
+ task_group=inp.task_group,
4655
+ task_id=inp.task_id,
4656
+ work_category=inp.work_category,
4657
+ approved_plan_path=inp.approved_plan_path,
4658
+ stage=inp.stage,
4659
+ stage_map=ctx_stage_map,
4660
+ )
4661
+ )
4662
+ target = acquisition.target
4663
+ diff_stat = _git_out(
4664
+ target.worktree_path,
4665
+ "diff",
4666
+ "--stat",
4667
+ f"{target.base}..{target.head}",
5058
4668
  )
5059
- _apply_final_verification_target(ctx, acquisition)
4669
+ apply_verification_target(ctx, acquisition, diff_stat)
5060
4670
 
5061
4671
  _write_bundle_artifacts(
5062
4672
  inp, ctx, project_root, lead_runtime, claude_session_id,
@@ -5083,7 +4693,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
5083
4693
 
5084
4694
  # ---- final status + manifest/discovery renders ----
5085
4695
  _finalize_status_and_render_manifests(
5086
- inp, ctx, task_index_template, forbidden_by_phase
4696
+ inp, ctx, task_index_template, boundary
5087
4697
  )
5088
4698
  persist_static_execution_identity(
5089
4699
  run_manifest_path=Path(ctx["RUN_MANIFEST_PATH"]),