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
@@ -0,0 +1,296 @@
1
+ """final-verification 이 검증할 대상을 고른다.
2
+
3
+ 단일 stage 검증은 그 stage 의 worktree 를 읽기만 한다. 전체 작업 검증은 완료된
4
+ stage 전부를 담은 stage 브랜치가 있으면 그것을 쓰고, 없으면 task 브랜치에 통합한
5
+ 결과를 쓴다. 원장·registry·Git·통합은 공통 모듈이 제공하고, 여기서는 어느 것을
6
+ 언제 부를지만 정한다. 전체 과정은 task 키 잠금 하나 안에서 돈다.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, replace
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from okstra_project.dirs import okstra_home
15
+ from okstra_project.slug import slugify
16
+
17
+ from okstra_ctl import worktree_registry
18
+ from okstra_ctl.consumers import (
19
+ backfill_done_from_carry,
20
+ latest_done_by_stage,
21
+ read_stage_consumer_state,
22
+ )
23
+ from okstra_ctl.locks import worktree_provision_mutex
24
+ from okstra_ctl.plan_run_root import plan_run_root_from_approved_plan
25
+ from okstra_ctl.prepare_error import PrepareError
26
+ from okstra_ctl.stage_integrate import IntegrateResult
27
+ from okstra_ctl.stage_reconcile import auto_reconcile_best_effort
28
+ from okstra_ctl.stage_targets import (
29
+ FinalVerificationTarget,
30
+ StageTargetError,
31
+ _resolve_and_integrate_whole_task_unlocked,
32
+ containing_stage,
33
+ relocate_nested_stage_worktrees,
34
+ )
35
+ from okstra_ctl.worktree import _git, is_dirty_excluding_okstra
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class FinalVerificationTargetRequest:
40
+ """Semantic inputs needed to acquire one final-verification target."""
41
+
42
+ project_root: Path
43
+ project_id: str
44
+ task_group: str
45
+ task_id: str
46
+ work_category: str
47
+ approved_plan_path: Path
48
+ stage: int | None
49
+ stage_map: tuple[dict[str, Any], ...]
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class FinalVerificationTargetAcquisition:
54
+ """Resolved target facts returned to the prepare adapter."""
55
+
56
+ target: FinalVerificationTarget
57
+ worktree_branch: str
58
+ integration_result: IntegrateResult | None
59
+ # whole-task 진입이 task worktree 안에서 꺼낸 중첩 stage worktree.
60
+ # `{"stage": N, "from": <old path>, "to": <new path>}` 행.
61
+ relocations: tuple[dict[str, Any], ...] = ()
62
+
63
+
64
+ def _resolve_single_stage_target(
65
+ *,
66
+ requested_stage: int,
67
+ done_rows: list[dict[str, Any]],
68
+ stage_base: str,
69
+ stage_worktree_path: str,
70
+ stage_head: str,
71
+ stage_dirty: bool,
72
+ ) -> FinalVerificationTarget:
73
+ """Resolve single-stage final-verification target, enforcing all gates."""
74
+ n = requested_stage
75
+ done_by_stage = latest_done_by_stage(done_rows)
76
+ if n not in done_by_stage:
77
+ raise StageTargetError(
78
+ f"final-verification(single-stage): stage {n} not done — "
79
+ f"run implementation --stage {n} first"
80
+ )
81
+ if not stage_worktree_path:
82
+ raise StageTargetError(
83
+ f"final-verification(single-stage): stage worktree not found for "
84
+ f"stage {n} (torn down?) — use whole-task mode (--stage auto)"
85
+ )
86
+ if stage_dirty:
87
+ raise StageTargetError(
88
+ "final-verification: worktree has uncommitted source changes "
89
+ "(outside .okstra/) — commit or stash before verifying"
90
+ )
91
+ return FinalVerificationTarget(
92
+ scope="single-stage",
93
+ base=stage_base,
94
+ head=stage_head,
95
+ worktree_path=stage_worktree_path,
96
+ stages=[n],
97
+ reports=[done_by_stage[n].get("report_path", "")],
98
+ )
99
+
100
+
101
+ def _read_final_verification_done_rows(
102
+ request: FinalVerificationTargetRequest,
103
+ registry_coordinates: tuple[str, str, str],
104
+ ) -> list[dict[str, Any]]:
105
+ plan_run_root = plan_run_root_from_approved_plan(request.approved_plan_path)
106
+ backfill_done_from_carry(plan_run_root)
107
+ project_id, task_group, task_id = registry_coordinates
108
+ auto_reconcile_best_effort(
109
+ replace(
110
+ request,
111
+ project_id=project_id,
112
+ task_group=task_group,
113
+ task_id=task_id,
114
+ ),
115
+ plan_run_root,
116
+ )
117
+ return read_stage_consumer_state(plan_run_root).done_rows
118
+
119
+
120
+ def _final_verification_registry_coordinates(
121
+ request: FinalVerificationTargetRequest,
122
+ ) -> tuple[str, str, str]:
123
+
124
+ def segment(value: str) -> str:
125
+ return slugify(value) or "_"
126
+
127
+ return (
128
+ segment(request.project_id),
129
+ segment(request.task_group),
130
+ segment(request.task_id),
131
+ )
132
+
133
+
134
+ def _acquire_single_stage_target(
135
+ request: FinalVerificationTargetRequest,
136
+ done_rows: list[dict[str, Any]],
137
+ registry_coordinates: tuple[str, str, str],
138
+ ) -> FinalVerificationTargetAcquisition:
139
+ stage = request.stage
140
+ assert stage is not None
141
+ row = worktree_registry.get_stage_row(*registry_coordinates, stage)
142
+ worktree_path = (row or {}).get("worktree_path", "")
143
+ head = ""
144
+ if worktree_path and Path(worktree_path).is_dir():
145
+ head_result = _git(worktree_path, "rev-parse", "HEAD")
146
+ if head_result.returncode == 0:
147
+ head = head_result.stdout.strip()
148
+ if not head:
149
+ worktree_path = ""
150
+ target = _resolve_single_stage_target(
151
+ requested_stage=stage,
152
+ done_rows=done_rows,
153
+ stage_base=(row or {}).get("base_ref", ""),
154
+ stage_worktree_path=worktree_path,
155
+ stage_head=head,
156
+ stage_dirty=(
157
+ is_dirty_excluding_okstra(worktree_path) if worktree_path else False
158
+ ),
159
+ )
160
+ return FinalVerificationTargetAcquisition(
161
+ target=target,
162
+ worktree_branch=(row or {}).get("branch", ""),
163
+ integration_result=None,
164
+ )
165
+
166
+
167
+ def _acquire_tip_stage_target(
168
+ request: FinalVerificationTargetRequest,
169
+ done_rows: list[dict[str, Any]],
170
+ registry_coordinates: tuple[str, str, str],
171
+ ) -> FinalVerificationTargetAcquisition | None:
172
+ """전체를 담은 단일 stage 브랜치가 있으면 그것을 whole-task 검증 대상으로
173
+ 삼는다 — 머지 없이. 없거나 그 워크트리가 쓸 수 없으면 None 을 돌려 기존
174
+ task 브랜치 통합 경로로 넘긴다."""
175
+ done = latest_done_by_stage(done_rows)
176
+ planned = [int(s["stage_number"]) for s in request.stage_map]
177
+ if any(n not in done for n in planned):
178
+ return None # 미완 stage 의 거부 메시지는 기존 경로가 낸다
179
+ tip = containing_stage(request.project_root,
180
+ {n: done[n] for n in planned})
181
+ if tip is None:
182
+ return None
183
+ row = worktree_registry.get_stage_row(*registry_coordinates, tip) or {}
184
+ worktree_path = row.get("worktree_path", "")
185
+ if not worktree_path or not Path(worktree_path).is_dir():
186
+ return None
187
+ head_result = _git(worktree_path, "rev-parse", "HEAD")
188
+ if head_result.returncode != 0:
189
+ return None
190
+ head = head_result.stdout.strip()
191
+ if head != done[tip].get("head_commit", ""):
192
+ # 브랜치 tip 이 done 기록과 갈라져 있다. 검증 대상이 무엇인지 모호하므로
193
+ # 판정을 기존 경로로 넘긴다.
194
+ return None
195
+ if is_dirty_excluding_okstra(worktree_path):
196
+ raise StageTargetError(
197
+ "final-verification: worktree has uncommitted source changes "
198
+ "(outside .okstra/) — commit or stash before verifying"
199
+ )
200
+ anchor_base = worktree_registry.get_implementation_base(
201
+ *registry_coordinates) or ""
202
+ stages = sorted(planned)
203
+ return FinalVerificationTargetAcquisition(
204
+ target=FinalVerificationTarget(
205
+ scope="whole-task",
206
+ base=anchor_base,
207
+ head=head,
208
+ worktree_path=worktree_path,
209
+ stages=stages,
210
+ reports=[done[n].get("report_path", "") for n in stages],
211
+ ),
212
+ relocations=(),
213
+ worktree_branch=row.get("branch", ""),
214
+ integration_result=None,
215
+ )
216
+
217
+
218
+ def _acquire_whole_task_target(
219
+ request: FinalVerificationTargetRequest,
220
+ done_rows: list[dict[str, Any]],
221
+ registry_coordinates: tuple[str, str, str],
222
+ ) -> FinalVerificationTargetAcquisition:
223
+ tip = _acquire_tip_stage_target(request, done_rows, registry_coordinates)
224
+ if tip is not None:
225
+ return tip
226
+
227
+ project_id, task_group, task_id = registry_coordinates
228
+ entry = worktree_registry.lookup(project_id, task_group, task_id)
229
+ worktree_path = (
230
+ entry.worktree_path if entry is not None else str(request.project_root)
231
+ )
232
+ relocations = (
233
+ relocate_nested_stage_worktrees(
234
+ project_id, task_group, task_id, worktree_path,
235
+ stages=[int(row["stage_number"]) for row in request.stage_map],
236
+ )
237
+ if entry is not None
238
+ else []
239
+ )
240
+ whole = _resolve_and_integrate_whole_task_unlocked(
241
+ project_id=project_id,
242
+ task_group=task_group,
243
+ task_id=task_id,
244
+ task_worktree_path=worktree_path,
245
+ stage_map=list(request.stage_map),
246
+ done_rows=done_rows,
247
+ anchor_base=(
248
+ worktree_registry.get_implementation_base(
249
+ project_id, task_group, task_id
250
+ )
251
+ or ""
252
+ ),
253
+ # 정리는 판정 뒤로 미룬다(Phase 7 `teardown-stages`). 되돌릴 수 없는 정리를
254
+ # 판정 앞에 두면, 재작업이 가장 필요한 blocked 판정에서 stage 작업물이 이미
255
+ # 사라져 있다. 여기서는 통합만 하고 worktree/registry 키는 남긴다.
256
+ teardown=False,
257
+ )
258
+ return FinalVerificationTargetAcquisition(
259
+ target=whole["target"],
260
+ relocations=tuple(relocations),
261
+ worktree_branch=entry.branch if entry is not None else "",
262
+ integration_result=whole["integrate_result"],
263
+ )
264
+
265
+
266
+ def acquire_final_verification_target(
267
+ request: FinalVerificationTargetRequest,
268
+ ) -> FinalVerificationTargetAcquisition:
269
+ """Acquire a stable final-verification target behind one task-key lock."""
270
+ try:
271
+ with worktree_provision_mutex(
272
+ okstra_home(),
273
+ request.project_id,
274
+ slugify(request.task_group),
275
+ slugify(request.task_id),
276
+ ):
277
+ registry_coordinates = _final_verification_registry_coordinates(request)
278
+ done_rows = _read_final_verification_done_rows(
279
+ request,
280
+ registry_coordinates,
281
+ )
282
+ if request.stage is not None:
283
+ return _acquire_single_stage_target(
284
+ request,
285
+ done_rows,
286
+ registry_coordinates,
287
+ )
288
+ return _acquire_whole_task_target(
289
+ request,
290
+ done_rows,
291
+ registry_coordinates,
292
+ )
293
+ except PrepareError:
294
+ raise
295
+ except (OSError, RuntimeError, StageTargetError, ValueError) as exc:
296
+ raise PrepareError(str(exc)) from exc
@@ -0,0 +1,190 @@
1
+ """final-verification 이 소유하는 보고서 내용 판정.
2
+
3
+ 추가 표면·판정·검증 범위의 일관성과, 보고서가 준비된 검증 대상을 그대로
4
+ 비추는지를 본다. 이동 적합성은 공통 검증이 이어서 본다. 이 모듈은 공통 실행 조립부를
5
+ 가져오지 않는다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from collections.abc import Mapping
11
+ from pathlib import Path
12
+
13
+ from okstra_ctl.verification_target import TARGET_FIELD_RES, read_verification_target
14
+
15
+ _ADDED_SURFACE_NO_CALLER_RE = re.compile(r"^\s*none\b", re.IGNORECASE)
16
+
17
+
18
+ def validate_final_verification_content(data: dict, failures: list[str]) -> None:
19
+ """판정·차단 항목·검증 범위가 서로 맞는지 본다. 다른 작업 유형은 건너뛴다."""
20
+ if (data.get("header") or {}).get("taskType") != "final-verification":
21
+ return
22
+ _validate_added_surface_audit(data, failures)
23
+ _validate_verdict_consistency(data, failures)
24
+ _validate_verification_scope(data, failures)
25
+
26
+
27
+ def _validate_added_surface_audit(data: dict, failures: list[str]) -> None:
28
+ """Every surface the diff added is traced to a requirement, exempted, or paid for.
29
+
30
+ The coverage table proves each requirement reached the diff. Nothing proved
31
+ the reverse — that each thing the diff added answers a requirement — so work
32
+ nobody asked for passed every gate. This check reads the reverse table and
33
+ refuses a row that calls itself over-delivery without the blocker or
34
+ condition it became: a caller-less surface is an acceptance blocker, and a
35
+ surface with callers but no requirement is a conditional-acceptance
36
+ condition (ADR-0009 grades the two differently on purpose).
37
+ """
38
+ fv = data.get("finalVerification")
39
+ if not isinstance(fv, Mapping):
40
+ return
41
+ rows = fv.get("addedSurfaceAudit")
42
+ if not isinstance(rows, list):
43
+ return
44
+ blocker_ids = _row_ids(fv.get("acceptanceBlockers"))
45
+ condition_ids = _row_ids(
46
+ (data.get("finalVerdict") or {}).get("conditionalAcceptanceConditions")
47
+ )
48
+ for row in rows:
49
+ if isinstance(row, Mapping):
50
+ failures.extend(_added_surface_row_failures(row, blocker_ids, condition_ids))
51
+
52
+
53
+ def _row_ids(rows: object) -> set[str]:
54
+ if not isinstance(rows, list):
55
+ return set()
56
+ return {str(row.get("id")) for row in rows if isinstance(row, Mapping)}
57
+
58
+
59
+ def _added_surface_row_failures(
60
+ row: Mapping,
61
+ blocker_ids: set[str],
62
+ condition_ids: set[str],
63
+ ) -> list[str]:
64
+ row_id = str(row.get("id") or "<id 없음>")
65
+ disposition = str(row.get("disposition") or "")
66
+ note = str(row.get("note") or "")
67
+ if disposition == "traced" and not str(row.get("requirement") or "").strip():
68
+ return [
69
+ f"final-verification: addedSurfaceAudit {row_id} is `traced` but "
70
+ "names no requirement — a surface is traced to something the "
71
+ "brief asked for, or it is not traced."
72
+ ]
73
+ if disposition != "over-delivery":
74
+ return []
75
+ caller_less = bool(_ADDED_SURFACE_NO_CALLER_RE.match(str(row.get("callers") or "")))
76
+ expected, known = ("AB", blocker_ids) if caller_less else ("CA", condition_ids)
77
+ cited = set(re.findall(rf"\b{expected}-\d{{3,}}\b", note))
78
+ if not cited:
79
+ detail = (
80
+ "a caller-less surface is an acceptance blocker"
81
+ if caller_less
82
+ else "a surface with callers but no requirement is a "
83
+ "conditional-acceptance condition"
84
+ )
85
+ callers = "none" if caller_less else "recorded"
86
+ return [
87
+ f"final-verification: addedSurfaceAudit {row_id} is "
88
+ f"`over-delivery` with callers {callers}, so its note MUST cite "
89
+ f"the `{expected}-NNN` row it became — {detail}."
90
+ ]
91
+ missing = sorted(cited - known)
92
+ if missing:
93
+ return [
94
+ f"final-verification: addedSurfaceAudit {row_id} cites "
95
+ f"{missing}, which the report does not carry."
96
+ ]
97
+ return []
98
+
99
+
100
+ def _validate_verdict_consistency(data: dict, failures: list[str]) -> None:
101
+ verdict = data.get("finalVerdict") or {}
102
+ token = (verdict.get("verdictToken") or "").strip().lower()
103
+ blockers = (data.get("finalVerification") or {}).get("acceptanceBlockers") or []
104
+ conditions = verdict.get("conditionalAcceptanceConditions") or []
105
+ if token == "accepted" and blockers:
106
+ failures.append(
107
+ "final-verification: verdict `accepted` but acceptanceBlockers is "
108
+ "non-empty — an accepted verdict must have zero blockers."
109
+ )
110
+ if token == "blocked" and not blockers:
111
+ failures.append(
112
+ "final-verification: verdict `blocked` but acceptanceBlockers is "
113
+ "empty — a blocked verdict must list at least one blocker."
114
+ )
115
+ if token == "conditional-accept" and not conditions:
116
+ failures.append(
117
+ "final-verification: verdict `conditional-accept` but "
118
+ "conditionalAcceptanceConditions is empty — list every condition."
119
+ )
120
+
121
+
122
+ def _validate_verification_scope(data: dict, failures: list[str]) -> None:
123
+ scope = data.get("verificationScope", "whole-task")
124
+ if scope not in ("whole-task", "single-stage"):
125
+ failures.append(
126
+ f"final-verification: verificationScope must be `whole-task` or "
127
+ f"`single-stage`, got {scope!r}."
128
+ )
129
+
130
+
131
+ def validate_verification_target_match(
132
+ data: dict,
133
+ run_manifest: dict,
134
+ project_root: Path,
135
+ failures: list[str],
136
+ ) -> None:
137
+ """The verification report must mirror the target it was prepared against.
138
+
139
+ `verificationScope`, the worktree, and the base/head refs were entirely
140
+ self-declared: the schema required the fields to exist but nothing compared
141
+ them to the digest-verified snapshot written at prep time. That matters
142
+ because both `handoff.compute_eligibility` and the `release-handoff`
143
+ routing check read `verificationScope` — a single-stage run that writes
144
+ `whole-task` passes both, and an `accepted` verdict can be rendered against
145
+ a worktree or head nobody verified.
146
+ """
147
+ # 스냅샷의 다이제스트 규칙과 파싱은 `okstra_ctl.verification_target` 한 곳이
148
+ # 쥔다. 조립도 같은 파일을 읽어 `verificationScope` 를 기록한다.
149
+ # 최상위 `verificationTargetPath` 가 run 매니페스트의 실물 키다(render.py).
150
+ # `instructionSet` 블록은 active-run-context 의 것이라 여기서 읽으면 검사가
151
+ # 통째로 건너뛰어졌다(실측 2026-09-06, dev-10626 final-verification 001).
152
+ relative = str(run_manifest.get("verificationTargetPath") or "").strip()
153
+ if not relative:
154
+ return
155
+ target = read_verification_target(project_root, relative)
156
+ if target is None:
157
+ return
158
+
159
+ source = (data.get("finalVerification") or {}).get("sourceImplementationReport") or {}
160
+ declared = {
161
+ "scope": str(data.get("verificationScope") or "").strip(),
162
+ "worktree": str(source.get("worktreePath") or "").strip(),
163
+ "base": str(source.get("implementationBaseRef") or "").strip(),
164
+ "head": str(source.get("capturedHeadSha") or "").strip(),
165
+ }
166
+ for key, expected in ((k, target[k]) for k in TARGET_FIELD_RES):
167
+ actual = declared[key]
168
+ if expected and actual and actual != expected:
169
+ failures.append(
170
+ f"final-verification report declares {key} `{actual}` but the "
171
+ f"prepared verification target says `{expected}` "
172
+ f"(`{relative}`). The report must mirror the target it was "
173
+ "prepared against — `verificationScope` in particular gates "
174
+ "both stage-group eligibility and release-handoff routing, so "
175
+ "a self-declared value lets a run be judged as something it "
176
+ "was not."
177
+ )
178
+
179
+ declared_stages = {
180
+ row.get("stage")
181
+ for row in ((data.get("finalVerification") or {}).get("stageReports") or [])
182
+ if isinstance(row, dict) and isinstance(row.get("stage"), int)
183
+ }
184
+ if target["stages"] and declared_stages and declared_stages != target["stages"]:
185
+ failures.append(
186
+ f"final-verification report covers stages {sorted(declared_stages)} "
187
+ f"but the prepared target names {sorted(target['stages'])} "
188
+ f"(`{relative}`). A verdict must not be rendered for a stage set "
189
+ "nobody prepared evidence for."
190
+ )
@@ -0,0 +1,38 @@
1
+ """final-verification 이 위저드의 stage 선택에 거는 조건."""
2
+ from __future__ import annotations
3
+
4
+
5
+ def whole_task_verification_allowed(
6
+ *,
7
+ stage_numbers: tuple[int, ...],
8
+ done_stages: set[int],
9
+ ) -> bool:
10
+ """승인된 계획의 stage 가 전부 done 일 때만 전체 작업 검증을 고를 수 있다."""
11
+ if not stage_numbers:
12
+ return False
13
+ return all(number in done_stages for number in stage_numbers)
14
+
15
+
16
+ class StageAnswerError(ValueError):
17
+ """stage 선택 답이 final-verification 이 받을 수 있는 값이 아니다."""
18
+
19
+
20
+ def validate_stage_answer(
21
+ answer: str, *, whole_task_token: str, whole_task_allowed: bool
22
+ ) -> str:
23
+ """전체 작업 토큰 또는 stage 번호 하나만 받는다. 여러 개나 auto 는 받지 않는다."""
24
+ if not answer:
25
+ raise StageAnswerError("value required")
26
+ if answer == whole_task_token:
27
+ if not whole_task_allowed:
28
+ raise StageAnswerError(
29
+ "whole-task verification requires final-verification "
30
+ "with all stages done")
31
+ return answer
32
+ try:
33
+ int(answer)
34
+ except ValueError:
35
+ raise StageAnswerError(
36
+ f"answer must be whole-task or a stage number, got {answer!r}"
37
+ ) from None
38
+ return answer
@@ -0,0 +1 @@
1
+ """implementation 단계의 실행 정책과 자산."""
@@ -0,0 +1,17 @@
1
+ {
2
+ "allowed": " - approved-plan reference and quoted user-approval evidence\n - commit list with SHA, message, and the plan step each commit satisfies\n - `git diff --stat <base>..HEAD` summary plus per-file one-line change summary\n - `Out-of-plan edits` block listing every file touched outside the approved plan with rationale (empty block preferred)\n - validation evidence: actual stdout/stderr and exit code for every pre / mid / post command from the plan (no paraphrased \"tests pass\")\n - TDD evidence for TDD-applicable steps: failing-test output before implementation commit and passing-test output after, with framing SHAs\n - per-verifier sections for every verifier in the resolved roster, with an independent verdict (PASS / CONCERNS / FAIL) and cited diff snippets; dissent is preserved by the Okstra lead\n - rollback verification (advisory, never blocks — record the revert path for a human; `result` is ok / not-applicable / advisory — human-run)\n - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `final-verification`, `error-analysis`, `implementation-planning`, `implementation`, and `rationale` is why that one and nothing else. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`",
3
+ "forbidden": [
4
+ "any Edit/Write or state-mutating Bash before the pre-implementation gate passes (gate requires --approved-plan pointing to a final-report.md whose frontmatter has `approved: true`)",
5
+ "`git push` of any kind (including `--dry-run` against a real remote that produces side-effects), `npm publish` / `cargo publish` / `pip publish`, `gh release`, `docker push`",
6
+ "real database migrations, schema changes against shared environments, or writes to non-local datastores",
7
+ "production credentials, deploy commands, infra mutation (`terraform apply`, `kubectl apply` against non-local cluster, etc.)",
8
+ "external API write calls (POST/PUT/PATCH/DELETE) to third-party services other than localhost test fixtures",
9
+ "source edits or Bash mutations performed by any verifier role (`Antigravity verifier`, `Codex verifier`, `Claude verifier` are read-only — recommend, do not apply)",
10
+ "dispatching parallel sub-agents beyond the required worker roster",
11
+ "silent scope expansion: every file edited outside the approved plan list MUST appear in the `Out-of-plan edits` block with rationale",
12
+ "leaving placeholders such as TBD / TODO / \"implement later\" / \"handle edge cases\" in newly-added lines of this run (check via `git diff <base>..HEAD | grep -E '^\\+[^+].*\\b(TBD|TODO|FIXME|XXX|implement later|handle edge cases|similar to|placeholder)\\b'`; pre-existing strings in untouched regions are out of scope)",
13
+ "lead substituting its own verdict when every verifier present in the resolved roster returned a non-result terminal status (`timeout`/`error`/`not-run`); in that case the run MUST end as `blocked` with routing recommendation back to `error-analysis`, never with a lead-only verdict",
14
+ "declaring overall task acceptance — that is `final-verification` ownership; this phase reports only \"ready for final-verification\" or \"needs new planning loop\"",
15
+ "delegating the self-review pass — the Okstra lead must run it"
16
+ ]
17
+ }
@@ -7,17 +7,18 @@ isolated stage worktree, and record the started event.
7
7
  from __future__ import annotations
