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
@@ -0,0 +1,12 @@
1
+ {
2
+ "allowed": " - pre-planning context exploration notes (files/interfaces inspected, recent commits scanned, ambiguities flagged)\n - selected-direction planning: realize the one validated selected direction without reopening candidate comparison, with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n - legacy candidate-comparison planning only: at least two implementation option candidates, each with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n - legacy candidate-comparison planning only: a trade-off matrix across options (complexity, risk, reversibility, test cost, rollout cost) and recommended option with rationale tied to isolation / single-responsibility / YAGNI principles\n - bite-sized stepwise execution order for the selected direction or legacy recommended option (each step ~2-5 min, exact file paths and commands, TDD ordering when applicable, no placeholders)\n - dependency / migration risk assessment, validation checklist (pre / mid / post with exact commands), rollback strategy with revert path and trigger signal\n - every unresolved ambiguity registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under `5.5.x` — the unified table is the single home)\n - report record `frontmatter.approved: false` awaiting `--approve` or the in-session wizard\n - self-review confirmation (spec coverage, placeholder scan, internal consistency, ambiguity, scope)\n - one endStateCoverage row per brief end-state id, each mapped to the R-NNN row that carries it",
3
+ "forbidden": [
4
+ "source code edits of any kind (Edit/Write on project source files is forbidden)",
5
+ "file writes outside the run`s artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`), including task-root QA scripts, manifest, and tsconfig (planning declares conformance commands and required dependencies; implementation writes these files); in particular, do not write to `docs/superpowers/specs/` or `docs/superpowers/plans/`",
6
+ "executing builds, migrations, deployments, or any state-mutating command",
7
+ "starting `implementation` inside this run (must be a separate run authorised by an approved deliverable from this phase), even if the user says \"다음 단계 진행해\"",
8
+ "dispatching parallel sub-agents beyond the required worker roster (okstra owns worker fan-out)",
9
+ "leaving placeholders such as TBD / TODO / \"handle edge cases\" / \"similar to Option N\" in the report",
10
+ "delegating the self-review pass — the Okstra lead must run it"
11
+ ]
12
+ }
@@ -0,0 +1,161 @@
1
+ """구현 계획의 신규 진입·재실행 입력을 검증한다."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Protocol
8
+
9
+ from okstra_ctl.prepare_error import PrepareError
10
+ from okstra_ctl.incremental_scope import ReverifyScopeError, parse_user_reverify_scope
11
+ from okstra_ctl.implementation_direction import (
12
+ DirectionSelectionError,
13
+ SelectedDirection,
14
+ lexical_absolute_path,
15
+ resolve_selected_direction,
16
+ validate_task_artifact_path,
17
+ )
18
+
19
+ class PlanningInputs(Protocol):
20
+ """공통 준비 입력 중 계획 진입 정책이 읽는 필드."""
21
+
22
+ project_root: str | Path
23
+ project_id: str
24
+ task_group: str
25
+ task_id: str
26
+ task_type: str
27
+ selected_direction_path: str
28
+ clarification_response_path: str
29
+ reverify_scope: str
30
+
31
+
32
+ _PLANNING_REPORT_RE = re.compile(
33
+ r"^final-report-implementation-planning-\d{3,}\.(?:md|data\.json)$"
34
+ )
35
+
36
+
37
+ def _resolve_planning_input_path(path_value: str, project_root: Path) -> Path | None:
38
+ """Resolve cwd-first planning inputs without following symbolic links."""
39
+ raw_path = Path(path_value).expanduser()
40
+ cwd_candidate = lexical_absolute_path(raw_path)
41
+ if cwd_candidate.is_file():
42
+ return cwd_candidate
43
+ if not raw_path.is_absolute():
44
+ project_candidate = lexical_absolute_path(project_root / raw_path)
45
+ if project_candidate.is_file():
46
+ return project_candidate
47
+ return None
48
+
49
+
50
+ def _is_existing_planning_report(
51
+ path_value: str, project_root: Path, inp: PlanningInputs
52
+ ) -> bool:
53
+ from okstra_ctl.paths import task_dir
54
+
55
+ try:
56
+ report = lexical_absolute_path(Path(path_value))
57
+ task_root = lexical_absolute_path(
58
+ task_dir(project_root, inp.task_group, inp.task_id)
59
+ )
60
+ relative = report.relative_to(task_root)
61
+ except ValueError:
62
+ return False
63
+ has_planning_layout = (
64
+ _PLANNING_REPORT_RE.fullmatch(report.name) is not None
65
+ and relative.parts[:3]
66
+ == (
67
+ "runs",
68
+ "implementation-planning",
69
+ "reports",
70
+ )
71
+ and len(relative.parts) == 4
72
+ )
73
+ if not has_planning_layout:
74
+ return False
75
+ try:
76
+ validate_task_artifact_path(report, task_root, "planning report")
77
+ except DirectionSelectionError:
78
+ return False
79
+ return True
80
+
81
+
82
+ def _validate_planning_entry_inputs(project_root: Path, inp: PlanningInputs) -> None:
83
+ if inp.task_type != "implementation-planning":
84
+ if inp.selected_direction_path:
85
+ raise PrepareError(
86
+ "--selected-direction is only meaningful with --task-type "
87
+ f"implementation-planning; got {inp.task_type}"
88
+ )
89
+ return
90
+ if inp.selected_direction_path and inp.clarification_response_path:
91
+ raise PrepareError(
92
+ "implementation-planning accepts either --selected-direction for a "
93
+ "new plan or --clarification-response for a rerun, not both"
94
+ )
95
+ if inp.selected_direction_path:
96
+ return
97
+ if inp.clarification_response_path:
98
+ if not _is_existing_planning_report(
99
+ inp.clarification_response_path, project_root, inp
100
+ ):
101
+ raise PrepareError(
102
+ "implementation-planning rerun --clarification-response must point "
103
+ "to an existing implementation-planning report for the same task"
104
+ )
105
+ return
106
+ raise PrepareError(
107
+ "implementation-planning requires --selected-direction for a new plan or "
108
+ "--clarification-response to an existing implementation-planning report"
109
+ )
110
+
111
+
112
+ def _resolve_planning_direction(inp: PlanningInputs) -> SelectedDirection | None:
113
+ if inp.task_type != "implementation-planning" or not inp.selected_direction_path:
114
+ return None
115
+ from okstra_ctl.paths import task_dir
116
+
117
+ try:
118
+ report = lexical_absolute_path(Path(inp.selected_direction_path))
119
+ expected_task_root = lexical_absolute_path(
120
+ task_dir(Path(inp.project_root), inp.task_group, inp.task_id)
121
+ )
122
+ report.relative_to(expected_task_root)
123
+ except ValueError as exc:
124
+ raise PrepareError(
125
+ "selected direction report must belong to the same task"
126
+ ) from exc
127
+ try:
128
+ validate_task_artifact_path(
129
+ report, expected_task_root, "selected direction report"
130
+ )
131
+ return resolve_selected_direction(
132
+ report,
133
+ expected_task_key=(f"{inp.project_id}:{inp.task_group}:{inp.task_id}"),
134
+ )
135
+ except DirectionSelectionError as exc:
136
+ raise PrepareError(str(exc)) from exc
137
+
138
+
139
+ def _validate_reverify_scope(inp: PlanningInputs) -> None:
140
+ """A pinned re-verification scope is only actionable on a planning re-run.
141
+
142
+ Every other phase renders the tokens too (the template always reads them),
143
+ but nothing consumes them there — so a value outside the one phase that
144
+ acts on it is a caller mistake, not a preference to honour silently.
145
+ """
146
+ if not (inp.reverify_scope or "").strip():
147
+ return
148
+ if inp.task_type != "implementation-planning":
149
+ raise PrepareError(
150
+ "--reverify-scope is only meaningful with --task-type "
151
+ f"implementation-planning; got {inp.task_type}"
152
+ )
153
+ if not inp.clarification_response_path:
154
+ raise PrepareError(
155
+ "--reverify-scope needs --clarification-response: there is no prior "
156
+ "report to narrow re-verification against"
157
+ )
158
+ try:
159
+ parse_user_reverify_scope(inp.reverify_scope)
160
+ except ReverifyScopeError as exc:
161
+ raise PrepareError(str(exc)) from exc
@@ -0,0 +1,178 @@
1
+ """구현 계획의 의존·결정 기록과 사용자 승인 안내 규칙."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from okstra_ctl.clarification_items import APPROVAL_BLOCKS, progress_blocking_ids
8
+ from okstra_ctl.plan_approval import plan_is_approved
9
+
10
+
11
+ def _validate_implementation_planning_cross_project(
12
+ data: dict, failures: list[str]
13
+ ) -> None:
14
+ """타 프로젝트 의존을 DM 행(`kind == 'cross-project'`)으로 선언했다면
15
+ `crossProjectDependencies` 에 `direction == 'upstream-precondition'` 행이
16
+ 반드시 있어야 한다 — 실제 선행 필수 의존이 soft `recommendedNextSteps`
17
+ 추천으로 새어나가지 못하게 강제한다. 각 XP 행의 필드 비-빈은 스키마가
18
+ 보장하고, 이 검사는 cross-project DM 신호가 있을 때 XP 행이 *존재*하는지를 본다.
19
+ """
20
+ ip = data.get("implementationPlanning")
21
+ if not isinstance(ip, dict):
22
+ return
23
+ dm_rows = ip.get("dependencyMigrationRisk") or []
24
+ has_cross_project_dm = any(
25
+ isinstance(r, dict) and r.get("kind") == "cross-project" for r in dm_rows
26
+ )
27
+ if not has_cross_project_dm:
28
+ return
29
+ xp_rows = ip.get("crossProjectDependencies") or []
30
+ has_upstream = any(
31
+ isinstance(r, dict) and r.get("direction") == "upstream-precondition"
32
+ for r in xp_rows
33
+ )
34
+ if not has_upstream:
35
+ failures.append(
36
+ "final-report data.json: a dependencyMigrationRisk row has "
37
+ "kind='cross-project' but crossProjectDependencies carries no "
38
+ "direction='upstream-precondition' entry. Record the cross-project "
39
+ "dependency as a mandatory precondition (concrete requiredWork / "
40
+ "verificationSignal / howToStart) — not a soft Recommended Next Step."
41
+ )
42
+
43
+
44
+ def _validate_implementation_planning_decision_drafts(
45
+ data: dict, failures: list[str]
46
+ ) -> None:
47
+ """`decisionDrafts` 가 비어있지 않으면 어느 stage 의 stepwiseExecution step 이
48
+ `.okstra/decisions/` 파일을 생성하는 materialization step 을 포함해야 한다.
49
+ 프로파일 Decision-record evaluation 의 "approved plan stepwise MUST include
50
+ Create .okstra/decisions/<NNNN>-<slug>.md" 를 실제 강제한다. draft 존재 자체가
51
+ trigger 이므로 self-contained 하다.
52
+ """
53
+ ip = data.get("implementationPlanning")
54
+ if not isinstance(ip, dict):
55
+ return
56
+ if not (ip.get("decisionDrafts") or []):
57
+ return
58
+ has_materialization = any(
59
+ ".okstra/decisions/" in (step.get("files") or "")
60
+ or ".okstra/decisions/" in (step.get("action") or "")
61
+ for stage in (ip.get("stages") or [])
62
+ if isinstance(stage, dict)
63
+ for step in (stage.get("stepwiseExecution") or [])
64
+ if isinstance(step, dict)
65
+ )
66
+ if not has_materialization:
67
+ failures.append(
68
+ "final-report data.json: implementationPlanning.decisionDrafts is "
69
+ "non-empty but no stage's stepwiseExecution creates a "
70
+ "`.okstra/decisions/<NNNN>-<slug>.md` file. The approved plan must "
71
+ "materialize each decision draft via a stepwise step (profile "
72
+ "Decision-record evaluation)."
73
+ )
74
+
75
+
76
+ _RERUN_FLAG = "--answered-clarifications"
77
+ _USER_RESPONSE_HINT = re.compile(r"okstra-user-response", re.IGNORECASE)
78
+ _APPROVE_HINT = re.compile(r"--approve|\bapprov", re.IGNORECASE)
79
+
80
+
81
+ def _next_step_texts(steps: object) -> list[str]:
82
+ """Every reader-visible string in `recommendedNextSteps`, prose and command."""
83
+ texts: list[str] = []
84
+ for step in steps if isinstance(steps, list) else []:
85
+ if not isinstance(step, dict):
86
+ continue
87
+ texts.append(str(step.get("text") or ""))
88
+ for command in step.get("commands") or []:
89
+ if isinstance(command, dict):
90
+ texts.append(str(command.get("claudeCode") or ""))
91
+ texts.append(str(command.get("terminal") or ""))
92
+ return texts
93
+
94
+
95
+ def _has_unresolved_approval_blocker(data: dict) -> bool:
96
+ return bool(
97
+ progress_blocking_ids(
98
+ data.get("clarificationItems"),
99
+ APPROVAL_BLOCKS,
100
+ report_data=data,
101
+ )
102
+ )
103
+
104
+
105
+ def _planning_gate_blocks_approval(data: dict) -> bool:
106
+ planning = data.get("implementationPlanning")
107
+ if not isinstance(planning, dict):
108
+ return False
109
+ verification = planning.get("planBodyVerification")
110
+ if not isinstance(verification, dict):
111
+ return False
112
+ gate = str(verification.get("gateResult") or "").strip().lower()
113
+ if gate == "aborted-non-result":
114
+ return True
115
+ if gate != "blocked-by-disagreement":
116
+ return False
117
+ return _has_unresolved_approval_blocker(data) or not any(
118
+ isinstance(row, dict) and row.get("blocks") == "approval"
119
+ for row in data.get("clarificationItems") or []
120
+ )
121
+
122
+
123
+ def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
124
+ """A report that withholds approval must say how to come back from it.
125
+
126
+ The way forward — answer the blockers, then re-run carrying those ids —
127
+ lived only in the lead prompt, which is read *after* the next run has
128
+ already started. The person who has to act reads the report instead, and it
129
+ told them nothing about the next command. Requiring the flag by name is a
130
+ low bar deliberately: it does not check that the rest of the step is right,
131
+ only that the report stops leaving the reader to work the mechanics out.
132
+ """
133
+ if (data.get("header") or {}).get("taskType") != "implementation-planning":
134
+ return
135
+ if not _has_unresolved_approval_blocker(data):
136
+ return
137
+ texts = _next_step_texts(data.get("recommendedNextSteps"))
138
+ if any(_RERUN_FLAG in text for text in texts) and any(
139
+ _USER_RESPONSE_HINT.search(text) for text in texts
140
+ ):
141
+ return
142
+ failures.append(
143
+ "final-report data.json: this plan withholds approval on an unresolved "
144
+ "`blocks: approval` clarification, but no `recommendedNextSteps` entry "
145
+ "tells the reader the command to run now — name `/okstra-user-response` "
146
+ f"and the `{_RERUN_FLAG}` re-run in a step's `text` or one of its "
147
+ "`commands`. `okstra recap assemble` prints the exact ids and flag "
148
+ "value once the answers are recorded."
149
+ )
150
+
151
+
152
+ def _validate_approval_guidance(data: dict, failures: list[str]) -> None:
153
+ """승인 가능한 plan-ready 는 사용자에게 승인하라고 말해야 한다.
154
+
155
+ 포인터가 implementation/ready 여도 승인은 사용자만 뒤집는다. 다음 단계
156
+ 안내가 계획 재실행이면 승인 칸을 건너뛰고 같은 단계를 다시 돈다.
157
+ """
158
+ if (data.get("header") or {}).get("taskType") != "implementation-planning":
159
+ return
160
+ planning = data.get("implementationPlanning")
161
+ if not isinstance(planning, dict) or planning.get("outcome") != "plan-ready":
162
+ return
163
+ if _has_unresolved_approval_blocker(data) or _planning_gate_blocks_approval(data):
164
+ return
165
+ if plan_is_approved(data):
166
+ return
167
+ if any(
168
+ _APPROVE_HINT.search(text)
169
+ for text in _next_step_texts(data.get("recommendedNextSteps"))
170
+ ):
171
+ return
172
+ failures.append(
173
+ "final-report data.json: this plan is ready for the user to approve, "
174
+ "but no `recommendedNextSteps` entry tells the reader to approve — "
175
+ "name `--approve` or the in-session wizard in a step's `text` or "
176
+ "one of its `commands`. Do not recommend another "
177
+ "implementation-planning run."
178
+ )