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,241 @@
1
+ """오류 분석의 재현·원인 연결·선택된 이동 결과를 검증한다."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Mapping
6
+ from okstra_ctl.workflow import ERROR_ANALYSIS_ROUTING_DIRECTIONS
7
+
8
+
9
+ def _upstream_by_candidate(candidates: list[Any]) -> dict[str, list[str]]:
10
+ upstream: dict[str, list[str]] = {}
11
+ for candidate in candidates:
12
+ if not isinstance(candidate, Mapping):
13
+ continue
14
+ candidate_id = candidate.get("id")
15
+ declared = candidate.get("downstreamOf")
16
+ if isinstance(candidate_id, str) and isinstance(declared, list):
17
+ upstream[candidate_id] = [row for row in declared if isinstance(row, str)]
18
+ return upstream
19
+
20
+
21
+ def _chain_cycle(upstream: dict[str, list[str]]) -> list[str]:
22
+ """The first cycle reachable through `downstreamOf`, as the ids that form it.
23
+
24
+ A cycle is a diagnosis that says each step is caused by the next, so it
25
+ names no first cause. It also hangs the figure's layering, which relaxes
26
+ until depths settle.
27
+ """
28
+ settled: set[str] = set()
29
+ for start in sorted(upstream):
30
+ stack = [(start, False)]
31
+ on_path: list[str] = []
32
+ active: dict[str, int] = {}
33
+ while stack:
34
+ current, exiting = stack.pop()
35
+ if exiting:
36
+ active.pop(current)
37
+ on_path.pop()
38
+ settled.add(current)
39
+ continue
40
+ if current in active:
41
+ return on_path[active[current] :] + [current]
42
+ if current in settled or current not in upstream:
43
+ continue
44
+ active[current] = len(on_path)
45
+ on_path.append(current)
46
+ stack.append((current, True))
47
+ stack.extend((parent, False) for parent in upstream[current])
48
+ return []
49
+
50
+
51
+ def _validate_cause_chain(
52
+ candidates: list[Any], candidate_ids: set[str], failures: list[str]
53
+ ) -> None:
54
+ """`downstreamOf` must name a sibling candidate, and never itself."""
55
+ upstream = _upstream_by_candidate(candidates)
56
+ for candidate_id in sorted(upstream):
57
+ unknown = sorted(set(upstream[candidate_id]) - candidate_ids)
58
+ if unknown:
59
+ failures.append(
60
+ f"final-report data.json: {candidate_id}.downstreamOf names "
61
+ "unknown cause candidate(s): " + ", ".join(unknown) + "."
62
+ )
63
+ if candidate_id in upstream[candidate_id]:
64
+ failures.append(
65
+ f"final-report data.json: {candidate_id}.downstreamOf names itself."
66
+ )
67
+ cycle = _chain_cycle(
68
+ {
69
+ key: [row for row in value if row in candidate_ids]
70
+ for key, value in upstream.items()
71
+ }
72
+ )
73
+ if cycle:
74
+ failures.append(
75
+ "final-report data.json: cause candidates form a downstreamOf cycle: "
76
+ + " -> ".join(cycle)
77
+ + "."
78
+ )
79
+
80
+
81
+ def _validate_reproduction(
82
+ error_analysis: Mapping[str, Any], failures: list[str]
83
+ ) -> None:
84
+ reproduction_value = error_analysis.get("reproduction")
85
+ reproduction = reproduction_value if isinstance(reproduction_value, Mapping) else {}
86
+ reproduction_status = reproduction.get("status")
87
+ blocked_reason = reproduction.get("blockedReason")
88
+ if reproduction_status == "blocked-before-repro":
89
+ if not isinstance(blocked_reason, str) or not blocked_reason.strip():
90
+ failures.append(
91
+ "final-report data.json: blocked-before-repro requires a non-empty "
92
+ "errorAnalysis.reproduction.blockedReason."
93
+ )
94
+ elif blocked_reason != "":
95
+ failures.append(
96
+ "final-report data.json: errorAnalysis.reproduction.blockedReason "
97
+ "must be exactly empty unless status is blocked-before-repro."
98
+ )
99
+
100
+
101
+ def _validate_cause_candidates(candidates: list[Any], failures: list[str]) -> set[str]:
102
+ candidate_ids: list[str] = []
103
+ for candidate in candidates:
104
+ if not isinstance(candidate, Mapping):
105
+ continue
106
+ candidate_id = candidate.get("id")
107
+ if isinstance(candidate_id, str):
108
+ candidate_ids.append(candidate_id)
109
+ duplicate_ids = sorted(
110
+ candidate_id
111
+ for candidate_id in set(candidate_ids)
112
+ if candidate_ids.count(candidate_id) > 1
113
+ )
114
+ if duplicate_ids:
115
+ failures.append(
116
+ "final-report data.json: duplicate cause candidate id(s): "
117
+ + ", ".join(duplicate_ids)
118
+ + "."
119
+ )
120
+
121
+ _validate_cause_chain(candidates, set(candidate_ids), failures)
122
+
123
+ return set(candidate_ids)
124
+
125
+
126
+ def _validate_routing_cause(
127
+ error_analysis: Mapping[str, Any],
128
+ candidates: list[Any],
129
+ candidate_id_set: set[str],
130
+ failures: list[str],
131
+ ) -> Any:
132
+ routing_value = error_analysis.get("routing")
133
+ routing = routing_value if isinstance(routing_value, Mapping) else {}
134
+ target = routing.get("nextTaskType")
135
+ leading_cause_id = routing.get("leadingCauseId")
136
+ if isinstance(target, str) and target not in ERROR_ANALYSIS_ROUTING_DIRECTIONS:
137
+ failures.append(
138
+ "final-report data.json: errorAnalysis.routing has unsupported "
139
+ f"routing target `{target}`."
140
+ )
141
+ if target == "implementation-option-selection":
142
+ if not candidates:
143
+ failures.append(
144
+ "final-report data.json: implementation-option-selection routing requires "
145
+ "at least one cause candidate."
146
+ )
147
+ if (
148
+ not isinstance(leading_cause_id, str)
149
+ or leading_cause_id not in candidate_id_set
150
+ ):
151
+ failures.append(
152
+ "final-report data.json: implementation-option-selection routing "
153
+ "leadingCauseId must reference a cause candidate."
154
+ )
155
+ elif target == "error-analysis" and (
156
+ not isinstance(leading_cause_id, str)
157
+ or (leading_cause_id != "" and leading_cause_id not in candidate_id_set)
158
+ ):
159
+ failures.append(
160
+ "final-report data.json: error-analysis routing leadingCauseId must be "
161
+ "empty or reference a cause candidate."
162
+ )
163
+
164
+ return target
165
+
166
+
167
+ def _validate_verdict_directions(
168
+ data: Mapping[str, Any], target: Any, failures: list[str]
169
+ ) -> None:
170
+ expected_direction = ERROR_ANALYSIS_ROUTING_DIRECTIONS.get(target)
171
+ verdict_card_value = data.get("verdictCard")
172
+ verdict_card = verdict_card_value if isinstance(verdict_card_value, Mapping) else {}
173
+ final_verdict_value = data.get("finalVerdict")
174
+ final_verdict = (
175
+ final_verdict_value if isinstance(final_verdict_value, Mapping) else {}
176
+ )
177
+ if expected_direction:
178
+ for field_name, verdict in (
179
+ ("verdictCard", verdict_card),
180
+ ("finalVerdict", final_verdict),
181
+ ):
182
+ if verdict.get("direction") != expected_direction:
183
+ failures.append(
184
+ f"final-report data.json: {field_name}.direction must be "
185
+ f"`{expected_direction}` for `{target}` routing."
186
+ )
187
+
188
+
189
+ def _validate_phase_continuation(
190
+ data: Mapping[str, Any], target: Any, failures: list[str]
191
+ ) -> None:
192
+ follow_up_tasks_value = data.get("followUpTasks")
193
+ follow_up_tasks = (
194
+ follow_up_tasks_value if isinstance(follow_up_tasks_value, list) else []
195
+ )
196
+ continuations = [
197
+ row
198
+ for row in follow_up_tasks
199
+ if isinstance(row, Mapping) and row.get("origin") == "phase-continuation"
200
+ ]
201
+ if len(continuations) != 1:
202
+ failures.append(
203
+ "final-report data.json: followUpTasks must contain exactly one "
204
+ "phase-continuation row."
205
+ )
206
+ else:
207
+ continuation = continuations[0]
208
+ if continuation.get("suggestedTaskType") != target:
209
+ failures.append(
210
+ "final-report data.json: phase-continuation suggestedTaskType "
211
+ "must match the routing target."
212
+ )
213
+ # `newTaskId` 의 형식과 `priority` 값은 여기서 보지 않는다. 이 행은
214
+ # `okstra-spawn-followups.py` 의 `NON_SPAWNING_ORIGINS` 에 들어 있어
215
+ # 아무것도 스폰하지 않는 표식이고, 그래서 두 필드에는 소비자가 없다.
216
+ # 게다가 그 스크립트의 priority 기본값은 `P1` 인데 여기서는 `P0` 를
217
+ # 요구해 두 값이 정면으로 어긋났고, 두 규칙 중 어느 쪽도 report-writer
218
+ # 가 읽는 스키마·프로필·템플릿 어디에도 적혀 있지 않았다 — 작성자가
219
+ # 알 수 없는 규칙을 소비자 없는 필드에 걸어 두고 있었다.
220
+ if continuation.get("autoSpawn") != "no":
221
+ failures.append(
222
+ "final-report data.json: phase-continuation autoSpawn must be no."
223
+ )
224
+
225
+
226
+ def _validate_error_analysis_consistency(
227
+ data: Mapping[str, Any], failures: list[str]
228
+ ) -> None:
229
+ error_analysis_value = data.get("errorAnalysis")
230
+ error_analysis = (
231
+ error_analysis_value if isinstance(error_analysis_value, Mapping) else {}
232
+ )
233
+ _validate_reproduction(error_analysis, failures)
234
+ candidates_value = error_analysis.get("causeCandidates")
235
+ candidates = candidates_value if isinstance(candidates_value, list) else []
236
+ candidate_ids = _validate_cause_candidates(candidates, failures)
237
+ target = _validate_routing_cause(
238
+ error_analysis, candidates, candidate_ids, failures
239
+ )
240
+ _validate_verdict_directions(data, target, failures)
241
+ _validate_phase_continuation(data, target, failures)
@@ -0,0 +1 @@
1
+ """기능 분석 단계의 대상 해석과 보고서 정책."""
@@ -0,0 +1,8 @@
1
+ {
2
+ "allowed": " - confirmed target and normal, alternate, and failure flows\n - rules, state transitions, external calls, and test coverage scope\n",
3
+ "forbidden": [
4
+ "source or configuration edits",
5
+ "tests, builds, migrations, or deployments",
6
+ "starting any other lifecycle phase inside this run"
7
+ ]
8
+ }
@@ -0,0 +1,63 @@
1
+ """선택한 프로젝트 증거에서 기능 분석 대상을 해석한다."""
2
+
3
+ from __future__ import annotations
4
+ import re
5
+ from pathlib import Path
6
+ from typing import Mapping, Sequence
7
+ from okstra_ctl.prepare_error import PrepareError
8
+ from okstra_ctl.analysis_inputs import (
9
+ AnalysisInputError,
10
+ AnalysisReportCandidate,
11
+ ResolvedEvidenceInput,
12
+ )
13
+
14
+ _FEATURE_ID_RE = re.compile(r"PF-\d{3}")
15
+
16
+
17
+ def resolve_analysis_target(
18
+ raw_target: str,
19
+ evidence_inputs: Sequence[ResolvedEvidenceInput],
20
+ candidates: Mapping[Path, AnalysisReportCandidate],
21
+ ) -> dict[str, object]:
22
+ """Normalize a feature-index identifier or a non-empty free-text target."""
23
+ target = raw_target.strip()
24
+ if not target:
25
+ raise AnalysisInputError("analysis target must not be empty")
26
+ if not _FEATURE_ID_RE.fullmatch(target):
27
+ return {"inputMode": "free-text", "requestedValue": target}
28
+ matches: list[dict[object, object]] = []
29
+ for evidence in evidence_inputs:
30
+ if evidence.relation != "project-context":
31
+ continue
32
+ candidate = candidates.get(evidence.report_path)
33
+ if candidate is None:
34
+ candidate = next(
35
+ (
36
+ value
37
+ for path, value in candidates.items()
38
+ if path.resolve() == evidence.report_path
39
+ ),
40
+ None,
41
+ )
42
+ if candidate is None:
43
+ continue
44
+ matches.extend(
45
+ feature
46
+ for feature in candidate.feature_index
47
+ if isinstance(feature, dict) and feature.get("id") == target
48
+ )
49
+ if len(matches) != 1:
50
+ raise AnalysisInputError(
51
+ f"exactly one project-context feature must match {target}"
52
+ )
53
+ return {
54
+ "inputMode": "feature-index",
55
+ "requestedValue": target,
56
+ "feature": dict(matches[0]),
57
+ }
58
+
59
+
60
+ def validate_feature_target_input(raw_target: str) -> None:
61
+ """실행 생성 전에 필수 대상 입력을 확인한다."""
62
+ if not raw_target.strip():
63
+ raise PrepareError("feature-analysis requires --analysis-target")
@@ -1,9 +1,10 @@
1
1
  """Human-first feature-analysis view model."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