8
8
 
9
9
  import datetime as _dt
10
+ import re
10
11
  import subprocess
11
12
  import sys
12
13
  from dataclasses import dataclass, field
13
14
  from pathlib import Path
14
15
  from typing import Any
15
16
 
16
- from . import stage_targets
17
- from .design_prep import DesignPrepDecision, DesignPrepError, resolve_design_prep
18
- from .final_report_paths import final_report_data_path
19
- from .stage_reconcile import auto_reconcile_best_effort
20
- from .worker_prompt_policy import IMPLEMENTATION_STAGE_HEADER
17
+ from okstra_ctl import stage_targets
18
+ from okstra_ctl.design_prep import DesignPrepDecision, DesignPrepError, resolve_design_prep
19
+ from okstra_ctl.final_report_paths import final_report_data_path
20
+ from okstra_ctl.stage_reconcile import auto_reconcile_best_effort
21
+ from okstra_ctl.worker_prompt_policy import IMPLEMENTATION_STAGE_HEADER
21
22
 
22
23
 
23
24
  class ImplementationStageError(Exception):
@@ -87,9 +88,9 @@ def claim_implementation_stage_run(
87
88
  callers can compute stage-specific run paths after this function resolves
88
89
  the selected stage.
89
90
  """
90
- from .consumers import backfill_done_from_carry
91
- from . import worktree as _worktree
92
- from . import worktree_registry as _reg
91
+ from okstra_ctl.consumers import backfill_done_from_carry
92
+ from okstra_ctl import worktree as _worktree
93
+ from okstra_ctl import worktree_registry as _reg
93
94
 
94
95
  plan_run_root = Path(inp.approved_plan_path).resolve().parents[1]
95
96
  backfill_done_from_carry(plan_run_root)
@@ -201,7 +202,7 @@ def claim_implementation_stage_run(
201
202
  base_commit=stage_base,
202
203
  )
203
204
  except RuntimeError as exc:
204
- from .git_reconcile import guidance
205
+ from okstra_ctl.git_reconcile import guidance
205
206
 
206
207
  hint = guidance(
207
208
  plan_run_root=plan_run_root,
@@ -234,7 +235,7 @@ def _record_stage_run_claim_started(
234
235
  plan_run_root: Path,
235
236
  claim: StageRunClaim,
236
237
  ) -> None:
237
- from .consumers import append_consumer
238
+ from okstra_ctl.consumers import append_consumer
238
239
 
239
240
  now = _dt.datetime.now(_dt.timezone.utc).isoformat()
240
241
  append_consumer(
@@ -275,3 +276,14 @@ def publish_stage_run_claim(
275
276
  ctx["EXECUTOR_WORKTREE_BASE_REF"] = claim.worktree_base_ref
276
277
  ctx["EXECUTOR_WORKTREE_STATUS"] = claim.worktree_status
277
278
  ctx["EXECUTOR_WORKTREE_NOTE"] = claim.worktree_note
279
+
280
+
281
+ def resolve_instruction_paths(text: str, asset_root: Path) -> str:
282
+ """리드가 직접 여는 보조 지침은 설치 형태에 맞는 실제 경로로 기록한다."""
283
+ from okstra_ctl.phases.catalog import profile_sidecar
284
+
285
+ return re.sub(
286
+ r"(?:prompts/profiles|scripts/okstra_ctl/phases/implementation/instructions)/_implementation-[\w-]+\.md",
287
+ lambda match: str(profile_sidecar(asset_root, match.group(0))),
288
+ text,
289
+ )
@@ -54,7 +54,7 @@ If the anchor (`implementation_base_commit`) is reported unresolvable, run the s
54
54
  Because of the dependency closure, the chain queue **may include a stage that another implementation run has occupied as started/reserved.** That stage's `render-bundle` is rejected with `--stage N already in progress or reserved by another run` (StageTargetError). This is **not** an exception gate needing human judgment but a "next stage not yet ready" situation. On this rejection, **terminate the chain normally** and report the remaining queue to the user (e.g. `remaining queue: stage 4, 5 — resume with okstra-run after occupancy is released`). This is a different branch from the exception gate below (data corruption·concurrent-occupancy conflict confirmation).
55
55
 
56
56
  ### Stage ended FAIL — stop the queue and report (not an exception gate)
57
- When a stage's synthesised verdict is `FAIL`, Phase 6 writes no carry sidecar and appends a `status:"failed"` row in place of `done` (`prompts/profiles/_implementation-deliverable.md` "Lead post-stage persistence"). **Stop the queue at that stage** and report the failed stage, its report path, and the remaining queue (e.g. `stage 1 FAIL — remaining queue: stage 2, 3, 5; re-enter with okstra-run --stage 1 after the fix`). Do **not** continue to the next stage even when that stage is dependency-independent: an unattended chain that keeps building past a confirmed regression stacks later work on top of it. The `failed` row releases the stage's occupancy, so `--stage <N>` re-enters the same stage on its preserved worktree and branch — there is nothing to unblock by hand.
57
+ When a stage's synthesised verdict is `FAIL`, Phase 6 writes no carry sidecar and appends a `status:"failed"` row in place of `done` (`scripts/okstra_ctl/phases/implementation/instructions/_implementation-deliverable.md` "Lead post-stage persistence"). **Stop the queue at that stage** and report the failed stage, its report path, and the remaining queue (e.g. `stage 1 FAIL — remaining queue: stage 2, 3, 5; re-enter with okstra-run --stage 1 after the fix`). Do **not** continue to the next stage even when that stage is dependency-independent: an unattended chain that keeps building past a confirmed regression stacks later work on top of it. The `failed` row releases the stage's occupancy, so `--stage <N>` re-enters the same stage on its preserved worktree and branch — there is nothing to unblock by hand.
58
58
 
59
59
  ### Exception gate during chaining
60
60
  If `render-bundle` raises Step 5's concurrent-run conflict detection (concurrent-run branch) or git stale-SHA reconciliation (git-reconcile branch), **stop the chain at that stage** and present the gate to the user exactly as Step 5 prescribes. Once the user resolves the gate, resume the chain in place (continue with the remaining queue). Data corruption·concurrent-occupancy conflicts are confirmed by a human — this is the safety boundary of unattended chaining. (Unlike the "not ready" rejection above, these two branches do not discard the queue; they wait for user resolution.)
@@ -37,7 +37,7 @@ are collected and convergence finished. Phase 1-5 do not need it.
37
37
  evidence and add one `recommendedNextSteps` item with environment
38
38
  prerequisites and the expected `QA-RESULT`. This is user-owned verification;
39
39
  do not route back or fail implementation solely for this advisory.
40
- - **Routing recommendation**: `implementation.routingRecommendation` is an **object** with exactly two fields — `target`, one of `final-verification`, `error-analysis`, `implementation-planning`, `implementation`, and `rationale`, one or two sentences on why that target and nothing else. It is not a prose note: Phase 7 projects `workflow.nextRecommendedPhase` from `target` alone, so a phase named only in the prose does not route the task. Pick `final-verification` when this stage's plan items landed and validation passed; `error-analysis` when a failure's cause is not understood; `implementation-planning` when the approved plan itself no longer fits the evidence; `implementation` when work remains inside this stage and the next run is a fix run. **Enforced:** `schemas/final-report-v2.0.schema.json` rejects a `target` outside the enum, a missing `rationale`, and a string in place of the object.
40
+ - **Routing recommendation**: `implementation.routingRecommendation` is an **object** with exactly two fields — `target`, one of `final-verification`, `error-analysis`, `implementation-planning`, `implementation`, and `rationale`, one or two sentences on why that target and nothing else. It is not a prose note: Phase 7 projects `workflow.nextRecommendedPhase` from `target` alone, so a phase named only in the prose does not route the task. Record the stage completion, validation outcome, failure-cause certainty, and plan-fit evidence; the lead chooses the destination using `prompts/lead/phase-routing.md` §implementation. **Enforced:** `schemas/final-report-v2.0.schema.json` rejects a `target` outside the enum, a missing `rationale`, and a string in place of the object.
41
41
  - **Follow-up tasks (Section 4 of the final report)**: every item discovered during this run that was *not* delivered MUST appear in the final report's `## 4. Follow-up Tasks` table with a concrete `Origin`, `New Task ID`, `Suggested task-type`, `Scope`, and `Reason / Why deferred`. Sources include: out-of-scope discoveries that the executor consciously chose not to fold into this run, verifier concerns the executor declined to fix in-place, scope-boundary items from the approved plan that turned out to need their own ticket, and any unresolved `## 1. Clarification Items` row carried over from the approved plan (`Status` ∈ `{open, answered}` at approval time). An empty section is acceptable but only when expressed as the single line `- No follow-up tasks.` — silence is treated as a contract violation. Rows with `Auto-spawn? = yes` will be materialised by `scripts/okstra-spawn-followups.py` in Phase 7; rows with `Auto-spawn? = no` MUST also appear in `Section 3. Recommended Next Steps` so the user knows to act manually.
42
42
 
43
43
  ## Self-review pass before finalising the report (the Okstra lead runs this; do not delegate it)