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.
- package/README.md +2 -2
- package/dist/commands/lifecycle/install.mjs +2 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture.md +16 -16
- package/docs/cli.md +4 -4
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +45 -21
- package/package.json +2 -3
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +3 -3
- package/runtime/prompts/lead/okstra-lead-contract.md +14 -55
- package/runtime/prompts/lead/phase-routing.md +82 -0
- package/runtime/prompts/lead/report-writer.md +2 -2
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +10 -1
- package/runtime/python/okstra_ctl/analysis_inputs.py +0 -39
- package/runtime/python/okstra_ctl/analysis_scope.py +31 -0
- package/runtime/python/okstra_ctl/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/contract_graph.py +75 -10
- package/runtime/python/okstra_ctl/dispatch_state.py +22 -0
- package/runtime/python/okstra_ctl/doctor.py +15 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +27 -1
- package/runtime/python/okstra_ctl/handoff.py +11 -466
- package/runtime/python/okstra_ctl/handoff_error.py +5 -0
- package/runtime/python/okstra_ctl/implementation_direction.py +9 -485
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
- package/runtime/python/okstra_ctl/next_phase.py +2 -2
- package/runtime/python/okstra_ctl/option_comparison.py +3 -165
- package/runtime/python/okstra_ctl/option_votes.py +3 -191
- package/runtime/python/okstra_ctl/paths.py +22 -6
- package/runtime/python/okstra_ctl/phases/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/catalog.py +260 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/boundary.json +11 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/entry.py +39 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/change_impact_analysis.py → phases/change_impact_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/spec.md +26 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/validation.py +23 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/boundary.json +9 -0
- package/runtime/{prompts/profiles/error-analysis.md → python/okstra_ctl/phases/error_analysis/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/error_analysis.py → phases/error_analysis/report.py} +9 -8
- package/runtime/{templates/reports → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis-input.template.md +1 -1
- package/{docs/task-process/error-analysis.md → runtime/python/okstra_ctl/phases/error_analysis/spec.md} +22 -7
- package/runtime/python/okstra_ctl/phases/error_analysis/validation.py +241 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/entry.py +63 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/feature_analysis.py → phases/feature_analysis/report.py} +12 -5
- package/runtime/python/okstra_ctl/phases/feature_analysis/spec.md +22 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/validation.py +27 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/wizard.py +95 -0
- package/runtime/python/okstra_ctl/phases/final_verification/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/entry.py +166 -0
- package/runtime/{prompts/profiles/final-verification.md → python/okstra_ctl/phases/final_verification/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/final_verification.py → phases/final_verification/report.py} +12 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/{docs/task-process/final-verification.md → runtime/python/okstra_ctl/phases/final_verification/spec.md} +42 -25
- package/runtime/python/okstra_ctl/phases/final_verification/target.py +296 -0
- package/runtime/python/okstra_ctl/phases/final_verification/validation.py +190 -0
- package/runtime/python/okstra_ctl/phases/final_verification/wizard.py +38 -0
- package/runtime/python/okstra_ctl/phases/implementation/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/boundary.json +17 -0
- package/runtime/python/okstra_ctl/{implementation_stage.py → phases/implementation/entry.py} +22 -10
- package/runtime/{prompts/host-orchestration/implementation.md → python/okstra_ctl/phases/implementation/host-rules.md} +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-deliverable.md +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-executor.md +4 -3
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-verifier.md +4 -4
- package/runtime/{prompts/profiles/implementation.md → python/okstra_ctl/phases/implementation/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation.py → phases/implementation/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation/report_assets}/implementation-input.template.md +1 -1
- package/{docs/task-process/implementation.md → runtime/python/okstra_ctl/phases/implementation/spec.md} +20 -8
- package/runtime/python/okstra_ctl/phases/implementation/validation.py +205 -0
- package/runtime/python/okstra_ctl/phases/implementation/wizard.py +39 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +80 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +10 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/comparison.py +168 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +27 -0
- package/runtime/{prompts/profiles/implementation-option-selection.md → python/okstra_ctl/phases/implementation_option_selection/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/{docs/task-process/implementation-option-selection.md → runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md} +20 -7
- package/runtime/python/okstra_ctl/{implementation_options.py → phases/implementation_option_selection/validation.py} +3 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/votes.py +194 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +2345 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +12 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/entry.py +161 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/guidance.py +178 -0
- package/runtime/{prompts/lead → python/okstra_ctl/phases/implementation_planning/instructions}/plan-body-verification.md +61 -51
- package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +3295 -0
- package/runtime/{prompts/profiles/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/profile.md} +74 -25
- package/runtime/python/okstra_ctl/phases/implementation_planning/report.py +237 -0
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning-input.template.md +2 -2
- package/{docs/task-process/implementation-planning.md → runtime/python/okstra_ctl/phases/implementation_planning/spec.md} +30 -6
- package/runtime/python/okstra_ctl/phases/implementation_planning/validation.py +597 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/wizard.py +166 -0
- package/runtime/python/okstra_ctl/phases/improvement_discovery/boundary.json +12 -0
- package/runtime/python/okstra_ctl/{improvement_lenses.py → phases/improvement_discovery/lenses.py} +1 -6
- package/runtime/{prompts/profiles/improvement-discovery.md → python/okstra_ctl/phases/improvement_discovery/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/improvement_discovery.py → phases/improvement_discovery/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery-input.template.md +1 -2
- package/runtime/python/okstra_ctl/phases/improvement_discovery/spec.md +29 -0
- package/runtime/{validators/validate_improvement_report.py → python/okstra_ctl/phases/improvement_discovery/validation.py} +5 -14
- package/runtime/python/okstra_ctl/phases/project_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/entry.py +11 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/project_analysis.py → phases/project_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/project_analysis/spec.md +33 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/validation.py +55 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +17 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/entry.py +147 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +446 -0
- package/runtime/{prompts/profiles/release-handoff.md → python/okstra_ctl/phases/release_handoff/profile.md} +3 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/release_handoff.py → phases/release_handoff/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff-input.template.md +1 -1
- package/{docs/task-process/release-handoff.md → runtime/python/okstra_ctl/phases/release_handoff/spec.md} +22 -9
- package/runtime/python/okstra_ctl/phases/release_handoff/wizard.py +84 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/boundary.json +9 -0
- package/runtime/{prompts/profiles/requirements-discovery.md → python/okstra_ctl/phases/requirements_discovery/profile.md} +2 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/requirements_discovery.py → phases/requirements_discovery/report.py} +3 -3
- package/{docs/task-process/requirements-discovery.md → runtime/python/okstra_ctl/phases/requirements_discovery/spec.md} +25 -6
- package/runtime/{validators/validate_fanout.py → python/okstra_ctl/phases/requirements_discovery/validation.py} +11 -12
- package/runtime/python/okstra_ctl/phases/technical_verification/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/boundary.json +9 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +100 -0
- package/runtime/{prompts/profiles/technical-verification.md → python/okstra_ctl/phases/technical_verification/profile.md} +1 -1
- package/runtime/python/okstra_ctl/{report_html/view_models/technical_verification.py → phases/technical_verification/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/technical_verification/spec.md +37 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +90 -0
- package/runtime/python/okstra_ctl/plan_approval.py +70 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +2 -2130
- package/runtime/python/okstra_ctl/profile_show.py +9 -3
- package/runtime/python/okstra_ctl/render.py +9 -2
- package/runtime/python/okstra_ctl/render_final_report.py +3 -2
- package/runtime/python/okstra_ctl/report_assembly.py +14 -93
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_html/router.py +9 -33
- package/runtime/python/okstra_ctl/report_projections.py +1 -36
- package/runtime/python/okstra_ctl/report_routing.py +23 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +4 -73
- package/runtime/python/okstra_ctl/report_template_loader.py +35 -0
- package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +18 -2
- package/runtime/python/okstra_ctl/run.py +113 -503
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/stage_targets.py +9 -286
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- package/runtime/python/okstra_ctl/user_response.py +199 -4
- package/runtime/python/okstra_ctl/verification_target.py +1 -1
- package/runtime/python/okstra_ctl/wizard/__init__.py +31 -31
- package/runtime/python/okstra_ctl/wizard/api.py +18 -0
- package/runtime/python/okstra_ctl/wizard/outcome.py +3 -12
- package/runtime/python/okstra_ctl/wizard/registry.py +20 -12
- package/runtime/python/okstra_ctl/wizard/state.py +6 -2
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +27 -278
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +2 -1
- package/runtime/python/okstra_ctl/work_categories.py +1 -1
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +23 -4
- package/runtime/templates/reports/quick-input.template.md +1 -1
- package/runtime/templates/reports/task-brief.template.md +1 -1
- package/runtime/validators/validate-brief.py +2 -2
- package/runtime/validators/validate-run.py +287 -4091
- package/runtime/validators/validate_analysis_report.py +14 -126
- package/docs/task-process/README.md +0 -82
- package/docs/task-process/common-flow.md +0 -173
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +0 -147
- package/runtime/python/okstra_ctl/technical_verification.py +0 -195
- /package/runtime/{prompts/profiles/change-impact-analysis.json → python/okstra_ctl/phases/change_impact_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/change-impact-analysis.md → python/okstra_ctl/phases/change_impact_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/error-analysis.json → python/okstra_ctl/phases/error_analysis/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.json → python/okstra_ctl/phases/feature_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.md → python/okstra_ctl/phases/feature_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/final-verification.json → python/okstra_ctl/phases/final_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.template.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-diff-review.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-self-check.md +0 -0
- /package/runtime/{prompts/profiles/implementation.json → python/okstra_ctl/phases/implementation/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.md +0 -0
- /package/runtime/{prompts/profiles/implementation-option-selection.json → python/okstra_ctl/phases/implementation_option_selection/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.md +0 -0
- /package/runtime/{prompts/host-orchestration/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/host-rules.md} +0 -0
- /package/runtime/{prompts/profiles/implementation-planning.json → python/okstra_ctl/phases/implementation_planning/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.md +0 -0
- /package/runtime/{prompts/profiles/improvement-discovery.json → python/okstra_ctl/phases/improvement_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/project-analysis.json → python/okstra_ctl/phases/project_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/project-analysis.md → python/okstra_ctl/phases/project_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/release-handoff.json → python/okstra_ctl/phases/release_handoff/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.md +0 -0
- /package/runtime/python/okstra_ctl/{fanout.py → phases/requirements_discovery/fanout.py} +0 -0
- /package/runtime/{prompts/profiles/requirements-discovery.json → python/okstra_ctl/phases/requirements_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/requirements_discovery/report_assets}/fan-out-unit.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/technical-verification.json → python/okstra_ctl/phases/technical_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.md +0 -0
|
@@ -641,3 +641,16 @@ def _resolve_plan_path(task_root: Path, value: str) -> Path:
|
|
|
641
641
|
# 예전에는 okstra_project.state 가 이 어댑터를 들고 okstra_ctl 을 함수 본문에서
|
|
642
642
|
# 지연 import 했다. 하위 계층이 상위를 되짚는 배치였다. 읽는 대상이 okstra_ctl 의
|
|
643
643
|
# Stage Map 과 consumer 상태이므로 소유자를 여기로 옮긴다.
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def parse_stage_map_for_prepare(plan_path: str) -> list:
|
|
647
|
+
"""Parse the approved plan into context records for execution consumers."""
|
|
648
|
+
from .prepare_error import PrepareError
|
|
649
|
+
|
|
650
|
+
try:
|
|
651
|
+
return stage_map_records(parse_stage_map_file(Path(plan_path)))
|
|
652
|
+
except StageMapError as exc:
|
|
653
|
+
raise PrepareError(
|
|
654
|
+
"approved-plan 의 Stage Map 을 신뢰할 수 없어 거부합니다 "
|
|
655
|
+
f"({plan_path}): {exc.reason}. plan 의 Stage Map 을 점검하세요."
|
|
656
|
+
) from exc
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"""Stage readiness and verification target rules.
|
|
2
2
|
|
|
3
|
-
This module owns the policy that decides which Stage Map stage can run
|
|
4
|
-
commit it branches from, and
|
|
3
|
+
This module owns the policy that decides which Stage Map stage can run and which
|
|
4
|
+
commit it branches from, plus the Git and ledger facts final-verification builds
|
|
5
|
+
its target from (whole-task integration, the stage that contains every other
|
|
6
|
+
done stage, teardown after the verdict). Which of those facts a
|
|
7
|
+
final-verification run uses is decided in
|
|
8
|
+
``phases/final_verification/target.py``. It keeps
|
|
5
9
|
the stage lifecycle rules behind one interface instead of leaking raw
|
|
6
10
|
``consumers.jsonl`` rows and git ancestry checks into prepare callers.
|
|
7
11
|
"""
|
|
@@ -10,13 +14,11 @@ from __future__ import annotations
|
|
|
10
14
|
import heapq
|
|
11
15
|
import subprocess
|
|
12
16
|
import sys
|
|
13
|
-
from dataclasses import dataclass
|
|
17
|
+
from dataclasses import dataclass
|
|
14
18
|
from pathlib import Path
|
|
15
19
|
from typing import Any
|
|
16
20
|
|
|
17
|
-
from .plan_run_root import plan_run_root_from_approved_plan
|
|
18
21
|
from .prepare_error import PrepareError # noqa: F401 — 재노출(단일 참조점)
|
|
19
|
-
from .stage_integrate import IntegrateResult
|
|
20
22
|
from okstra_project.slug import slugify
|
|
21
23
|
|
|
22
24
|
|
|
@@ -37,32 +39,6 @@ class FinalVerificationTarget:
|
|
|
37
39
|
reports: list[str]
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
@dataclass(frozen=True)
|
|
41
|
-
class FinalVerificationTargetRequest:
|
|
42
|
-
"""Semantic inputs needed to acquire one final-verification target."""
|
|
43
|
-
|
|
44
|
-
project_root: Path
|
|
45
|
-
project_id: str
|
|
46
|
-
task_group: str
|
|
47
|
-
task_id: str
|
|
48
|
-
work_category: str
|
|
49
|
-
approved_plan_path: Path
|
|
50
|
-
stage: int | None
|
|
51
|
-
stage_map: tuple[dict[str, Any], ...]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@dataclass(frozen=True)
|
|
55
|
-
class FinalVerificationTargetAcquisition:
|
|
56
|
-
"""Resolved target facts returned to the prepare adapter."""
|
|
57
|
-
|
|
58
|
-
target: FinalVerificationTarget
|
|
59
|
-
worktree_branch: str
|
|
60
|
-
integration_result: IntegrateResult | None
|
|
61
|
-
# whole-task 진입이 task worktree 안에서 꺼낸 중첩 stage worktree.
|
|
62
|
-
# `{"stage": N, "from": <old path>, "to": <new path>}` 행.
|
|
63
|
-
relocations: tuple[dict[str, Any], ...] = ()
|
|
64
|
-
|
|
65
|
-
|
|
66
42
|
@dataclass(frozen=True)
|
|
67
43
|
class StageLifecycle:
|
|
68
44
|
stage: int
|
|
@@ -809,117 +785,6 @@ def resolve_and_integrate_whole_task(
|
|
|
809
785
|
)
|
|
810
786
|
|
|
811
787
|
|
|
812
|
-
def _resolve_single_stage_target(
|
|
813
|
-
*,
|
|
814
|
-
requested_stage: int,
|
|
815
|
-
done_rows: list[dict[str, Any]],
|
|
816
|
-
stage_base: str,
|
|
817
|
-
stage_worktree_path: str,
|
|
818
|
-
stage_head: str,
|
|
819
|
-
stage_dirty: bool,
|
|
820
|
-
) -> FinalVerificationTarget:
|
|
821
|
-
"""Resolve single-stage final-verification target, enforcing all gates."""
|
|
822
|
-
from .consumers import latest_done_by_stage
|
|
823
|
-
|
|
824
|
-
n = requested_stage
|
|
825
|
-
done_by_stage = latest_done_by_stage(done_rows)
|
|
826
|
-
if n not in done_by_stage:
|
|
827
|
-
raise StageTargetError(
|
|
828
|
-
f"final-verification(single-stage): stage {n} not done — "
|
|
829
|
-
f"run implementation --stage {n} first"
|
|
830
|
-
)
|
|
831
|
-
if not stage_worktree_path:
|
|
832
|
-
raise StageTargetError(
|
|
833
|
-
f"final-verification(single-stage): stage worktree not found for "
|
|
834
|
-
f"stage {n} (torn down?) — use whole-task mode (--stage auto)"
|
|
835
|
-
)
|
|
836
|
-
if stage_dirty:
|
|
837
|
-
raise StageTargetError(
|
|
838
|
-
"final-verification: worktree has uncommitted source changes "
|
|
839
|
-
"(outside .okstra/) — commit or stash before verifying"
|
|
840
|
-
)
|
|
841
|
-
return FinalVerificationTarget(
|
|
842
|
-
scope="single-stage",
|
|
843
|
-
base=stage_base,
|
|
844
|
-
head=stage_head,
|
|
845
|
-
worktree_path=stage_worktree_path,
|
|
846
|
-
stages=[n],
|
|
847
|
-
reports=[done_by_stage[n].get("report_path", "")],
|
|
848
|
-
)
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
def _read_final_verification_done_rows(
|
|
852
|
-
request: FinalVerificationTargetRequest,
|
|
853
|
-
registry_coordinates: tuple[str, str, str],
|
|
854
|
-
) -> list[dict[str, Any]]:
|
|
855
|
-
from .consumers import backfill_done_from_carry, read_stage_consumer_state
|
|
856
|
-
from .stage_reconcile import auto_reconcile_best_effort
|
|
857
|
-
|
|
858
|
-
plan_run_root = plan_run_root_from_approved_plan(request.approved_plan_path)
|
|
859
|
-
backfill_done_from_carry(plan_run_root)
|
|
860
|
-
project_id, task_group, task_id = registry_coordinates
|
|
861
|
-
auto_reconcile_best_effort(
|
|
862
|
-
replace(
|
|
863
|
-
request,
|
|
864
|
-
project_id=project_id,
|
|
865
|
-
task_group=task_group,
|
|
866
|
-
task_id=task_id,
|
|
867
|
-
),
|
|
868
|
-
plan_run_root,
|
|
869
|
-
)
|
|
870
|
-
return read_stage_consumer_state(plan_run_root).done_rows
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
def _final_verification_registry_coordinates(
|
|
874
|
-
request: FinalVerificationTargetRequest,
|
|
875
|
-
) -> tuple[str, str, str]:
|
|
876
|
-
|
|
877
|
-
def segment(value: str) -> str:
|
|
878
|
-
return slugify(value) or "_"
|
|
879
|
-
|
|
880
|
-
return (
|
|
881
|
-
segment(request.project_id),
|
|
882
|
-
segment(request.task_group),
|
|
883
|
-
segment(request.task_id),
|
|
884
|
-
)
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
def _acquire_single_stage_target(
|
|
888
|
-
request: FinalVerificationTargetRequest,
|
|
889
|
-
done_rows: list[dict[str, Any]],
|
|
890
|
-
registry_coordinates: tuple[str, str, str],
|
|
891
|
-
) -> FinalVerificationTargetAcquisition:
|
|
892
|
-
from . import worktree_registry
|
|
893
|
-
from .worktree import _git, is_dirty_excluding_okstra
|
|
894
|
-
|
|
895
|
-
stage = request.stage
|
|
896
|
-
assert stage is not None
|
|
897
|
-
row = worktree_registry.get_stage_row(*registry_coordinates, stage)
|
|
898
|
-
worktree_path = (row or {}).get("worktree_path", "")
|
|
899
|
-
head = ""
|
|
900
|
-
if worktree_path and Path(worktree_path).is_dir():
|
|
901
|
-
head_result = _git(worktree_path, "rev-parse", "HEAD")
|
|
902
|
-
if head_result.returncode == 0:
|
|
903
|
-
head = head_result.stdout.strip()
|
|
904
|
-
if not head:
|
|
905
|
-
worktree_path = ""
|
|
906
|
-
target = _resolve_single_stage_target(
|
|
907
|
-
requested_stage=stage,
|
|
908
|
-
done_rows=done_rows,
|
|
909
|
-
stage_base=(row or {}).get("base_ref", ""),
|
|
910
|
-
stage_worktree_path=worktree_path,
|
|
911
|
-
stage_head=head,
|
|
912
|
-
stage_dirty=(
|
|
913
|
-
is_dirty_excluding_okstra(worktree_path) if worktree_path else False
|
|
914
|
-
),
|
|
915
|
-
)
|
|
916
|
-
return FinalVerificationTargetAcquisition(
|
|
917
|
-
target=target,
|
|
918
|
-
worktree_branch=(row or {}).get("branch", ""),
|
|
919
|
-
integration_result=None,
|
|
920
|
-
)
|
|
921
|
-
|
|
922
|
-
|
|
923
788
|
def single_containing_head(project_root: Path,
|
|
924
789
|
heads: dict[int, str]) -> int | None:
|
|
925
790
|
"""주어진 stage 들의 commit 을 혼자서 모두 담고 있는 stage. 없으면 None.
|
|
@@ -952,117 +817,12 @@ def containing_stage(project_root: Path,
|
|
|
952
817
|
return single_containing_head(project_root, heads)
|
|
953
818
|
|
|
954
819
|
|
|
955
|
-
def _acquire_tip_stage_target(
|
|
956
|
-
request: FinalVerificationTargetRequest,
|
|
957
|
-
done_rows: list[dict[str, Any]],
|
|
958
|
-
registry_coordinates: tuple[str, str, str],
|
|
959
|
-
) -> FinalVerificationTargetAcquisition | None:
|
|
960
|
-
"""전체를 담은 단일 stage 브랜치가 있으면 그것을 whole-task 검증 대상으로
|
|
961
|
-
삼는다 — 머지 없이. 없거나 그 워크트리가 쓸 수 없으면 None 을 돌려 기존
|
|
962
|
-
task 브랜치 통합 경로로 넘긴다."""
|
|
963
|
-
from . import worktree_registry
|
|
964
|
-
from .consumers import latest_done_by_stage
|
|
965
|
-
from .worktree import _git, is_dirty_excluding_okstra
|
|
966
|
-
|
|
967
|
-
done = latest_done_by_stage(done_rows)
|
|
968
|
-
planned = [int(s["stage_number"]) for s in request.stage_map]
|
|
969
|
-
if any(n not in done for n in planned):
|
|
970
|
-
return None # 미완 stage 의 거부 메시지는 기존 경로가 낸다
|
|
971
|
-
tip = containing_stage(request.project_root,
|
|
972
|
-
{n: done[n] for n in planned})
|
|
973
|
-
if tip is None:
|
|
974
|
-
return None
|
|
975
|
-
row = worktree_registry.get_stage_row(*registry_coordinates, tip) or {}
|
|
976
|
-
worktree_path = row.get("worktree_path", "")
|
|
977
|
-
if not worktree_path or not Path(worktree_path).is_dir():
|
|
978
|
-
return None
|
|
979
|
-
head_result = _git(worktree_path, "rev-parse", "HEAD")
|
|
980
|
-
if head_result.returncode != 0:
|
|
981
|
-
return None
|
|
982
|
-
head = head_result.stdout.strip()
|
|
983
|
-
if head != done[tip].get("head_commit", ""):
|
|
984
|
-
# 브랜치 tip 이 done 기록과 갈라져 있다. 검증 대상이 무엇인지 모호하므로
|
|
985
|
-
# 판정을 기존 경로로 넘긴다.
|
|
986
|
-
return None
|
|
987
|
-
if is_dirty_excluding_okstra(worktree_path):
|
|
988
|
-
raise StageTargetError(
|
|
989
|
-
"final-verification: worktree has uncommitted source changes "
|
|
990
|
-
"(outside .okstra/) — commit or stash before verifying"
|
|
991
|
-
)
|
|
992
|
-
anchor_base = worktree_registry.get_implementation_base(
|
|
993
|
-
*registry_coordinates) or ""
|
|
994
|
-
stages = sorted(planned)
|
|
995
|
-
return FinalVerificationTargetAcquisition(
|
|
996
|
-
target=FinalVerificationTarget(
|
|
997
|
-
scope="whole-task",
|
|
998
|
-
base=anchor_base,
|
|
999
|
-
head=head,
|
|
1000
|
-
worktree_path=worktree_path,
|
|
1001
|
-
stages=stages,
|
|
1002
|
-
reports=[done[n].get("report_path", "") for n in stages],
|
|
1003
|
-
),
|
|
1004
|
-
relocations=(),
|
|
1005
|
-
worktree_branch=row.get("branch", ""),
|
|
1006
|
-
integration_result=None,
|
|
1007
|
-
)
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
def _acquire_whole_task_target(
|
|
1011
|
-
request: FinalVerificationTargetRequest,
|
|
1012
|
-
done_rows: list[dict[str, Any]],
|
|
1013
|
-
registry_coordinates: tuple[str, str, str],
|
|
1014
|
-
) -> FinalVerificationTargetAcquisition:
|
|
1015
|
-
from . import worktree_registry
|
|
1016
|
-
|
|
1017
|
-
tip = _acquire_tip_stage_target(request, done_rows, registry_coordinates)
|
|
1018
|
-
if tip is not None:
|
|
1019
|
-
return tip
|
|
1020
|
-
|
|
1021
|
-
project_id, task_group, task_id = registry_coordinates
|
|
1022
|
-
entry = worktree_registry.lookup(project_id, task_group, task_id)
|
|
1023
|
-
worktree_path = (
|
|
1024
|
-
entry.worktree_path if entry is not None else str(request.project_root)
|
|
1025
|
-
)
|
|
1026
|
-
relocations = (
|
|
1027
|
-
relocate_nested_stage_worktrees(
|
|
1028
|
-
project_id, task_group, task_id, worktree_path,
|
|
1029
|
-
stages=[int(row["stage_number"]) for row in request.stage_map],
|
|
1030
|
-
)
|
|
1031
|
-
if entry is not None
|
|
1032
|
-
else []
|
|
1033
|
-
)
|
|
1034
|
-
whole = _resolve_and_integrate_whole_task_unlocked(
|
|
1035
|
-
project_id=project_id,
|
|
1036
|
-
task_group=task_group,
|
|
1037
|
-
task_id=task_id,
|
|
1038
|
-
task_worktree_path=worktree_path,
|
|
1039
|
-
stage_map=list(request.stage_map),
|
|
1040
|
-
done_rows=done_rows,
|
|
1041
|
-
anchor_base=(
|
|
1042
|
-
worktree_registry.get_implementation_base(
|
|
1043
|
-
project_id, task_group, task_id
|
|
1044
|
-
)
|
|
1045
|
-
or ""
|
|
1046
|
-
),
|
|
1047
|
-
# 정리는 판정 뒤로 미룬다(Phase 7 `teardown-stages`). 되돌릴 수 없는 정리를
|
|
1048
|
-
# 판정 앞에 두면, 재작업이 가장 필요한 blocked 판정에서 stage 작업물이 이미
|
|
1049
|
-
# 사라져 있다. 여기서는 통합만 하고 worktree/registry 키는 남긴다.
|
|
1050
|
-
teardown=False,
|
|
1051
|
-
)
|
|
1052
|
-
return FinalVerificationTargetAcquisition(
|
|
1053
|
-
target=whole["target"],
|
|
1054
|
-
relocations=tuple(relocations),
|
|
1055
|
-
worktree_branch=entry.branch if entry is not None else "",
|
|
1056
|
-
integration_result=whole["integrate_result"],
|
|
1057
|
-
)
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
820
|
def integrate_and_teardown_whole_task(
|
|
1061
821
|
*, project_root: Path, task_group: str, task_id: str,
|
|
1062
822
|
) -> dict[str, Any]:
|
|
1063
823
|
"""판정이 끝난 whole-task 검증의 stage worktree 와 registry 키를 회수한다.
|
|
1064
824
|
|
|
1065
|
-
진입은 통합만 하고 정리를 남겨 두므로(`
|
|
825
|
+
진입은 통합만 하고 정리를 남겨 두므로(`phases/final_verification/target.py`), 정리는
|
|
1066
826
|
판정 뒤인 Phase 7 에서 여기로 들어온다. 통합은 이미 끝나 있어 Phase A 는 전부
|
|
1067
827
|
`already_merged` 로 지나가고 Phase B 만 실제 일을 한다. 두 번 불려도 결과는 같다 —
|
|
1068
828
|
사라진 worktree 는 건너뛰고, 미커밋 변경이 남은 stage 트리는 보존한다.
|
|
@@ -1117,7 +877,7 @@ def integrate_and_teardown_whole_task(
|
|
|
1117
877
|
done_rows=done_rows,
|
|
1118
878
|
teardown=True,
|
|
1119
879
|
# 한 stage 브랜치가 이미 전체를 담고 있으면 검증은 거기서 돌았다
|
|
1120
|
-
# (`
|
|
880
|
+
# (`phases/final_verification/target.py`). 그 경우 task 브랜치로의 머지는 아무도
|
|
1121
881
|
# 읽지 않는 산출물이므로 정리만 한다.
|
|
1122
882
|
merge=tip is None,
|
|
1123
883
|
verified_head=(done_by_stage.get(tip) or {}).get("head_commit", ""),
|
|
@@ -1130,40 +890,3 @@ def integrate_and_teardown_whole_task(
|
|
|
1130
890
|
],
|
|
1131
891
|
"warnings": result.warnings,
|
|
1132
892
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
def acquire_final_verification_target(
|
|
1136
|
-
request: FinalVerificationTargetRequest,
|
|
1137
|
-
) -> FinalVerificationTargetAcquisition:
|
|
1138
|
-
"""Acquire a stable final-verification target behind one task-key lock."""
|
|
1139
|
-
from okstra_project.dirs import okstra_home
|
|
1140
|
-
|
|
1141
|
-
from .locks import worktree_provision_mutex
|
|
1142
|
-
|
|
1143
|
-
try:
|
|
1144
|
-
with worktree_provision_mutex(
|
|
1145
|
-
okstra_home(),
|
|
1146
|
-
request.project_id,
|
|
1147
|
-
slugify(request.task_group),
|
|
1148
|
-
slugify(request.task_id),
|
|
1149
|
-
):
|
|
1150
|
-
registry_coordinates = _final_verification_registry_coordinates(request)
|
|
1151
|
-
done_rows = _read_final_verification_done_rows(
|
|
1152
|
-
request,
|
|
1153
|
-
registry_coordinates,
|
|
1154
|
-
)
|
|
1155
|
-
if request.stage is not None:
|
|
1156
|
-
return _acquire_single_stage_target(
|
|
1157
|
-
request,
|
|
1158
|
-
done_rows,
|
|
1159
|
-
registry_coordinates,
|
|
1160
|
-
)
|
|
1161
|
-
return _acquire_whole_task_target(
|
|
1162
|
-
request,
|
|
1163
|
-
done_rows,
|
|
1164
|
-
registry_coordinates,
|
|
1165
|
-
)
|
|
1166
|
-
except PrepareError:
|
|
1167
|
-
raise
|
|
1168
|
-
except (OSError, RuntimeError, StageTargetError, ValueError) as exc:
|
|
1169
|
-
raise PrepareError(str(exc)) from exc
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""방향 비교와 기술 검증이 함께 읽는 미확정 사실 계약."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Any, TypedDict
|
|
6
|
+
|
|
7
|
+
from .clarification_items import USER_INPUT_BLOCKS, progress_blocking_ids
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VerificationFact(TypedDict):
|
|
11
|
+
id: str
|
|
12
|
+
candidateId: str
|
|
13
|
+
factIndex: int
|
|
14
|
+
fact: str
|
|
15
|
+
whyItMatters: str
|
|
16
|
+
evidence: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TechnicalVerificationError(ValueError):
|
|
20
|
+
"""시험 입력 또는 관측 결과가 원본 사실과 일치하지 않는다."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def technical_verification_facts(data: Mapping[str, Any]) -> list[VerificationFact]:
|
|
24
|
+
"""안전 차단이 없는 후보의 명시적인 기술 검증 사실만 추출한다."""
|
|
25
|
+
blockers = progress_blocking_ids(
|
|
26
|
+
data.get("clarificationItems"), USER_INPUT_BLOCKS, report_data=data
|
|
27
|
+
)
|
|
28
|
+
if blockers:
|
|
29
|
+
raise TechnicalVerificationError(
|
|
30
|
+
"unresolved user decisions: " + ", ".join(blockers)
|
|
31
|
+
)
|
|
32
|
+
selection = data.get("implementationOptionSelection") or {}
|
|
33
|
+
facts: list[VerificationFact] = []
|
|
34
|
+
for candidate in selection.get("candidateAudit") or []:
|
|
35
|
+
if candidate.get("safetyBlockers"):
|
|
36
|
+
continue
|
|
37
|
+
for index, fact in enumerate(candidate.get("unresolvedFeasibilityFacts") or []):
|
|
38
|
+
if fact.get("resolutionKind") != "technical-verification":
|
|
39
|
+
continue
|
|
40
|
+
facts.append(
|
|
41
|
+
{
|
|
42
|
+
"id": f"TV-{len(facts) + 1:03d}",
|
|
43
|
+
"candidateId": candidate["id"],
|
|
44
|
+
"factIndex": index,
|
|
45
|
+
"fact": fact["fact"],
|
|
46
|
+
"whyItMatters": fact["whyItMatters"],
|
|
47
|
+
"evidence": fact["evidence"],
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
if not facts:
|
|
51
|
+
raise TechnicalVerificationError("no eligible technical-verification facts")
|
|
52
|
+
return facts
|