- from ..common import analysis_review_ids, evidence_index
5
- from ..models import HumanReportView, VisualNode
6
- from ..visualizations import flow_figure
5
+ from okstra_ctl.report_html.common import analysis_review_ids, evidence_index
6
+ from okstra_ctl.report_html.models import HumanReportView, VisualNode
7
+ from okstra_ctl.report_html.visualizations import flow_figure
7
8
 
8
9
  # Record fields this template anchors as `id-<row id>` (see
9
10
  # `HumanReportView.anchored_fields`); ids elsewhere land in the ledger.
@@ -15,7 +16,11 @@ ANCHORED_FIELDS = (
15
16
  )
16
17
 
17
18
 
18
- _FLOW_KINDS = {"normal": "Normal path", "alternative": "Alternative path", "failure": "Failure path"}
19
+ _FLOW_KINDS = {
20
+ "normal": "Normal path",
21
+ "alternative": "Alternative path",
22
+ "failure": "Failure path",
23
+ }
19
24
 
20
25
 
21
26
  def _flow_nodes(feature: dict) -> tuple[VisualNode, ...]:
@@ -33,7 +38,9 @@ def _flow_nodes(feature: dict) -> tuple[VisualNode, ...]:
33
38
 
34
39
  def build_feature_analysis_view(data: dict) -> HumanReportView:
