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
@@ -5,10 +5,8 @@ from __future__ import annotations
5
5
  import hashlib
6
6
  import json
7
7
  import os
8
- import posixpath
9
8
  import re
10
9
  import stat
11
- from collections import Counter
12
10
  from dataclasses import dataclass
13
11
  from pathlib import Path
14
12
  from typing import Any, Mapping
@@ -19,7 +17,6 @@ from .final_report_paths import (
19
17
  final_report_markdown_path,
20
18
  is_full_reading_copy_path,
21
19
  )
22
- from .exact_coverage import ExactCoverageError, calculate_plan_exact_coverage
23
20
  from .final_report_schema import (
24
21
  SchemaError,
25
22
  load_schema_for_data,
@@ -30,9 +27,11 @@ from .json_boundary import (
30
27
  load_owned_object_snapshot,
31
28
  write_owned_object_atomic,
32
29
  )
33
- from .report_views import normalize_direction_selection_identity
30
+ from .report_views import (
31
+ direction_candidate_ineligibility,
32
+ normalize_direction_selection_identity,
33
+ )
34
34
  from .qa_commands import find_denied_tokens
35
- from .scope_provenance import brief_end_state_id_sequence
36
35
  from .user_response import UserResponseError, parse_direction_selection
37
36
 
38
37
 
@@ -309,15 +308,13 @@ def _selected_option(
309
308
  raise DirectionSelectionError(str(exc)) from exc
310
309
  if report_option_id != option_id:
311
310
  raise DirectionSelectionError("selected candidate id does not match report")
312
- coverage = option.get("coverageSummary")
313
- if not isinstance(coverage, Mapping) or coverage.get("coverageVerdict") != "exact":
314
- raise DirectionSelectionError("selected candidate must have exact coverage")
311
+ ineligibility = direction_candidate_ineligibility(option)
312
+ if ineligibility:
313
+ raise DirectionSelectionError(ineligibility)
315
314
  return option, report_option_name
316
315
 
317
316
 
318
- def _validate_no_blockers(data: Mapping[str, Any], option: Mapping[str, Any]) -> None:
319
- if option.get("safetyBlockers") or option.get("unresolvedFeasibilityFacts"):
320
- raise DirectionSelectionError("selected candidate has a safety blocker")
317
+ def _validate_no_blockers(data: Mapping[str, Any]) -> None:
321
318
  blockers = progress_blocking_ids(
322
319
  data.get("clarificationItems"), USER_INPUT_BLOCKS
323
320
  )
@@ -389,7 +386,7 @@ def resolve_selected_direction(
389
386
  else:
390
387
  raise DirectionSelectionError(f"unsupported selection mode: {mode}")
391
388
  option, normalized_option_name = _selected_option(selection, option_id)
392
- _validate_no_blockers(data, option)
389
+ _validate_no_blockers(data)
393
390
  return SelectedDirection(
394
391
  mode=mode,
395
392
  task_key=expected_task_key,
@@ -435,28 +432,6 @@ def write_selected_direction_snapshot(
435
432
  return destination
436
433
 
437
434
 
438
- _PLAN_EXECUTION_FIELDS = (
439
- "directionRealization",
440
- "stageMap",
441
- "stages",
442
- "designPreparation",
443
- "dependencyMigrationRisk",
444
- "validationChecklist",
445
- "rollbackStrategy",
446
- "requirementCoverage",
447
- "coverageSummary",
448
- "variationPointAnalysis",
449
- "planBodyVerification",
450
- "stepwiseExecution",
451
- "supersessionLedger",
452
- "crossProjectDependencies",
453
- "decisionDrafts",
454
- "skippedAdrCandidates",
455
- "incrementalDecision",
456
- "userNarrative",
457
- )
458
-
459
-
460
435
  def load_selected_direction_snapshot(
461
436
  snapshot_path: Path,
462
437
  ) -> tuple[SelectedDirectionSnapshot | None, list[str]]:
@@ -521,341 +496,6 @@ def validate_selected_direction_ref(
521
496
  return failures
522
497
 
523
498
 
524
- def _direction_realization_errors(
525
- realization: object, snapshot: SelectedDirectionSnapshot
526
- ) -> list[str]:
527
- if not isinstance(realization, Mapping):
528
- return ["directionRealization is missing from a plan-ready result"]
529
- direction = snapshot.data.get("direction")
530
- if not isinstance(direction, Mapping):
531
- return ["selected direction snapshot is missing its direction object"]
532
- comparisons = {
533
- "coreMechanism": direction.get("coreMechanism"),
534
- "architectureBoundaries": direction.get("architectureBoundaries"),
535
- "planningInvariants": snapshot.data.get("planningInvariants"),
536
- "userConstraints": snapshot.data.get("userConstraints"),
537
- }
538
- return [
539
- f"directionRealization.{field} must exactly preserve the selected-direction snapshot"
540
- for field, expected in comparisons.items()
541
- if realization.get(field) != expected
542
- ]
543
-
544
-
545
- def _plan_reference_sets(
546
- planning: Mapping[str, Any],
547
- ) -> tuple[set[int], set[str], set[str], tuple[str, ...]]:
548
- stages = [row for row in planning.get("stages") or () if isinstance(row, Mapping)]
549
- stage_ids = {
550
- row["stage"] for row in stages if isinstance(row.get("stage"), int)
551
- }
552
- step_ids = {
553
- f"{stage['stage']}.{step['step']}"
554
- for stage in stages
555
- if isinstance(stage.get("stage"), int)
556
- for step in stage.get("stepwiseExecution") or ()
557
- if isinstance(step, Mapping) and isinstance(step.get("step"), int)
558
- }
559
- validation_ids = {
560
- str(row.get("id"))
561
- for row in planning.get("validationChecklist") or ()
562
- if isinstance(row, Mapping) and row.get("id")
563
- }
564
- realization = planning.get("directionRealization")
565
- file_rows = (
566
- realization.get("fileStructure") or ()
567
- if isinstance(realization, Mapping)
568
- else ()
569
- )
570
- file_paths = tuple(
571
- str(row.get("path"))
572
- for row in file_rows
573
- if isinstance(row, Mapping) and row.get("path")
574
- )
575
- return stage_ids, step_ids, validation_ids, file_paths
576
-
577
-
578
- def _duplicate_values(values: list[object]) -> tuple[object, ...]:
579
- return tuple(value for value, count in Counter(values).items() if count > 1)
580
-
581
-
582
- def _normalized_plan_file_path(value: object) -> str:
583
- return posixpath.normpath(str(value).replace("\\", "/"))
584
-
585
-
586
- def _plan_structure_errors(planning: Mapping[str, Any]) -> list[str]:
587
- failures: list[str] = []
588
- stage_map = [
589
- row for row in planning.get("stageMap") or () if isinstance(row, Mapping)
590
- ]
591
- stages = [
592
- row for row in planning.get("stages") or () if isinstance(row, Mapping)
593
- ]
594
- stage_map_numbers = [
595
- row["stage"] for row in stage_map if isinstance(row.get("stage"), int)
596
- ]
597
- stage_numbers = [
598
- row["stage"] for row in stages if isinstance(row.get("stage"), int)
599
- ]
600
- duplicate_stage_map = set(_duplicate_values(stage_map_numbers))
601
- duplicate_stages = set(_duplicate_values(stage_numbers))
602
- failures.extend(
603
- f"stageMap contains duplicate stage {number}"
604
- for number in sorted(duplicate_stage_map)
605
- )
606
- failures.extend(
607
- f"stages contains duplicate stage {number}"
608
- for number in sorted(duplicate_stages)
609
- )
610
-
611
- stage_map_set = set(stage_map_numbers)
612
- stage_set = set(stage_numbers)
613
- failures.extend(
614
- f"stageMap is missing stages stage {number}"
615
- for number in sorted(stage_set - stage_map_set)
616
- )
617
- failures.extend(
618
- f"stages is missing stageMap stage {number}"
619
- for number in sorted(stage_map_set - stage_set)
620
- )
621
- unique_stage_map = {
622
- row["stage"]: row
623
- for row in stage_map
624
- if isinstance(row.get("stage"), int)
625
- and row["stage"] not in duplicate_stage_map
626
- }
627
- unique_stages = {
628
- row["stage"]: row
629
- for row in stages
630
- if isinstance(row.get("stage"), int) and row["stage"] not in duplicate_stages
631
- }
632
- failures.extend(
633
- f"stageMap and stages stage {number} title must match"
634
- for number in sorted(unique_stage_map.keys() & unique_stages.keys())
635
- if unique_stage_map[number].get("title")
636
- != unique_stages[number].get("title")
637
- )
638
-
639
- for stage in stages:
640
- stage_number = stage.get("stage")
641
- step_numbers = [
642
- step["step"]
643
- for step in stage.get("stepwiseExecution") or ()
644
- if isinstance(step, Mapping) and isinstance(step.get("step"), int)
645
- ]
646
- failures.extend(
647
- f"stages stage {stage_number} step {step_number} is duplicate"
648
- for step_number in sorted(_duplicate_values(step_numbers))
649
- )
650
-
651
- validation_ids = [
652
- str(row["id"])
653
- for row in planning.get("validationChecklist") or ()
654
- if isinstance(row, Mapping) and row.get("id")
655
- ]
656
- failures.extend(
657
- f"validationChecklist id {validation_id} is duplicate"
658
- for validation_id in sorted(_duplicate_values(validation_ids))
659
- )
660
-
661
- realization = planning.get("directionRealization")
662
- file_rows = (
663
- [row for row in realization.get("fileStructure") or () if isinstance(row, Mapping)]
664
- if isinstance(realization, Mapping)
665
- else []
666
- )
667
- file_ids = [str(row["id"]) for row in file_rows if row.get("id")]
668
- failures.extend(
669
- f"directionRealization.fileStructure id {file_id} is duplicate"
670
- for file_id in sorted(_duplicate_values(file_ids))
671
- )
672
- normalized_paths = [
673
- _normalized_plan_file_path(row["path"])
674
- for row in file_rows
675
- if row.get("path")
676
- ]
677
- failures.extend(
678
- f"directionRealization.fileStructure path {path} is duplicate after normalization"
679
- for path in sorted(_duplicate_values(normalized_paths))
680
- )
681
- return failures
682
-
683
-
684
- def _coverage_reference_errors(
685
- rows: list[Mapping[str, Any]],
686
- stage_ids: set[int],
687
- step_ids: set[str],
688
- validation_ids: set[str],
689
- file_paths: tuple[str, ...],
690
- dependencies: list[Mapping[str, Any]],
691
- ) -> list[str]:
692
- failures: list[str] = []
693
- reference_sets = {
694
- "stageRefs": stage_ids,
695
- "stepRefs": step_ids,
696
- "validationRefs": validation_ids,
697
- "fileRefs": set(file_paths),
698
- "crossProjectDependencyRefs": {row.get("id") for row in dependencies},
699
- }
700
- for row in rows:
701
- requirement_id = str(row.get("originalRequirementId") or "<missing>")
702
- external_refs = row.get("crossProjectDependencyRefs") or ()
703
- if row.get("status") == "externally-tracked":
704
- if not external_refs:
705
- failures.append(
706
- f"requirementCoverage {requirement_id} requires crossProjectDependencyRefs"
707
- )
708
- for ref in external_refs:
709
- matches = [item for item in dependencies if item.get("id") == ref]
710
- if (
711
- len(matches) != 1
712
- or any(
713
- not isinstance(matches[0].get(field), str)
714
- or not matches[0][field].strip()
715
- for field in (
716
- "project",
717
- "requiredWork",
718
- "verificationSignal",
719
- "linkedWork",
720
- "howToStart",
721
- )
722
- )
723
- or matches[0].get("direction")
724
- not in {"upstream-precondition", "downstream-carry"}
725
- ):
726
- failures.append(
727
- f"requirementCoverage {requirement_id} crossProjectDependencyRefs must resolve to one complete dependency: {ref}"
728
- )
729
- elif external_refs:
730
- failures.append(
731
- f"requirementCoverage {requirement_id} external references require externally-tracked status"
732
- )
733
- elif any(
734
- not row.get(field)
735
- for field in ("stageRefs", "stepRefs", "validationRefs", "fileRefs")
736
- ):
737
- failures.append(
738
- f"requirementCoverage {requirement_id} requires local stage, step, validation and file references"
739
- )
740
- for field, valid_values in reference_sets.items():
741
- dangling = [
742
- value for value in row.get(field) or () if value not in valid_values
743
- ]
744
- if dangling:
745
- failures.append(
746
- f"requirementCoverage {requirement_id} {field} contains "
747
- f"dangling reference(s): {dangling}"
748
- )
749
- return failures
750
-
751
-
752
- def _requirement_ids_for_scope(
753
- rows: list[Mapping[str, Any]], field: str, value: object
754
- ) -> tuple[str, ...]:
755
- return tuple(
756
- str(row.get("originalRequirementId"))
757
- for row in rows
758
- if value in (row.get(field) or ()) and row.get("originalRequirementId")
759
- )
760
-
761
-
762
- def _coverage_summary_errors(
763
- planning: Mapping[str, Any], original_ids: tuple[str, ...]
764
- ) -> list[str]:
765
- rows = [
766
- row
767
- for row in planning.get("requirementCoverage") or ()
768
- if isinstance(row, Mapping)
769
- ]
770
- row_ids = tuple(str(row.get("originalRequirementId") or "") for row in rows)
771
- failures: list[str] = []
772
- if not original_ids:
773
- return ["original requirement denominator from taskBriefPath is empty"]
774
- if row_ids != original_ids:
775
- failures.append(
776
- "original requirement rows must match the taskBriefPath requirement "
777
- "sequence exactly once"
778
- )
779
- return failures
780
- structure_failures = _plan_structure_errors(planning)
781
- failures.extend(structure_failures)
782
- stage_ids, step_ids, validation_ids, file_paths = _plan_reference_sets(planning)
783
- failures.extend(
784
- _coverage_reference_errors(
785
- rows,
786
- stage_ids,
787
- step_ids,
788
- validation_ids,
789
- file_paths,
790
- [
791
- row
792
- for row in planning.get("crossProjectDependencies") or ()
793
- if isinstance(row, Mapping)
794
- ],
795
- )
796
- )
797
- if failures:
798
- return failures
799
- # 검증된 외부 작업 연결은 계획 범위에만 포함하며 실제 완료 상태는 바꾸지 않는다.
800
- statuses = {
801
- row_id: "covered"
802
- if row.get("status") == "externally-tracked"
803
- else str(row.get("status") or "")
804
- for row_id, row in zip(row_ids, rows)
805
- }
806
- stage_requirements = {
807
- stage: _requirement_ids_for_scope(rows, "stageRefs", stage)
808
- for stage in sorted(stage_ids)
809
- }
810
- file_requirements = {
811
- path: _requirement_ids_for_scope(rows, "fileRefs", path) for path in file_paths
812
- }
813
- try:
814
- result, unmapped_stages, unmapped_files = calculate_plan_exact_coverage(
815
- original_ids, statuses, stage_requirements, file_requirements
816
- )
817
- except ExactCoverageError as exc:
818
- return [*failures, f"plan-ready requires exact 100% coverage: {exc}"]
819
- failures.extend(
820
- f"unmapped stage {stage} has no original requirement"
821
- for stage in unmapped_stages
822
- )
823
- failures.extend(
824
- f"unmapped file change {path} has no original requirement"
825
- for path in unmapped_files
826
- )
827
- failures.extend(
828
- _declared_coverage_summary_errors(
829
- planning.get("coverageSummary"), result, unmapped_stages, unmapped_files
830
- )
831
- )
832
- if result.verdict != "exact":
833
- failures.append("plan-ready requires exact 100% coverage")
834
- return failures
835
-
836
-
837
- def _declared_coverage_summary_errors(
838
- summary: object,
839
- result: Any,
840
- unmapped_stages: tuple[int, ...],
841
- unmapped_files: tuple[str, ...],
842
- ) -> list[str]:
843
- if not isinstance(summary, Mapping):
844
- return ["coverageSummary is missing from a plan-ready result"]
845
- expected = {
846
- "coveragePercent": result.coverage_percent,
847
- "scopePrecisionPercent": result.scope_precision_percent,
848
- "coverageVerdict": result.verdict,
849
- "unmappedStages": list(unmapped_stages),
850
- "unmappedFileChanges": list(unmapped_files),
851
- }
852
- return [
853
- f"coverageSummary.{field} does not match recalculated plan coverage"
854
- for field, value in expected.items()
855
- if summary.get(field) != value
856
- ]
857
-
858
-
859
499
  # stage 를 게이트하는 검증 행이 implementation run 안에서 실행 가능함을 강제하는
860
500
  # 마커. 실측 실패값(dev-10341 VC-013/VC-014)에서 왔다: 수동 워크스루·배포 전제
861
501
  # 문구가 stageRefs 를 단 채 실리면, implementation run 은 배포 금지·자격증명
@@ -909,119 +549,3 @@ def stage_validation_executability_errors(
909
549
  "or final-verification's user-owned external QA"
910
550
  )
911
551
  return failures
912
-
913
-
914
- def _is_config_only_path(path: str) -> bool:
915
- name = posixpath.basename(posixpath.normpath(str(path).replace("\\", "/")))
916
- if name == "Dockerfile" or name.startswith("Dockerfile."):
917
- return True
918
- if name.startswith("docker-compose") or name.startswith(".env"):
919
- return True
920
- return name.endswith((".yml", ".yaml"))
921
-
922
-
923
- def _micro_stage_fold_errors(planning: Mapping[str, Any]) -> list[str]:
924
- """설정 파일 전용 종속 stage 는 독립 stage 가 아니다 — 커밋으로만 나눈다.
925
-
926
- 실측(dev-10341): 다른 stage 가 도입한 플래그를 Dockerfile/compose 2줄,
927
- workflow 2줄로 전파하는 작업이 stage 2·3 을 각각 차지해 run 2회(회당
928
- 30분+)와 검증 차단 1회를 낳았다. 값 전파는 도입 stage 의 스텝이고,
929
- 분리가 필요하면 커밋을 나눈다. 첫 stage(의존 없음)는 예외다 — 설정만
930
- 바꾸는 태스크 자체는 정당하다.
931
- """
932
- dependent: dict[int, bool] = {}
933
- for row in planning.get("stageMap") or ():
934
- if isinstance(row, Mapping) and isinstance(row.get("stage"), int):
935
- raw = str(row.get("dependsOn") or "").strip().lower()
936
- dependent[row["stage"]] = raw not in ("", "(none)", "none")
937
- failures: list[str] = []
938
- for stage in planning.get("stages") or ():
939
- if not isinstance(stage, Mapping):
940
- continue
941
- number = stage.get("stage")
942
- if not isinstance(number, int) or not dependent.get(number):
943
- continue
944
- paths = [
945
- str(path)
946
- for step in stage.get("stepwiseExecution") or ()
947
- if isinstance(step, Mapping)
948
- for path in step.get("plannedPaths") or ()
949
- ]
950
- if paths and all(_is_config_only_path(path) for path in paths):
951
- failures.append(
952
- f"stage {number} is a config-only propagation stage "
953
- f"({sorted(set(paths))}); fold it into the stage that introduces "
954
- "the value and split the work by commits, not stages"
955
- )
956
- return failures
957
-
958
-
959
- def _direction_invalidation_errors(planning: Mapping[str, Any]) -> list[str]:
960
- failures: list[str] = []
961
- invalidation = planning.get("directionInvalidation")
962
- if not isinstance(invalidation, Mapping):
963
- failures.append("direction-invalidated requires directionInvalidation evidence")
964
- else:
965
- for field in ("reasons", "codeEvidence"):
966
- values = invalidation.get(field)
967
- if not isinstance(values, list) or not values or any(
968
- not isinstance(value, str) or not value.strip() for value in values
969
- ):
970
- failures.append(f"directionInvalidation.{field} must contain evidence")
971
- if planning.get("routing") != "implementation-option-selection":
972
- failures.append(
973
- "direction-invalidated routing must be implementation-option-selection"
974
- )
975
- for field in _PLAN_EXECUTION_FIELDS:
976
- if field in planning:
977
- failures.append(
978
- f"direction-invalidated must not contain execution field {field}"
979
- )
980
- return failures
981
-
982
-
983
- def validate_selected_direction_plan(
984
- data: Mapping[str, Any], brief_path: Path, snapshot_path: Path
985
- ) -> list[str]:
986
- """Validate selected-direction planning semantics against independent inputs."""
987
- planning = data.get("implementationPlanning")
988
- if not isinstance(planning, Mapping):
989
- return ["implementationPlanning is missing"]
990
- if planning.get("planningContract") != "selected-direction":
991
- return ["implementationPlanning.planningContract must be selected-direction"]
992
- snapshot, failures = load_selected_direction_snapshot(snapshot_path)
993
- if snapshot is None:
994
- return failures
995
- header = data.get("header")
996
- plan_task_key = header.get("taskKey") if isinstance(header, Mapping) else None
997
- snapshot_task_key = snapshot.data.get("taskKey")
998
- if not isinstance(plan_task_key, str) or not plan_task_key:
999
- failures.append("plan header.taskKey must be a non-empty string")
1000
- if not isinstance(snapshot_task_key, str) or not snapshot_task_key:
1001
- failures.append("selected direction snapshot taskKey must be a non-empty string")
1002
- elif isinstance(plan_task_key, str) and snapshot_task_key != plan_task_key:
1003
- failures.append(
1004
- "selected direction snapshot taskKey must exactly match plan header.taskKey"
1005
- )
1006
- ref = planning.get("selectedDirectionRef")
1007
- if not isinstance(ref, Mapping):
1008
- failures.append("selectedDirectionRef is missing")
1009
- else:
1010
- failures.extend(validate_selected_direction_ref(ref, snapshot))
1011
- outcome = planning.get("outcome")
1012
- if outcome == "direction-invalidated":
1013
- failures.extend(_direction_invalidation_errors(planning))
1014
- return failures
1015
- if outcome != "plan-ready":
1016
- return [*failures, f"selected-direction outcome is invalid: {outcome!r}"]
1017
- failures.extend(
1018
- _direction_realization_errors(planning.get("directionRealization"), snapshot)
1019
- )
1020
- failures.extend(
1021
- _coverage_summary_errors(
1022
- planning, brief_end_state_id_sequence(Path(brief_path))
1023
- )
1024
- )
1025
- failures.extend(stage_validation_executability_errors(planning))
1026
- failures.extend(_micro_stage_fold_errors(planning))
1027
- return failures
@@ -958,7 +958,14 @@ def _required_resource_path_candidates(
958
958
  paths = (profiles / "_implementation-verifier.md",)
959
959
  else:
960
960
  return ()
961
- return paths
961
+ from .phases.catalog import profile_sidecar
962
+
963
+ root = context.request.runtime_root.resolve()
964
+ return tuple(
965
+ profile_sidecar(root, str(path.relative_to(root)))
966
+ if path.is_relative_to(root) else path
967
+ for path in paths
968
+ )
962
969
 
963
970
 
964
971
  def _executor_profile_path(context: _MaterializationContext) -> Path:
@@ -283,8 +283,8 @@ def _direction_selection_reason(selection: Mapping[str, Any]) -> str:
283
283
  # (`implementation_direction.py` `_comparison_selection`). 종전 문장은
284
284
  # "위저드에 후보가 선택지로 나온다" 고 해서 사이드카 없이 계획을 열게 했다.
285
285
  return (
286
- f"{head} 고르는 자리는 이 리포트의 HTML 열람본입니다 — 방향 선택 칸에서 "
287
- "후보를 고르고 Export 한 파일을 "
286
+ f"{head} `/okstra-user-response` 에서 이 리포트를 골라 채팅으로 고르거나, "
287
+ "이 리포트의 HTML 열람본 방향 선택 칸에서 고르고 Export 한 파일을 "
288
288
  "`runs/implementation-option-selection/user-responses/"
289
289
  "user-response-implementation-option-selection-<이 리포트 번호>.md` 로 "
290
290
  "저장하세요. 그다음 `/okstra-run` 으로 `implementation-planning` 을 "