okstra 0.206.0 → 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 (210) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/lifecycle/install.mjs +1 -1
  3. package/dist/commands/lifecycle/install.mjs.map +1 -1
  4. package/docs/architecture.md +12 -12
  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 +39 -18
  9. package/package.json +1 -1
  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 +13 -54
  16. package/runtime/prompts/lead/phase-routing.md +64 -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/dispatch_state.py +22 -0
  30. package/runtime/python/okstra_ctl/doctor.py +2 -1
  31. package/runtime/python/okstra_ctl/execution_mutation_audit.py +27 -1
  32. package/runtime/python/okstra_ctl/handoff.py +11 -466
  33. package/runtime/python/okstra_ctl/handoff_error.py +5 -0
  34. package/runtime/python/okstra_ctl/implementation_direction.py +0 -477
  35. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
  36. package/runtime/python/okstra_ctl/option_comparison.py +3 -165
  37. package/runtime/python/okstra_ctl/option_votes.py +3 -191
  38. package/runtime/python/okstra_ctl/paths.py +8 -6
  39. package/runtime/python/okstra_ctl/phases/catalog.py +56 -12
  40. package/runtime/python/okstra_ctl/phases/change_impact_analysis/boundary.json +11 -0
  41. package/runtime/python/okstra_ctl/phases/change_impact_analysis/entry.py +39 -0
  42. package/runtime/python/okstra_ctl/{report_html/view_models/change_impact_analysis.py → phases/change_impact_analysis/report.py} +3 -3
  43. package/runtime/python/okstra_ctl/phases/change_impact_analysis/spec.md +26 -0
  44. package/runtime/python/okstra_ctl/phases/change_impact_analysis/validation.py +23 -0
  45. package/runtime/python/okstra_ctl/phases/error_analysis/__init__.py +1 -0
  46. package/runtime/python/okstra_ctl/phases/error_analysis/boundary.json +9 -0
  47. package/runtime/{prompts/profiles/error-analysis.md → python/okstra_ctl/phases/error_analysis/profile.md} +2 -2
  48. package/runtime/python/okstra_ctl/{report_html/view_models/error_analysis.py → phases/error_analysis/report.py} +9 -8
  49. package/runtime/{templates/reports → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis-input.template.md +1 -1
  50. package/runtime/python/okstra_ctl/phases/error_analysis/spec.md +118 -0
  51. package/runtime/python/okstra_ctl/phases/error_analysis/validation.py +241 -0
  52. package/runtime/python/okstra_ctl/phases/feature_analysis/__init__.py +1 -0
  53. package/runtime/python/okstra_ctl/phases/feature_analysis/boundary.json +8 -0
  54. package/runtime/python/okstra_ctl/phases/feature_analysis/entry.py +63 -0
  55. package/runtime/python/okstra_ctl/{report_html/view_models/feature_analysis.py → phases/feature_analysis/report.py} +12 -5
  56. package/runtime/python/okstra_ctl/phases/feature_analysis/spec.md +22 -0
  57. package/runtime/python/okstra_ctl/phases/feature_analysis/validation.py +27 -0
  58. package/runtime/python/okstra_ctl/phases/feature_analysis/wizard.py +95 -0
  59. package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
  60. package/runtime/python/okstra_ctl/phases/final_verification/profile.md +1 -1
  61. package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
  62. package/runtime/python/okstra_ctl/phases/final_verification/spec.md +1 -1
  63. package/runtime/python/okstra_ctl/phases/implementation/__init__.py +1 -0
  64. package/runtime/python/okstra_ctl/phases/implementation/boundary.json +17 -0
  65. package/runtime/python/okstra_ctl/{implementation_stage.py → phases/implementation/entry.py} +22 -10
  66. package/runtime/{prompts/host-orchestration/implementation.md → python/okstra_ctl/phases/implementation/host-rules.md} +1 -1
  67. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-deliverable.md +1 -1
  68. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-executor.md +4 -3
  69. package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-verifier.md +4 -4
  70. package/runtime/{prompts/profiles/implementation.md → python/okstra_ctl/phases/implementation/profile.md} +5 -5
  71. package/runtime/python/okstra_ctl/{report_html/view_models/implementation.py → phases/implementation/report.py} +3 -3
  72. package/runtime/{templates/reports → python/okstra_ctl/phases/implementation/report_assets}/implementation-input.template.md +1 -1
  73. package/runtime/python/okstra_ctl/phases/implementation/spec.md +238 -0
  74. package/runtime/python/okstra_ctl/phases/implementation/validation.py +205 -0
  75. package/runtime/python/okstra_ctl/phases/implementation/wizard.py +39 -0
  76. package/runtime/python/okstra_ctl/phases/implementation_option_selection/__init__.py +1 -0
  77. package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +80 -0
  78. package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +10 -0
  79. package/runtime/python/okstra_ctl/phases/implementation_option_selection/comparison.py +168 -0
  80. package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +27 -0
  81. package/runtime/{prompts/profiles/implementation-option-selection.md → python/okstra_ctl/phases/implementation_option_selection/profile.md} +2 -2
  82. package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
  83. package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +83 -0
  84. package/runtime/python/okstra_ctl/{implementation_options.py → phases/implementation_option_selection/validation.py} +3 -3
  85. package/runtime/python/okstra_ctl/phases/implementation_option_selection/votes.py +194 -0
  86. package/runtime/python/okstra_ctl/phases/implementation_planning/__init__.py +1 -0
  87. package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +2345 -0
  88. package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +12 -0
  89. package/runtime/python/okstra_ctl/phases/implementation_planning/entry.py +161 -0
  90. package/runtime/python/okstra_ctl/phases/implementation_planning/guidance.py +178 -0
  91. package/runtime/{prompts/lead → python/okstra_ctl/phases/implementation_planning/instructions}/plan-body-verification.md +61 -51
  92. package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +3295 -0
  93. package/runtime/{prompts/profiles/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/profile.md} +74 -25
  94. package/runtime/python/okstra_ctl/phases/implementation_planning/report.py +237 -0
  95. package/runtime/{templates/reports → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning-input.template.md +2 -2
  96. package/runtime/python/okstra_ctl/phases/implementation_planning/spec.md +204 -0
  97. package/runtime/python/okstra_ctl/phases/implementation_planning/validation.py +597 -0
  98. package/runtime/python/okstra_ctl/phases/implementation_planning/wizard.py +166 -0
  99. package/runtime/python/okstra_ctl/phases/improvement_discovery/boundary.json +12 -0
  100. package/runtime/python/okstra_ctl/{improvement_lenses.py → phases/improvement_discovery/lenses.py} +1 -6
  101. package/runtime/{prompts/profiles/improvement-discovery.md → python/okstra_ctl/phases/improvement_discovery/profile.md} +5 -5
  102. package/runtime/python/okstra_ctl/{report_html/view_models/improvement_discovery.py → phases/improvement_discovery/report.py} +3 -3
  103. package/runtime/{templates/reports → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery-input.template.md +1 -2
  104. package/runtime/python/okstra_ctl/phases/improvement_discovery/spec.md +29 -0
  105. package/runtime/{validators/validate_improvement_report.py → python/okstra_ctl/phases/improvement_discovery/validation.py} +5 -14
  106. package/runtime/python/okstra_ctl/phases/project_analysis/__init__.py +1 -0
  107. package/runtime/python/okstra_ctl/phases/project_analysis/boundary.json +8 -0
  108. package/runtime/python/okstra_ctl/phases/project_analysis/entry.py +11 -0
  109. package/runtime/python/okstra_ctl/{report_html/view_models/project_analysis.py → phases/project_analysis/report.py} +3 -3
  110. package/runtime/python/okstra_ctl/phases/project_analysis/spec.md +33 -0
  111. package/runtime/python/okstra_ctl/phases/project_analysis/validation.py +55 -0
  112. package/runtime/python/okstra_ctl/phases/release_handoff/__init__.py +1 -0
  113. package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +17 -0
  114. package/runtime/python/okstra_ctl/phases/release_handoff/entry.py +147 -0
  115. package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +446 -0
  116. package/runtime/{prompts/profiles/release-handoff.md → python/okstra_ctl/phases/release_handoff/profile.md} +3 -3
  117. package/runtime/python/okstra_ctl/{report_html/view_models/release_handoff.py → phases/release_handoff/report.py} +3 -3
  118. package/runtime/{templates/reports → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff-input.template.md +1 -1
  119. package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +233 -0
  120. package/runtime/python/okstra_ctl/phases/release_handoff/wizard.py +84 -0
  121. package/runtime/python/okstra_ctl/phases/requirements_discovery/__init__.py +1 -0
  122. package/runtime/python/okstra_ctl/phases/requirements_discovery/boundary.json +9 -0
  123. package/runtime/{prompts/profiles/requirements-discovery.md → python/okstra_ctl/phases/requirements_discovery/profile.md} +2 -3
  124. package/runtime/python/okstra_ctl/{report_html/view_models/requirements_discovery.py → phases/requirements_discovery/report.py} +3 -3
  125. package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +132 -0
  126. package/runtime/{validators/validate_fanout.py → python/okstra_ctl/phases/requirements_discovery/validation.py} +11 -12
  127. package/runtime/python/okstra_ctl/phases/technical_verification/__init__.py +1 -0
  128. package/runtime/python/okstra_ctl/phases/technical_verification/boundary.json +9 -0
  129. package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +100 -0
  130. package/runtime/{prompts/profiles/technical-verification.md → python/okstra_ctl/phases/technical_verification/profile.md} +1 -1
  131. package/runtime/python/okstra_ctl/{report_html/view_models/technical_verification.py → phases/technical_verification/report.py} +2 -2
  132. package/runtime/python/okstra_ctl/phases/technical_verification/spec.md +37 -0
  133. package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +90 -0
  134. package/runtime/python/okstra_ctl/plan_approval.py +70 -0
  135. package/runtime/python/okstra_ctl/plan_items_cli.py +2 -2130
  136. package/runtime/python/okstra_ctl/profile_show.py +3 -3
  137. package/runtime/python/okstra_ctl/render.py +9 -2
  138. package/runtime/python/okstra_ctl/report_assembly.py +11 -90
  139. package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
  140. package/runtime/python/okstra_ctl/report_projections.py +1 -36
  141. package/runtime/python/okstra_ctl/report_routing.py +23 -0
  142. package/runtime/python/okstra_ctl/report_synthesis_packet.py +4 -73
  143. package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
  144. package/runtime/python/okstra_ctl/report_views.py +1 -1
  145. package/runtime/python/okstra_ctl/run.py +67 -349
  146. package/runtime/python/okstra_ctl/stage_map.py +13 -0
  147. package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
  148. package/runtime/python/okstra_ctl/wizard/__init__.py +31 -31
  149. package/runtime/python/okstra_ctl/wizard/api.py +18 -0
  150. package/runtime/python/okstra_ctl/wizard/outcome.py +3 -12
  151. package/runtime/python/okstra_ctl/wizard/registry.py +20 -12
  152. package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
  153. package/runtime/python/okstra_ctl/wizard/steps_plan.py +10 -263
  154. package/runtime/python/okstra_ctl/wizard/steps_roles.py +2 -1
  155. package/runtime/python/okstra_ctl/work_categories.py +1 -1
  156. package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
  157. package/runtime/python/okstra_ctl/workflow.py +26 -143
  158. package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
  159. package/runtime/skills/okstra-run/SKILL.md +1 -1
  160. package/runtime/templates/reports/quick-input.template.md +1 -1
  161. package/runtime/templates/reports/task-brief.template.md +1 -1
  162. package/runtime/validators/validate-brief.py +2 -2
  163. package/runtime/validators/validate-run.py +299 -3940
  164. package/runtime/validators/validate_analysis_report.py +14 -126
  165. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +0 -147
  166. package/runtime/python/okstra_ctl/technical_verification.py +0 -195
  167. /package/runtime/{prompts/profiles/change-impact-analysis.json → python/okstra_ctl/phases/change_impact_analysis/profile.json} +0 -0
  168. /package/runtime/{prompts/profiles/change-impact-analysis.md → python/okstra_ctl/phases/change_impact_analysis/profile.md} +0 -0
  169. /package/runtime/{templates/reports → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis-input.template.md +0 -0
  170. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.html +0 -0
  171. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.md +0 -0
  172. /package/runtime/{prompts/profiles/error-analysis.json → python/okstra_ctl/phases/error_analysis/profile.json} +0 -0
  173. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.html +0 -0
  174. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.md +0 -0
  175. /package/runtime/{prompts/profiles/feature-analysis.json → python/okstra_ctl/phases/feature_analysis/profile.json} +0 -0
  176. /package/runtime/{prompts/profiles/feature-analysis.md → python/okstra_ctl/phases/feature_analysis/profile.md} +0 -0
  177. /package/runtime/{templates/reports → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis-input.template.md +0 -0
  178. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.html +0 -0
  179. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.md +0 -0
  180. /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-diff-review.md +0 -0
  181. /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-self-check.md +0 -0
  182. /package/runtime/{prompts/profiles/implementation.json → python/okstra_ctl/phases/implementation/profile.json} +0 -0
  183. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.html +0 -0
  184. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.md +0 -0
  185. /package/runtime/{prompts/profiles/implementation-option-selection.json → python/okstra_ctl/phases/implementation_option_selection/profile.json} +0 -0
  186. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.html +0 -0
  187. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.md +0 -0
  188. /package/runtime/{prompts/host-orchestration/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/host-rules.md} +0 -0
  189. /package/runtime/{prompts/profiles/implementation-planning.json → python/okstra_ctl/phases/implementation_planning/profile.json} +0 -0
  190. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.html +0 -0
  191. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.md +0 -0
  192. /package/runtime/{prompts/profiles/improvement-discovery.json → python/okstra_ctl/phases/improvement_discovery/profile.json} +0 -0
  193. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.html +0 -0
  194. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.md +0 -0
  195. /package/runtime/{prompts/profiles/project-analysis.json → python/okstra_ctl/phases/project_analysis/profile.json} +0 -0
  196. /package/runtime/{prompts/profiles/project-analysis.md → python/okstra_ctl/phases/project_analysis/profile.md} +0 -0
  197. /package/runtime/{templates/reports → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis-input.template.md +0 -0
  198. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.html +0 -0
  199. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.md +0 -0
  200. /package/runtime/{prompts/profiles/release-handoff.json → python/okstra_ctl/phases/release_handoff/profile.json} +0 -0
  201. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.html +0 -0
  202. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.md +0 -0
  203. /package/runtime/python/okstra_ctl/{fanout.py → phases/requirements_discovery/fanout.py} +0 -0
  204. /package/runtime/{prompts/profiles/requirements-discovery.json → python/okstra_ctl/phases/requirements_discovery/profile.json} +0 -0
  205. /package/runtime/{templates/reports → python/okstra_ctl/phases/requirements_discovery/report_assets}/fan-out-unit.template.md +0 -0
  206. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.html +0 -0
  207. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.md +0 -0
  208. /package/runtime/{prompts/profiles/technical-verification.json → python/okstra_ctl/phases/technical_verification/profile.json} +0 -0
  209. /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.html +0 -0
  210. /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.md +0 -0
@@ -15,6 +15,16 @@ for _ssot_dir in (_VALIDATORS_DIR.parent / "scripts", _VALIDATORS_DIR.parent / "
15
15
  sys.path.insert(0, str(_ssot_dir))
16
16
 
17
17
  from okstra_ctl.analysis_inputs import ANALYSIS_TASK_TYPES
18
+ from okstra_ctl.phases.change_impact_analysis.validation import (
19
+ EVIDENCE_COLLECTIONS as CHANGE_IMPACT_EVIDENCE_COLLECTIONS,
20
+ validate_change_impact_semantics,
21
+ )
22
+ from okstra_ctl.phases.feature_analysis.validation import (
23
+ EVIDENCE_COLLECTIONS as FEATURE_EVIDENCE_COLLECTIONS,
24
+ _validate_feature_semantics,
25
+ )
26
+ from okstra_ctl.analysis_scope import _normalized_project_path, _path_is_included
27
+ from okstra_ctl.phases.project_analysis.validation import validate_project_semantics
18
28
  from okstra_ctl.final_report_paths import final_report_data_path
19
29
  from okstra_ctl.paths import RunRef
20
30
  from okstra_ctl.report_views import analysis_review_context
@@ -35,19 +45,8 @@ _EVIDENCE_COLLECTIONS = {
35
45
  "externalSystems",
36
46
  "featureIndex",
37
47
  ),
38
- "feature-analysis": (
39
- "flows",
40
- "domainRules",
41
- "stateChanges",
42
- "externalInteractions",
43
- ),
44
- "change-impact-analysis": (
45
- "preservedBehaviors",
46
- "impactItems",
47
- "dependencyBlastRadius",
48
- "testImpact",
49
- "operationalImpact",
50
- ),
48
+ "feature-analysis": FEATURE_EVIDENCE_COLLECTIONS,
49
+ "change-impact-analysis": CHANGE_IMPACT_EVIDENCE_COLLECTIONS,
51
50
  }
52
51
  _ANALYSIS_PARENT_KEYS = {
53
52
  "project-analysis": "projectAnalysis",
@@ -111,33 +110,6 @@ def validate_analysis_snapshot(
111
110
  )
112
111
 
113
112
 
114
- def _normalized_project_path(path: object) -> PurePosixPath | None:
115
- if not isinstance(path, str):
116
- return None
117
- raw = path.strip().rstrip("/")
118
- if not raw or raw == "." or raw.startswith("/"):
119
- return None
120
- if any(part in {"", ".", ".."} for part in raw.split("/")):
121
- return None
122
- normalized = PurePosixPath(raw)
123
- if normalized.is_absolute():
124
- return None
125
- return normalized
126
-
127
-
128
- def _path_is_included(path: object, included_paths: list[object]) -> bool:
129
- candidate = _normalized_project_path(path)
130
- if candidate is None:
131
- return False
132
- for raw in included_paths:
133
- included = _normalized_project_path(raw)
134
- if included is not None and (
135
- candidate == included or included in candidate.parents
136
- ):
137
- return True
138
- return False
139
-
140
-
141
113
  def _validate_included_paths(included_paths: list[object], errors: list[str]) -> None:
142
114
  for index, path in enumerate(included_paths):
143
115
  if _normalized_project_path(path) is None:
@@ -201,90 +173,6 @@ def _validate_all_current_code_evidence(
201
173
  )
202
174
 
203
175
 
204
- def _validate_project_semantics(data: dict, errors: list[str]) -> None:
205
- common = data.get("analysisCommon") or {}
206
- included = ((common.get("scope") or {}).get("includedPaths") or [])
207
- analysis = data.get("projectAnalysis") or {}
208
- for row in analysis.get("featureIndex") or []:
209
- if not isinstance(row, dict):
210
- continue
211
- entry = row.get("representativeEntryPoint") or {}
212
- if not _path_is_included(entry.get("path"), included):
213
- errors.append(
214
- f"projectAnalysis.featureIndex `{row.get('id') or '?'}` representative "
215
- "entry point must be inside analysisCommon.scope.includedPaths"
216
- )
217
- _validate_component_references(analysis, errors)
218
-
219
-
220
- def _validate_component_references(analysis: dict, errors: list[str]) -> None:
221
- """Every componentId a row names has to be a component this report declares.
222
-
223
- The reader clicks these — they render as links to the component's row — so
224
- a reference to a component that was never listed is a dead anchor, and one
225
- to a component that exists under a different id is worse: it reads as a
226
- relationship the analysis never found.
227
- """
228
- declared = {
229
- row.get("id")
230
- for row in analysis.get("components") or []
231
- if isinstance(row, dict) and row.get("id")
232
- }
233
- if not declared:
234
- return
235
-
236
- def check(component_id: object, where: str) -> None:
237
- if isinstance(component_id, str) and component_id and component_id not in declared:
238
- errors.append(f"{where} names component `{component_id}`, which is not declared")
239
-
240
- for row in analysis.get("internalInterfaces") or []:
241
- if not isinstance(row, dict):
242
- continue
243
- where = f"projectAnalysis.internalInterfaces `{row.get('id') or '?'}`"
244
- check(row.get("ownerComponentId"), where)
245
- for consumer in row.get("consumers") or []:
246
- check(consumer, where)
247
- for row in analysis.get("workflows") or []:
248
- if not isinstance(row, dict):
249
- continue
250
- for step in row.get("steps") or []:
251
- if isinstance(step, dict):
252
- check(step.get("componentId"), f"projectAnalysis.workflows `{row.get('id') or '?'}`")
253
-
254
-
255
- def _validate_feature_semantics(data: dict, errors: list[str]) -> None:
256
- analysis = data.get("featureAnalysis") or {}
257
- common_target = (((data.get("analysisCommon") or {}).get("scope") or {}).get(
258
- "resolvedTarget"
259
- ) or {})
260
- feature_target = analysis.get("target") or {}
261
- for field in ("inputMode", "requestedValue"):
262
- if feature_target.get(field) != common_target.get(field):
263
- errors.append(
264
- f"featureAnalysis.target.{field} must match "
265
- f"analysisCommon.scope.resolvedTarget.{field}"
266
- )
267
- resolved_feature = common_target.get("feature") or {}
268
- if common_target.get("inputMode") == "feature-index" and (
269
- feature_target.get("featureId") != resolved_feature.get("id")
270
- ):
271
- errors.append(
272
- "featureAnalysis.target.featureId must match the resolved feature id"
273
- )
274
-
275
-
276
- def _validate_change_impact_semantics(data: dict, errors: list[str]) -> None:
277
- analysis = data.get("changeImpactAnalysis") or {}
278
- allowed = {"constraint", "unknown"}
279
- for index, row in enumerate(analysis.get("planningInputs") or []):
280
- if not isinstance(row, dict):
281
- continue
282
- for field in sorted(set(row) - allowed):
283
- errors.append(
284
- f"changeImpactAnalysis.planningInputs[{index}] forbids `{field}`; "
285
- "planning inputs may contain constraints and unknowns only"
286
- )
287
-
288
176
 
289
177
  def _required_worker_roles(
290
178
  run_manifest: dict, errors: list[str]
@@ -475,11 +363,11 @@ def validate_analysis_semantics(
475
363
  )
476
364
  _validate_all_current_code_evidence(task_type, data, errors)
477
365
  if task_type == "project-analysis":
478
- _validate_project_semantics(data, errors)
366
+ validate_project_semantics(data, errors)
479
367
  elif task_type == "feature-analysis":
480
368
  _validate_feature_semantics(data, errors)
481
369
  elif task_type == "change-impact-analysis":
482
- _validate_change_impact_semantics(data, errors)
370
+ validate_change_impact_semantics(data, errors)
483
371
  _validate_scope_before_dispatch(
484
372
  data, analysis_worker_roles, reporter_confirmation, errors
485
373
  )
@@ -1,147 +0,0 @@
1
- """Human-first implementation-planning view and approval state."""
2
- from __future__ import annotations
3
-
4
- import re
5
- from dataclasses import dataclass
6
-
7
- from ..common import evidence_index
8
- from ..models import HumanReportView, VisualEdge, VisualNode
9
- from ..visualizations import stage_map_figure
10
-
11
-
12
- @dataclass(frozen=True)
13
- class PlanApprovalState:
14
- option_names: tuple[str, ...]
15
- recommended_option: str
16
- disabled_reason: str
17
- blocker_ids: tuple[str, ...]
18
- show_option_selector: bool = True
19
- reentry_command: str = ""
20
-
21
-
22
- def resolve_recommended_option(rec_name: str, names: tuple[str, ...]) -> str:
23
- if rec_name in names:
24
- return rec_name
25
- matches = [
26
- name
27
- for name in names
28
- if rec_name and (name.startswith(rec_name) or rec_name.startswith(name))
29
- ]
30
- return matches[0] if len(matches) == 1 else names[0]
31
-
32
-
33
- def plan_approval_state(data: dict) -> PlanApprovalState | None:
34
- planning = data.get("implementationPlanning")
35
- if not isinstance(planning, dict):
36
- return None
37
- blockers = tuple(
38
- row["id"]
39
- for row in data.get("clarificationItems", [])
40
- if row.get("blocks") == "approval"
41
- and row.get("status") in {"open", "answered"}
42
- )
43
- reason = f"{len(blockers)} approval blocker(s) unresolved" if blockers else ""
44
- if planning.get("planningContract") == "selected-direction":
45
- if planning.get("outcome") == "direction-invalidated" and not reason:
46
- reason = "selected direction invalidated"
47
- task_key = (data.get("header") or {}).get("taskKey", "")
48
- reentry_command = (
49
- f"/okstra-run task-key={task_key} "
50
- "task-type=implementation-option-selection"
51
- if planning.get("outcome") == "direction-invalidated" and task_key
52
- else ""
53
- )
54
- return PlanApprovalState(
55
- (), "", reason, blockers, False, reentry_command
56
- )
57
- names = tuple(
58
- row["name"]
59
- for row in planning.get("optionCandidates", [])
60
- if isinstance(row, dict) and row.get("name")
61
- )
62
- if not names:
63
- return None
64
- recommended = planning.get("recommendedOption", {}).get("name", "")
65
- return PlanApprovalState(
66
- names,
67
- resolve_recommended_option(recommended, names),
68
- reason,
69
- blockers,
70
- )
71
-
72
-
73
- def _stage_figure(planning: dict):
74
- nodes = tuple(
75
- VisualNode(
76
- f"stage-{row['stage']}",
77
- f"Stage {row['stage']} · {row['title']}",
78
- "stage",
79
- "planned",
80
- row["exitContractSummary"],
81
- )
82
- for row in planning["stageMap"]
83
- )
84
- edges = tuple(
85
- VisualEdge(f"stage-{dependency}", f"stage-{row['stage']}", "precedes", "dependency")
86
- for row in planning["stageMap"]
87
- for dependency in re.findall(r"\d+", row["dependsOn"])
88
- )
89
- return stage_map_figure(nodes=nodes, edges=edges, title="Implementation stage dependencies")
90
-
91
-
92
- # Record fields this template anchors as `id-<row id>` (see
93
- # `HumanReportView.anchored_fields`). The implementer-facing tables the
94
- # approver does not read — validation checklist, cross-project dependencies,
95
- # migration risk, rollback — are not drawn, so their rows land in the ledger.
96
- ANCHORED_FIELDS = (
97
- "implementationPlanning.directionRealization.fileStructure",
98
- "implementationPlanning.directionRealization.planningInvariants",
99
- "implementationPlanning.optionCandidates.fileStructure",
100
- "implementationPlanning.designPreparation.items",
101
- "implementationPlanning.requirementCoverage",
102
- "implementationPlanning.planBodyVerification.planItems",
103
- "implementationPlanning.supersessionLedger",
104
- )
105
-
106
-
107
- def build_implementation_planning_view(data: dict) -> HumanReportView:
108
- planning = data["implementationPlanning"]
109
- figure = _stage_figure(planning) if planning.get("stageMap") else None
110
- approval = plan_approval_state(data)
111
- activities = tuple(
112
- row for row in data.get("agentActivity", []) if isinstance(row, dict)
113
- )
114
- decision_cards = tuple(
115
- row
116
- for row in data.get("clarificationItems", [])
117
- if isinstance(row, dict)
118
- and row.get("blocks") == "approval"
119
- and isinstance(row.get("approvalContext"), dict)
120
- )
121
- context = {
122
- "humanSummary": data["humanSummary"],
123
- "planning": planning,
124
- "narrative": planning.get("userNarrative", {}),
125
- "stageFigure": figure,
126
- "approval": approval,
127
- "openDecisions": [
128
- row
129
- for row in data.get("clarificationItems", [])
130
- if isinstance(row, dict)
131
- and row.get("blocks") == "approval"
132
- and row.get("status") in {"open", "answered"}
133
- ],
134
- "agentActivities": activities,
135
- "activityById": {
136
- row["activityId"]: row for row in activities if row.get("activityId")
137
- },
138
- "decisionCards": decision_cards,
139
- "evidenceIndex": evidence_index(data, ANCHORED_FIELDS),
140
- }
141
- return HumanReportView(
142
- "implementation-planning",
143
- "html/tasks/implementation-planning.template.html",
144
- context,
145
- (figure,) if figure is not None else (),
146
- anchored_fields=ANCHORED_FIELDS,
147
- )
@@ -1,195 +0,0 @@
1
- """미확정 사실의 시험 입력과 관측 결과를 연결하며 도입 승인은 만들지 않는다."""
2
-
3
- from __future__ import annotations
4
-
5
- import hashlib
6
- import re
7
- from collections.abc import Mapping
8
- from pathlib import Path
9
- from typing import Any, TypedDict
10
-
11
- from .clarification_items import USER_INPUT_BLOCKS, progress_blocking_ids
12
- from .final_report_schema import load_schema_for_data, validate
13
- from .implementation_direction import validate_task_artifact_path
14
- from .json_boundary import load_owned_object_snapshot, serialize_owned_object
15
-
16
-
17
- class VerificationFact(TypedDict):
18
- id: str
19
- candidateId: str
20
- factIndex: int
21
- fact: str
22
- whyItMatters: str
23
- evidence: list[str]
24
-
25
-
26
- class VerificationInput(TypedDict):
27
- sourceReport: str
28
- sourceDataSha256: str
29
- scope: str
30
- facts: list[VerificationFact]
31
-
32
-
33
- class TechnicalVerificationError(ValueError):
34
- """시험 입력 또는 관측 결과가 원본 사실과 일치하지 않는다."""
35
-
36
-
37
- def technical_verification_facts(data: Mapping[str, Any]) -> list[VerificationFact]:
38
- """안전 차단이 없는 후보의 명시적인 기술 검증 사실만 추출한다."""
39
- blockers = progress_blocking_ids(
40
- data.get("clarificationItems"), USER_INPUT_BLOCKS, report_data=data
41
- )
42
- if blockers:
43
- raise TechnicalVerificationError(
44
- "unresolved user decisions: " + ", ".join(blockers)
45
- )
46
- selection = data.get("implementationOptionSelection") or {}
47
- facts: list[VerificationFact] = []
48
- for candidate in selection.get("candidateAudit") or []:
49
- if candidate.get("safetyBlockers"):
50
- continue
51
- for index, fact in enumerate(candidate.get("unresolvedFeasibilityFacts") or []):
52
- if fact.get("resolutionKind") != "technical-verification":
53
- continue
54
- facts.append(
55
- {
56
- "id": f"TV-{len(facts) + 1:03d}",
57
- "candidateId": candidate["id"],
58
- "factIndex": index,
59
- "fact": fact["fact"],
60
- "whyItMatters": fact["whyItMatters"],
61
- "evidence": fact["evidence"],
62
- }
63
- )
64
- if not facts:
65
- raise TechnicalVerificationError("no eligible technical-verification facts")
66
- return facts
67
-
68
-
69
- def resolve_technical_verification_input(
70
- report: Path, project_root: Path, task_root: Path, task_key: str
71
- ) -> VerificationInput:
72
- """같은 작업의 후보 비교 기록을 시험 입력으로 고정한다."""
73
- report = report if report.is_absolute() else project_root / report
74
- validate_task_artifact_path(report, task_root, "technical verification source")
75
- expected_parent = task_root / "runs/implementation-option-selection/reports"
76
- if report.parent != expected_parent or not re.fullmatch(
77
- r"final-report-implementation-option-selection-\d{3,}\.data\.json", report.name
78
- ):
79
- raise TechnicalVerificationError(
80
- "source must be this task's option-selection record"
81
- )
82
- snapshot = load_owned_object_snapshot(
83
- report, artifact="technical verification source"
84
- )
85
- data = snapshot.value
86
- errors = validate(data, load_schema_for_data(data))
87
- if errors:
88
- raise TechnicalVerificationError("invalid source report: " + "; ".join(errors))
89
- if data["header"]["taskKey"] != task_key:
90
- raise TechnicalVerificationError(
91
- "source taskKey does not match verification task"
92
- )
93
- return {
94
- "sourceReport": report.relative_to(project_root).as_posix(),
95
- "sourceDataSha256": hashlib.sha256(snapshot.raw_bytes).hexdigest(),
96
- "scope": "technical-evidence-only",
97
- "facts": technical_verification_facts(data),
98
- }
99
-
100
-
101
- def write_technical_verification_input(
102
- payload: VerificationInput, run_root: Path, seq: str
103
- ) -> Path:
104
- """실행 순번별 입력을 저장해 후속 실행이 이전 시험 범위를 덮지 않게 한다."""
105
- path = run_root / "state" / f"technical-verification-input-{seq}.json"
106
- serialized = serialize_owned_object(path, payload, artifact="technical verification input")
107
- path.parent.mkdir(parents=True, exist_ok=True)
108
- with path.open("x", encoding="utf-8") as output:
109
- output.write(serialized)
110
- return path
111
-
112
-
113
- def _check_execution_evidence(
114
- check: Mapping[str, Any], project_root: Path, run_root: Path, seq: str
115
- ) -> list[str]:
116
- """관측 판정은 실행 기록과 실행별 시험 디렉터리를 요구한다."""
117
- errors = []
118
- commands = check.get("commands") or []
119
- if check.get("status") != "not-run" and not commands:
120
- errors.append(f"{check['id']}: an observed result requires command evidence")
121
- for command in commands:
122
- try:
123
- log = project_root / command["logPath"]
124
- experiment_root = run_root / "experiments" / seq
125
- validate_task_artifact_path(log, experiment_root, "verification log")
126
- if not log.read_text(encoding="utf-8").strip():
127
- errors.append(f"{check['id']}: verification log is empty")
128
- cwd = project_root / command["cwd"]
129
- cwd.resolve(strict=True).relative_to(experiment_root.resolve(strict=True))
130
- relative = cwd.relative_to(experiment_root)
131
- current = run_root
132
- for part in ("experiments", seq, *relative.parts):
133
- current = current / part
134
- if current.is_symlink():
135
- raise TechnicalVerificationError(
136
- "experiment cwd contains a symlink"
137
- )
138
- if not cwd.is_dir() or cwd.is_symlink():
139
- errors.append(f"{check['id']}: experiment cwd must be a directory")
140
- except (OSError, ValueError) as exc:
141
- errors.append(f"{check['id']}: invalid execution evidence: {exc}")
142
- if check.get("status") == "supported" and command.get("exitCode") != 0:
143
- errors.append(f"{check['id']}: a supported result has a failed command")
144
- return errors
145
-
146
-
147
- def validate_technical_verification_report(
148
- data: Mapping[str, Any], report_path: Path, project_root: Path
149
- ) -> list[str]:
150
- """발행·최종 검증에서 같은 사실 집합과 실제 시험 산출물을 대조한다."""
151
- if (data.get("header") or {}).get("taskType") != "technical-verification":
152
- return []
153
- match = re.fullmatch(
154
- r"final-report-technical-verification-(\d{3,})\.data\.json", report_path.name
155
- )
156
- if match is None:
157
- return ["technical verification report must use its canonical record path"]
158
- run_root = report_path.parent.parent
159
- path = run_root / "state" / f"technical-verification-input-{match[1]}.json"
160
- try:
161
- validate_task_artifact_path(path, run_root, "technical verification input")
162
- source = load_owned_object_snapshot(
163
- path, artifact="technical verification input"
164
- ).value
165
- except (OSError, ValueError) as exc:
166
- return [str(exc)]
167
- block = data.get("technicalVerification") or {}
168
- errors = []
169
- for key in ("sourceReport", "sourceDataSha256", "scope"):
170
- if block.get(key) != source.get(key):
171
- errors.append(f"technicalVerification.{key} must match the run input")
172
- expected = {fact["id"]: fact for fact in source["facts"]}
173
- checks = block.get("checks") or []
174
- ids = [check.get("id") for check in checks]
175
- if len(ids) != len(set(ids)) or set(ids) != set(expected):
176
- errors.append(
177
- "technicalVerification.checks must cover each input fact exactly once"
178
- )
179
- for check in checks:
180
- fact = expected.get(check.get("id"))
181
- if fact is None:
182
- continue
183
- for key in ("candidateId", "factIndex", "fact"):
184
- if check.get(key) != fact[key]:
185
- errors.append(f"{check['id']}: {key} must preserve the input fact")
186
- errors.extend(
187
- _check_execution_evidence(check, project_root, run_root, match[1])
188
- )
189
- if (block.get("routing") or {}).get(
190
- "nextTaskType"
191
- ) != "implementation-option-selection":
192
- errors.append(
193
- "technical verification returns only to implementation-option-selection"
194
- )
195
- return errors