35
40
  feature = data["featureAnalysis"]
36
- figure = flow_figure(nodes=_flow_nodes(feature), edges=(), title="Feature behavior paths")
41
+ figure = flow_figure(
42
+ nodes=_flow_nodes(feature), edges=(), title="Feature behavior paths"
43
+ )
37
44
  context = {
38
45
  "humanSummary": data["humanSummary"],
39
46
  "feature": feature,
@@ -0,0 +1,22 @@
1
+ # Feature analysis phase
2
+
3
+ ## Purpose and boundaries
4
+
5
+ This phase describes a confirmed feature's normal, alternate and failure flows, domain rules, state changes, external interactions and test coverage. The profile guides read-only analysis without implementation alternatives or execution plans. Evidence report loading, user review authority, source snapshots and report publication remain common contracts.
6
+
7
+ ## Inputs and questions
8
+
9
+ `entry.resolve_analysis_target` trims free-text targets and resolves `PF-` identifiers against exactly one feature in the selected project-context evidence. `entry.validate_feature_target_input` rejects a missing target before provisioning. The wizard preserves project-evidence selection before feature-index selection or direct target text, including reuse and rewind behavior. `wizard.py` owns target questions; shared evidence questions stay in `wizard/steps_analysis.py`.
10
+
11
+ ## Reports and validation
12
+
13
+ `profile.md` and `profile.json` own instructions and worker roles. `report.py` and `report_assets/` own feature report content. `validation._validate_feature_semantics` checks that report target fields match the resolved target. The common analysis validator continues to enforce current-code evidence, included paths, worker completion and scope confirmation. Shared schemas retain the same versions and artifact names. The profile does not choose a next lifecycle phase; orchestration remains common.
14
+
15
+ ## Guarantees
16
+
17
+ | ID | Guarantee | Enforcement |
18
+ |---|---|---|
19
+ | FA-1 | A feature-index target resolves to exactly one selected project-context feature; free text cannot be empty. | `scripts/okstra_ctl/phases/feature_analysis/entry.py::resolve_analysis_target` |
20
+ | FA-2 | Report target identity matches the resolved analysis target. | `scripts/okstra_ctl/phases/feature_analysis/validation.py::_validate_feature_semantics` |
21
+ | FA-3 | Current-code evidence remains within the declared analysis scope. | `validators/validate_analysis_report.py::_validate_all_current_code_evidence` |
22
+ | FA-4 | Feature target selection preserves the wizard's rendered arguments. | `tests/domain/wizard/test_analysis_inputs.py::test_feature_target_from_index_reaches_render_args` |
@@ -0,0 +1,27 @@
1
+ """기능 분석 결과의 대상 일치 정책."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ EVIDENCE_COLLECTIONS = ("flows", "domainRules", "stateChanges", "externalInteractions")
7
+
8
+
9
+ def _validate_feature_semantics(data: dict, errors: list[str]) -> None:
10
+ analysis = data.get("featureAnalysis") or {}
11
+ common_target = ((data.get("analysisCommon") or {}).get("scope") or {}).get(
12
+ "resolvedTarget"
13
+ ) or {}
14
+ feature_target = analysis.get("target") or {}
15
+ for field in ("inputMode", "requestedValue"):
16
+ if feature_target.get(field) != common_target.get(field):
17
+ errors.append(
18
+ f"featureAnalysis.target.{field} must match "
19
+ f"analysisCommon.scope.resolvedTarget.{field}"
20
+ )
21
+ resolved_feature = common_target.get("feature") or {}
22
+ if common_target.get("inputMode") == "feature-index" and (
23
+ feature_target.get("featureId") != resolved_feature.get("id")
24
+ ):
25
+ errors.append(
26
+ "featureAnalysis.target.featureId must match the resolved feature id"
27
+ )
@@ -0,0 +1,95 @@
1
+ """기능 분석 대상 선택·직접 입력 질문."""
2
+
3
+ from __future__ import annotations
4
+ from pathlib import Path
5
+ from typing import Optional
6
+ from okstra_ctl.analysis_inputs import (
7
+ AnalysisInputError,
8
+ AnalysisReportCandidate,
9
+ load_analysis_report_candidate,
10
+ )
11
+ from okstra_ctl.wizard.ids import (
12
+ PICK_TYPE_CUSTOM,
13
+ S_ANALYSIS_TARGET,
14
+ S_ANALYSIS_TARGET_PICK,
15
+ )
16
+ from okstra_ctl.wizard.state import Prompt, WizardError, WizardState
17
+ from okstra_ctl.wizard.prompts import _opt, _p
18
+ from okstra_ctl.wizard.steps_analysis import (
19
+ _analysis_evidence_paths,
20
+ _resolve_analysis_evidence,
21
+ )
22
+ from .entry import resolve_analysis_target
23
+
24
+
25
+ def _feature_description(feature: dict[object, object]) -> str:
26
+ name = str(feature.get("name") or "")
27
+ summary = str(feature.get("summary") or "")
28
+ entry_point = str(feature.get("entryPoint") or feature.get("entrypoint") or "")
29
+ return " · ".join(value for value in (name, summary, entry_point) if value)
30
+
31
+
32
+ def _build_analysis_target_pick(state: WizardState) -> Prompt:
33
+ t = _p(state.workspace_root, S_ANALYSIS_TARGET_PICK)
34
+ try:
35
+ candidate = load_analysis_report_candidate(
36
+ Path(state.project_root), Path(state.project_evidence_path)
37
+ )
38
+ except AnalysisInputError as exc:
39
+ raise WizardError(str(exc)) from exc
40
+ options = [
41
+ _opt(str(feature["id"]), str(feature["id"]), _feature_description(feature))
42
+ for feature in candidate.feature_index
43
+ if isinstance(feature, dict) and isinstance(feature.get("id"), str)
44
+ ][:3]
45
+ options.append(_opt(PICK_TYPE_CUSTOM, t["options"][PICK_TYPE_CUSTOM]))
46
+ return Prompt(
47
+ step=S_ANALYSIS_TARGET_PICK,
48
+ kind="pick",
49
+ label=t["label"],
50
+ options=options,
51
+ echo_template=t["echo_template"],
52
+ )
53
+
54
+
55
+ def _accept_analysis_target(state: WizardState, value: str) -> str:
56
+ candidates: dict[Path, AnalysisReportCandidate] = {}
57
+ for path in _analysis_evidence_paths(state):
58
+ try:
59
+ candidate = load_analysis_report_candidate(Path(state.project_root), path)
60
+ except AnalysisInputError as exc:
61
+ raise WizardError(str(exc)) from exc
62
+ candidates[candidate.report_path] = candidate
63
+ try:
64
+ target = resolve_analysis_target(
65
+ value, _resolve_analysis_evidence(state), candidates
66
+ )
67
+ except AnalysisInputError as exc:
68
+ raise WizardError(str(exc)) from exc
69
+ requested_value = target["requestedValue"]
70
+ if not isinstance(requested_value, str):
71
+ raise WizardError("analysis target resolver returned an invalid requestedValue")
72
+ state.analysis_target = requested_value
73
+ state.analysis_target_pending_text = False
74
+ return requested_value
75
+
76
+
77
+ def _submit_analysis_target_pick(state: WizardState, value: str) -> Optional[str]:
78
+ if value == PICK_TYPE_CUSTOM:
79
+ state.analysis_target_pending_text = True
80
+ return None
81
+ return f"analysis-target: {_accept_analysis_target(state, value)}"
82
+
83
+
84
+ def _build_analysis_target(state: WizardState) -> Prompt:
85
+ t = _p(state.workspace_root, S_ANALYSIS_TARGET)
86
+ return Prompt(
87
+ step=S_ANALYSIS_TARGET,
88
+ kind="text",
89
+ label=t["label"],
90
+ echo_template=t["echo_template"],
91
+ )
92
+
93
+
94
+ def _submit_analysis_target(state: WizardState, value: str) -> Optional[str]:
95
+ return f"analysis-target: {_accept_analysis_target(state, value)}"
@@ -0,0 +1,4 @@
1
+ """final-verification 단계 패키지.
2
+
3
+ 공통 실행 조립부와 다른 단계 패키지를 여기서 가져오지 않는다.
4
+ """
@@ -0,0 +1,8 @@
1
+ {
2
+ "allowed": " - acceptance verdict with requirement coverage assessment\n - residual risk and regression notes\n - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, `done`, and `rationale` ties that choice to the verdict and the blocker list. Both `release-handoff` forms require a release-ready verdict — `accepted`, or `conditional-accept` with every condition declaring `blocksReleaseHandoff: false` (`okstra_ctl.release_gate.release_handoff_allowed`); either verification scope may route there, because release-handoff opens one PR per stage. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`",
3
+ "forbidden": [
4
+ "source code edits, follow-up bug fixes, or scope expansion",
5
+ "state-mutating commands against the project or shared environments; permitted mutations are limited to the run's own `.okstra` artifacts (reports, state, `<task_root>/qa/result-*.json` sidecars, `okstra handoff record-verified` on acceptance) and Tier3 conformance scripts that mutate only their qaEnv replica datastore — everything else is read-only execution of pre-existing test or validation commands",
6
+ "starting any follow-up phase inside this run; record findings and end the run"
7
+ ]
8
+ }