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
|
@@ -14,12 +14,17 @@ state passing, and are read once at the start.
|
|
|
14
14
|
"""
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from .
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
from okstra_ctl.phases.feature_analysis.entry import (
|
|
18
|
+
resolve_analysis_target, validate_feature_target_input,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from okstra_ctl.phases.implementation_planning.validation import validate_selected_direction_plan
|
|
22
|
+
|
|
23
|
+
from .phases.technical_verification.entry import (
|
|
24
|
+
prepare_technical_verification_input,
|
|
25
|
+
materialize_technical_input_packet,
|
|
20
26
|
)
|
|
21
27
|
|
|
22
|
-
import hashlib
|
|
23
28
|
from argparse import (
|
|
24
29
|
Action,
|
|
25
30
|
ArgumentError,
|
|
@@ -41,6 +46,13 @@ from typing import Mapping, Sequence
|
|
|
41
46
|
|
|
42
47
|
from okstra_project import project_json_path, upsert_project_json
|
|
43
48
|
from okstra_project.slug import slugify
|
|
49
|
+
from .phases.catalog import host_rules
|
|
50
|
+
from .phases.implementation_planning.entry import (
|
|
51
|
+
_resolve_planning_input_path,
|
|
52
|
+
_validate_planning_entry_inputs,
|
|
53
|
+
_resolve_planning_direction,
|
|
54
|
+
_validate_reverify_scope,
|
|
55
|
+
)
|
|
44
56
|
from . import fix_cycles
|
|
45
57
|
from .analysis_packet import build_analysis_packet
|
|
46
58
|
from . import group_context
|
|
@@ -56,7 +68,6 @@ from .analysis_inputs import (
|
|
|
56
68
|
load_candidate_map,
|
|
57
69
|
parse_evidence_paths,
|
|
58
70
|
resolve_analysis_head,
|
|
59
|
-
resolve_analysis_target,
|
|
60
71
|
resolve_evidence_inputs,
|
|
61
72
|
)
|
|
62
73
|
from .stage_fix_carry import derive_stage_fix_carry
|
|
@@ -68,13 +79,11 @@ from .clarification_items import (
|
|
|
68
79
|
scan_approval_gate,
|
|
69
80
|
)
|
|
70
81
|
from .error_report import prior_run_error_digest
|
|
71
|
-
from .incremental_scope import
|
|
82
|
+
from .incremental_scope import parse_user_reverify_scope
|
|
72
83
|
from .implementation_direction import (
|
|
73
84
|
DirectionSelectionError,
|
|
74
85
|
SelectedDirection,
|
|
75
86
|
lexical_absolute_path,
|
|
76
|
-
resolve_selected_direction,
|
|
77
|
-
validate_selected_direction_plan,
|
|
78
87
|
validate_task_artifact_path,
|
|
79
88
|
write_selected_direction_snapshot,
|
|
80
89
|
)
|
|
@@ -206,7 +215,9 @@ from .session import (
|
|
|
206
215
|
resolve_inproc_lead_session_id,
|
|
207
216
|
write_claude_resume_command_file,
|
|
208
217
|
)
|
|
209
|
-
from .
|
|
218
|
+
from .phases.release_handoff.entry import (
|
|
219
|
+
materialize_handoff_input, resolve_pr_template,
|
|
220
|
+
)
|
|
210
221
|
from .worker_artifacts import worker_provider_id
|
|
211
222
|
from .workers import (
|
|
212
223
|
normalize_workers,
|
|
@@ -214,11 +225,19 @@ from .workers import (
|
|
|
214
225
|
resolve_profile_workers,
|
|
215
226
|
validate_workers_against_profile,
|
|
216
227
|
)
|
|
217
|
-
from .workflow import compute_workflow_state,
|
|
228
|
+
from .workflow import compute_workflow_state, load_phase_boundary
|
|
218
229
|
from .locks import worktree_provision_mutex
|
|
219
230
|
from . import stage_targets as _stage_targets
|
|
220
|
-
from .
|
|
221
|
-
from . import
|
|
231
|
+
from .phases.catalog import PhaseAssetError, profile_markdown, resolve_include
|
|
232
|
+
from .phases.final_verification.target import acquire_final_verification_target
|
|
233
|
+
from .phases.final_verification.entry import (
|
|
234
|
+
apply_verification_target,
|
|
235
|
+
reject_prepare_flags,
|
|
236
|
+
single_stage_worktree,
|
|
237
|
+
verification_target_request,
|
|
238
|
+
write_verification_target_snapshot,
|
|
239
|
+
)
|
|
240
|
+
from .phases.implementation import entry as _implementation_stage
|
|
222
241
|
from .work_categories import resolve_work_category
|
|
223
242
|
from .worktree import (
|
|
224
243
|
WorktreeProvision,
|
|
@@ -228,7 +247,6 @@ from .brief_frontmatter import (
|
|
|
228
247
|
has_reporter_confirmation_contract,
|
|
229
248
|
read_brief_frontmatter,
|
|
230
249
|
)
|
|
231
|
-
from .scope_provenance import brief_end_state_id_sequence
|
|
232
250
|
|
|
233
251
|
# Frontmatter approval-flag matcher.
|
|
234
252
|
#
|
|
@@ -509,6 +527,9 @@ def _set_data_json_approved_true_if_present(path: Path) -> bool:
|
|
|
509
527
|
# PrepareError 는 stage_targets(저수준)에 정의돼 final-verification 과 container
|
|
510
528
|
# 가 whole-task 통합 실패를 동일 타입으로 받는다. run.py 는 재노출만 한다.
|
|
511
529
|
PrepareError = _stage_targets.PrepareError
|
|
530
|
+
from .phases.implementation_option_selection.entry import (
|
|
531
|
+
validate_analyser_roster, validate_brief_requirements,
|
|
532
|
+
)
|
|
512
533
|
|
|
513
534
|
|
|
514
535
|
def _normalize_lead_runtime(value: str) -> str:
|
|
@@ -688,17 +709,7 @@ def _validate_stage_structure(plan_path: str) -> None:
|
|
|
688
709
|
RUN_STEP_BUDGET = _stage_targets.RUN_STEP_BUDGET
|
|
689
710
|
|
|
690
711
|
|
|
691
|
-
|
|
692
|
-
"""Parse the approved plan into context records for execution consumers."""
|
|
693
|
-
from .stage_map import StageMapError, parse_stage_map_file, stage_map_records
|
|
694
|
-
|
|
695
|
-
try:
|
|
696
|
-
return stage_map_records(parse_stage_map_file(Path(plan_path)))
|
|
697
|
-
except StageMapError as exc:
|
|
698
|
-
raise PrepareError(
|
|
699
|
-
"approved-plan 의 Stage Map 을 신뢰할 수 없어 거부합니다 "
|
|
700
|
-
f"({plan_path}): {exc.reason}. plan 의 Stage Map 을 점검하세요."
|
|
701
|
-
) from exc
|
|
712
|
+
from .stage_map import parse_stage_map_for_prepare as _parse_stage_map_into_ctx
|
|
702
713
|
|
|
703
714
|
|
|
704
715
|
def _apply_cli_approval(path: str) -> None:
|
|
@@ -1215,8 +1226,11 @@ _HTML_COMMENT = re.compile(r"<!--.*?-->", re.DOTALL)
|
|
|
1215
1226
|
def _expand_profile_includes(profile_path: Path, _depth: int = 0) -> str:
|
|
1216
1227
|
"""Resolve `{{INCLUDE:<name>}}` directives in a profile file.
|
|
1217
1228
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1229
|
+
`phases.catalog.resolve_include` picks the search root: an unmigrated
|
|
1230
|
+
profile resolves beside itself, and a phase package's `profile.md`
|
|
1231
|
+
resolves through the logical paths of the asset root that holds it.
|
|
1232
|
+
Recursion stops at depth 4
|
|
1233
|
+
so a cycle fails instead of looping; the included file
|
|
1220
1234
|
contents replace the directive line in-place. Missing include targets
|
|
1221
1235
|
raise PrepareError so a bad reference fails fast instead of silently
|
|
1222
1236
|
leaving a `{{INCLUDE:...}}` token in the rendered profile.
|
|
@@ -1235,7 +1249,10 @@ def _expand_profile_includes(profile_path: Path, _depth: int = 0) -> str:
|
|
|
1235
1249
|
|
|
1236
1250
|
def _sub(match: "re.Match[str]") -> str:
|
|
1237
1251
|
target_name = match.group(1).strip()
|
|
1238
|
-
|
|
1252
|
+
try:
|
|
1253
|
+
target = resolve_include(profile_path, target_name)
|
|
1254
|
+
except PhaseAssetError as exc:
|
|
1255
|
+
raise PrepareError(str(exc)) from exc
|
|
1239
1256
|
if not target.is_file():
|
|
1240
1257
|
raise PrepareError(
|
|
1241
1258
|
f"profile include target missing: {target} (referenced from {profile_path})"
|
|
@@ -1289,7 +1306,10 @@ class _ResolvedAssets:
|
|
|
1289
1306
|
|
|
1290
1307
|
def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _ResolvedAssets:
|
|
1291
1308
|
"""task-type 별 profile + 공통 템플릿/스킬/validator 경로를 해소하고 존재를 확인한다."""
|
|
1292
|
-
|
|
1309
|
+
try:
|
|
1310
|
+
profile_file = profile_markdown(workspace_root, inp.task_type)
|
|
1311
|
+
except PhaseAssetError as exc:
|
|
1312
|
+
raise PrepareError(str(exc)) from exc
|
|
1293
1313
|
if not profile_file.is_file():
|
|
1294
1314
|
raise PrepareError(
|
|
1295
1315
|
f"analysis profile file not found for task-type {inp.task_type}: "
|
|
@@ -1335,9 +1355,7 @@ def _resolve_runtime_assets(workspace_root: Path, inp: PrepareInputs) -> _Resolv
|
|
|
1335
1355
|
raise PrepareError(
|
|
1336
1356
|
f"required okstra template or lead contract missing: {required}.{_INSTALL_HINT}"
|
|
1337
1357
|
)
|
|
1338
|
-
host_rules_file = (
|
|
1339
|
-
workspace_root / "prompts" / "host-orchestration" / f"{inp.task_type}.md"
|
|
1340
|
-
)
|
|
1358
|
+
host_rules_file = host_rules(workspace_root, inp.task_type)
|
|
1341
1359
|
return _ResolvedAssets(
|
|
1342
1360
|
profile_file=profile_file,
|
|
1343
1361
|
prompt_template=prompt_template,
|
|
@@ -1417,17 +1435,12 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
|
|
|
1417
1435
|
if inp.task_type == "release-handoff":
|
|
1418
1436
|
# brief 는 entry phase 의 입력물 — release-handoff 는 검증 보고서를
|
|
1419
1437
|
# 인용하는 input 문서를 prepare 가 직접 생성해 brief 자리에 채운다
|
|
1420
|
-
# (
|
|
1438
|
+
# (materialize_handoff_input, 이 검증 직후 실행).
|
|
1421
1439
|
pass
|
|
1422
1440
|
elif not inp.brief_path.is_file():
|
|
1423
1441
|
raise PrepareError(f"task brief not found: {inp.brief_path}")
|
|
1424
|
-
elif inp.task_type == "implementation-option-selection"
|
|
1425
|
-
inp.brief_path
|
|
1426
|
-
):
|
|
1427
|
-
raise PrepareError(
|
|
1428
|
-
"regenerate the brief with stable end-state IDs before starting "
|
|
1429
|
-
"implementation-option-selection"
|
|
1430
|
-
)
|
|
1442
|
+
elif inp.task_type == "implementation-option-selection":
|
|
1443
|
+
validate_brief_requirements(inp.brief_path)
|
|
1431
1444
|
ctx_stage_map: list = []
|
|
1432
1445
|
# implementation 과 final-verification 은 둘 다 승인된 plan 의 Stage Map 을
|
|
1433
1446
|
# 입력으로 받는다(전자는 실행 scope, 후자는 검증 scope).
|
|
@@ -1438,18 +1451,10 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
|
|
|
1438
1451
|
"--approved-plan <path-to-final-report.md>"
|
|
1439
1452
|
)
|
|
1440
1453
|
if inp.task_type == "final-verification":
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
"--approve is only meaningful with --task-type implementation "
|
|
1446
|
-
"and --approved-plan <path>"
|
|
1447
|
-
)
|
|
1448
|
-
if inp.implementation_option:
|
|
1449
|
-
raise PrepareError(
|
|
1450
|
-
"--implementation-option is only meaningful with --task-type "
|
|
1451
|
-
"implementation and --approved-plan <path>"
|
|
1452
|
-
)
|
|
1454
|
+
reject_prepare_flags(
|
|
1455
|
+
approve_plan_ack=inp.approve_plan_ack,
|
|
1456
|
+
implementation_option=inp.implementation_option,
|
|
1457
|
+
)
|
|
1453
1458
|
ctx_stage_map = _parse_stage_map_into_ctx(inp.approved_plan_path)
|
|
1454
1459
|
else:
|
|
1455
1460
|
if inp.approve_plan_ack:
|
|
@@ -1482,143 +1487,6 @@ def _validate_prepare_inputs(project_root: Path, inp: PrepareInputs) -> list:
|
|
|
1482
1487
|
return ctx_stage_map
|
|
1483
1488
|
|
|
1484
1489
|
|
|
1485
|
-
_PLANNING_REPORT_RE = re.compile(
|
|
1486
|
-
r"^final-report-implementation-planning-\d{3,}\.(?:md|data\.json)$"
|
|
1487
|
-
)
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
def _resolve_planning_input_path(
|
|
1491
|
-
path_value: str, project_root: Path
|
|
1492
|
-
) -> Path | None:
|
|
1493
|
-
"""Resolve cwd-first planning inputs without following symbolic links."""
|
|
1494
|
-
raw_path = Path(path_value).expanduser()
|
|
1495
|
-
cwd_candidate = lexical_absolute_path(raw_path)
|
|
1496
|
-
if cwd_candidate.is_file():
|
|
1497
|
-
return cwd_candidate
|
|
1498
|
-
if not raw_path.is_absolute():
|
|
1499
|
-
project_candidate = lexical_absolute_path(project_root / raw_path)
|
|
1500
|
-
if project_candidate.is_file():
|
|
1501
|
-
return project_candidate
|
|
1502
|
-
return None
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
def _is_existing_planning_report(
|
|
1506
|
-
path_value: str, project_root: Path, inp: PrepareInputs
|
|
1507
|
-
) -> bool:
|
|
1508
|
-
from .paths import task_dir
|
|
1509
|
-
|
|
1510
|
-
try:
|
|
1511
|
-
report = lexical_absolute_path(Path(path_value))
|
|
1512
|
-
task_root = lexical_absolute_path(
|
|
1513
|
-
task_dir(project_root, inp.task_group, inp.task_id)
|
|
1514
|
-
)
|
|
1515
|
-
relative = report.relative_to(task_root)
|
|
1516
|
-
except ValueError:
|
|
1517
|
-
return False
|
|
1518
|
-
has_planning_layout = (
|
|
1519
|
-
_PLANNING_REPORT_RE.fullmatch(report.name) is not None
|
|
1520
|
-
and relative.parts[:3] == (
|
|
1521
|
-
"runs",
|
|
1522
|
-
"implementation-planning",
|
|
1523
|
-
"reports",
|
|
1524
|
-
)
|
|
1525
|
-
and len(relative.parts) == 4
|
|
1526
|
-
)
|
|
1527
|
-
if not has_planning_layout:
|
|
1528
|
-
return False
|
|
1529
|
-
try:
|
|
1530
|
-
validate_task_artifact_path(report, task_root, "planning report")
|
|
1531
|
-
except DirectionSelectionError:
|
|
1532
|
-
return False
|
|
1533
|
-
return True
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
def _validate_planning_entry_inputs(
|
|
1537
|
-
project_root: Path, inp: PrepareInputs
|
|
1538
|
-
) -> None:
|
|
1539
|
-
if inp.task_type != "implementation-planning":
|
|
1540
|
-
if inp.selected_direction_path:
|
|
1541
|
-
raise PrepareError(
|
|
1542
|
-
"--selected-direction is only meaningful with --task-type "
|
|
1543
|
-
f"implementation-planning; got {inp.task_type}"
|
|
1544
|
-
)
|
|
1545
|
-
return
|
|
1546
|
-
if inp.selected_direction_path and inp.clarification_response_path:
|
|
1547
|
-
raise PrepareError(
|
|
1548
|
-
"implementation-planning accepts either --selected-direction for a "
|
|
1549
|
-
"new plan or --clarification-response for a rerun, not both"
|
|
1550
|
-
)
|
|
1551
|
-
if inp.selected_direction_path:
|
|
1552
|
-
return
|
|
1553
|
-
if inp.clarification_response_path:
|
|
1554
|
-
if not _is_existing_planning_report(
|
|
1555
|
-
inp.clarification_response_path, project_root, inp
|
|
1556
|
-
):
|
|
1557
|
-
raise PrepareError(
|
|
1558
|
-
"implementation-planning rerun --clarification-response must point "
|
|
1559
|
-
"to an existing implementation-planning report for the same task"
|
|
1560
|
-
)
|
|
1561
|
-
return
|
|
1562
|
-
raise PrepareError(
|
|
1563
|
-
"implementation-planning requires --selected-direction for a new plan or "
|
|
1564
|
-
"--clarification-response to an existing implementation-planning report"
|
|
1565
|
-
)
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
def _resolve_planning_direction(inp: PrepareInputs) -> SelectedDirection | None:
|
|
1569
|
-
if inp.task_type != "implementation-planning" or not inp.selected_direction_path:
|
|
1570
|
-
return None
|
|
1571
|
-
from .paths import task_dir
|
|
1572
|
-
|
|
1573
|
-
try:
|
|
1574
|
-
report = lexical_absolute_path(Path(inp.selected_direction_path))
|
|
1575
|
-
expected_task_root = lexical_absolute_path(
|
|
1576
|
-
task_dir(Path(inp.project_root), inp.task_group, inp.task_id)
|
|
1577
|
-
)
|
|
1578
|
-
report.relative_to(expected_task_root)
|
|
1579
|
-
except ValueError as exc:
|
|
1580
|
-
raise PrepareError(
|
|
1581
|
-
"selected direction report must belong to the same task"
|
|
1582
|
-
) from exc
|
|
1583
|
-
try:
|
|
1584
|
-
validate_task_artifact_path(
|
|
1585
|
-
report, expected_task_root, "selected direction report"
|
|
1586
|
-
)
|
|
1587
|
-
return resolve_selected_direction(
|
|
1588
|
-
report,
|
|
1589
|
-
expected_task_key=(
|
|
1590
|
-
f"{inp.project_id}:{inp.task_group}:{inp.task_id}"
|
|
1591
|
-
),
|
|
1592
|
-
)
|
|
1593
|
-
except DirectionSelectionError as exc:
|
|
1594
|
-
raise PrepareError(str(exc)) from exc
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
def _validate_reverify_scope(inp: PrepareInputs) -> None:
|
|
1598
|
-
"""A pinned re-verification scope is only actionable on a planning re-run.
|
|
1599
|
-
|
|
1600
|
-
Every other phase renders the tokens too (the template always reads them),
|
|
1601
|
-
but nothing consumes them there — so a value outside the one phase that
|
|
1602
|
-
acts on it is a caller mistake, not a preference to honour silently.
|
|
1603
|
-
"""
|
|
1604
|
-
if not (inp.reverify_scope or "").strip():
|
|
1605
|
-
return
|
|
1606
|
-
if inp.task_type != "implementation-planning":
|
|
1607
|
-
raise PrepareError(
|
|
1608
|
-
"--reverify-scope is only meaningful with --task-type "
|
|
1609
|
-
f"implementation-planning; got {inp.task_type}"
|
|
1610
|
-
)
|
|
1611
|
-
if not inp.clarification_response_path:
|
|
1612
|
-
raise PrepareError(
|
|
1613
|
-
"--reverify-scope needs --clarification-response: there is no prior "
|
|
1614
|
-
"report to narrow re-verification against"
|
|
1615
|
-
)
|
|
1616
|
-
try:
|
|
1617
|
-
parse_user_reverify_scope(inp.reverify_scope)
|
|
1618
|
-
except ReverifyScopeError as exc:
|
|
1619
|
-
raise PrepareError(str(exc)) from exc
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
1490
|
def _implementation_plan_contract(
|
|
1623
1491
|
inp: PrepareInputs,
|
|
1624
1492
|
) -> tuple[str, Path, dict | None]:
|
|
@@ -1764,13 +1632,12 @@ def _validate_analysis_prepare_inputs(project_root: Path, inp: PrepareInputs) ->
|
|
|
1764
1632
|
)
|
|
1765
1633
|
return
|
|
1766
1634
|
if inp.task_type == "project-analysis":
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
raise PrepareError("project-analysis does not accept evidence inputs")
|
|
1635
|
+
from .phases.project_analysis.entry import validate_project_analysis_inputs
|
|
1636
|
+
|
|
1637
|
+
validate_project_analysis_inputs(inp.analysis_target, inp.evidence_inputs_raw)
|
|
1771
1638
|
return
|
|
1772
|
-
if inp.task_type == "feature-analysis"
|
|
1773
|
-
|
|
1639
|
+
if inp.task_type == "feature-analysis":
|
|
1640
|
+
validate_feature_target_input(inp.analysis_target)
|
|
1774
1641
|
if inp.task_type != "feature-analysis" and inp.analysis_target:
|
|
1775
1642
|
raise PrepareError(f"{inp.task_type} does not accept an analysis target")
|
|
1776
1643
|
paths = parse_evidence_paths(inp.evidence_inputs_raw, project_root)
|
|
@@ -1785,127 +1652,10 @@ def _validate_analysis_prepare_inputs(project_root: Path, inp: PrepareInputs) ->
|
|
|
1785
1652
|
raise PrepareError(str(exc)) from exc
|
|
1786
1653
|
|
|
1787
1654
|
|
|
1788
|
-
def _collect_handoff_source_report_rows(
|
|
1789
|
-
rows: list, nums: list,
|
|
1790
|
-
) -> list:
|
|
1791
|
-
"""선택 stage 들의 마지막 verified 행에서 인용 표 행을 만든다 (last-wins)."""
|
|
1792
|
-
last_verified: dict = {}
|
|
1793
|
-
for r in rows:
|
|
1794
|
-
if r.get("status") == "verified" and isinstance(r.get("stage"), int):
|
|
1795
|
-
last_verified[r["stage"]] = r
|
|
1796
|
-
return [(str(n), Path(last_verified[n]["report_path"])) for n in nums]
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
def _render_handoff_sources(sources: list, project_root: Path) -> tuple[str, str]:
|
|
1800
|
-
"""인용 표와 미해결 조건은 같은 원본 판정에서 렌더한다."""
|
|
1801
|
-
from .release_gate import release_handoff_allowed, verdict_token
|
|
1802
|
-
|
|
1803
|
-
rows, conditions = [], []
|
|
1804
|
-
for stage, report in sources:
|
|
1805
|
-
data = load_owned_object(report, artifact="handoff verification report")
|
|
1806
|
-
if not release_handoff_allowed(data):
|
|
1807
|
-
raise PrepareError(f"verification no longer release-ready: {report}")
|
|
1808
|
-
relative = relative_to_project_root(report, project_root) or str(report)
|
|
1809
|
-
rows.append(f"| {stage} | {relative} | `{verdict_token(data)}` |")
|
|
1810
|
-
if verdict_token(data) == "conditional-accept":
|
|
1811
|
-
for condition in data["finalVerdict"]["conditionalAcceptanceConditions"]:
|
|
1812
|
-
conditions.append(
|
|
1813
|
-
f"- Stage {stage}, `{condition.get('id', '')}`\n"
|
|
1814
|
-
f" - Condition: {condition.get('condition', '')}\n"
|
|
1815
|
-
f" - Evidence required: {condition.get('evidenceRequired', '')}\n"
|
|
1816
|
-
f" - blocksReleaseHandoff: {json.dumps(condition['blocksReleaseHandoff'])}"
|
|
1817
|
-
)
|
|
1818
|
-
section = "## Unresolved Conditional Acceptance Conditions\n\n" + "\n".join(conditions) if conditions else ""
|
|
1819
|
-
return "\n".join(rows), section
|
|
1820
1655
|
|
|
1821
1656
|
|
|
1822
|
-
def _materialize_release_handoff_input(
|
|
1823
|
-
workspace_root: Path, project_root: Path, inp: "PrepareInputs",
|
|
1824
|
-
) -> dict:
|
|
1825
|
-
"""release-handoff 의 입력 문서를 검증 보고서 인용으로 자동 생성한다.
|
|
1826
|
-
|
|
1827
|
-
brief 는 entry phase 의 입력물이라 release-handoff 에는 없다 — 이 run 의
|
|
1828
|
-
`--task-brief` 자리는 여기서 생성된 input 문서가 채운다. stage 자격은
|
|
1829
|
-
okstra_ctl.handoff 의 SSOT 판정을 prepare 에서도 그대로 강제한다.
|
|
1830
|
-
반환: ctx 에 올릴 {"HANDOFF_STAGES": ...}."""
|
|
1831
|
-
from .consumers import read_consumers
|
|
1832
|
-
from .handoff import HandoffError, _require_eligible, compute_eligibility
|
|
1833
|
-
from .paths import task_dir
|
|
1834
|
-
from .render import render_template_with_ctx
|
|
1835
|
-
from .run_context import _now_task_date
|
|
1836
1657
|
|
|
1837
|
-
# Path("") 는 "." 로 정규화된다 — 빈 brief 센티널은 둘 다로 들어올 수 있다.
|
|
1838
|
-
if str(inp.brief_path).strip() not in ("", "."):
|
|
1839
|
-
raise PrepareError(
|
|
1840
|
-
"--task-brief is not accepted for --task-type release-handoff — "
|
|
1841
|
-
"the input document is generated from the cited final-verification "
|
|
1842
|
-
"reports (pick stages via --stages, or leave it empty to take "
|
|
1843
|
-
"every eligible stage)"
|
|
1844
|
-
)
|
|
1845
|
-
if not inp.approved_plan_path:
|
|
1846
|
-
raise PrepareError(
|
|
1847
|
-
"release-handoff requires --approved-plan "
|
|
1848
|
-
"<implementation-planning final-report> — the Stage Map and the "
|
|
1849
|
-
"consumers ledger drive stage eligibility"
|
|
1850
|
-
)
|
|
1851
|
-
plan = Path(inp.approved_plan_path)
|
|
1852
|
-
if not plan.is_file():
|
|
1853
|
-
raise PrepareError(f"approved plan not found: {plan}")
|
|
1854
|
-
stage_map = _parse_stage_map_into_ctx(str(plan))
|
|
1855
|
-
rows = read_consumers(plan_run_root_from_approved_plan(plan))
|
|
1856
1658
|
|
|
1857
|
-
if inp.stages:
|
|
1858
|
-
try:
|
|
1859
|
-
nums = sorted({int(x) for x in inp.stages.split(",") if x.strip()})
|
|
1860
|
-
except ValueError:
|
|
1861
|
-
raise PrepareError(
|
|
1862
|
-
f"--stages must be a comma-separated int list, got {inp.stages!r}"
|
|
1863
|
-
)
|
|
1864
|
-
if not nums:
|
|
1865
|
-
raise PrepareError("--stages must select at least one stage")
|
|
1866
|
-
try:
|
|
1867
|
-
_require_eligible(stage_map, rows, nums)
|
|
1868
|
-
except HandoffError as exc:
|
|
1869
|
-
raise PrepareError(str(exc)) from exc
|
|
1870
|
-
else:
|
|
1871
|
-
eligibility = compute_eligibility(stage_map, rows)
|
|
1872
|
-
nums = [e["stage"] for e in eligibility if e["eligible"]]
|
|
1873
|
-
if not nums:
|
|
1874
|
-
blocked = "; ".join(
|
|
1875
|
-
f"stage {e['stage']}: {', '.join(e['reasons'])}"
|
|
1876
|
-
for e in eligibility) or "(no stage in the Stage Map)"
|
|
1877
|
-
raise PrepareError(
|
|
1878
|
-
"release-handoff has no eligible stage — every stage must "
|
|
1879
|
-
"carry a release-ready single-stage final-verification of its "
|
|
1880
|
-
f"recorded implementation commit. {blocked}"
|
|
1881
|
-
)
|
|
1882
|
-
stages_csv = ",".join(str(n) for n in nums)
|
|
1883
|
-
report_rows = _collect_handoff_source_report_rows(rows, nums)
|
|
1884
|
-
|
|
1885
|
-
source_rows, source_conditions = _render_handoff_sources(report_rows, project_root)
|
|
1886
|
-
|
|
1887
|
-
template = (workspace_root / "templates" / "reports"
|
|
1888
|
-
/ "release-handoff-input.template.md")
|
|
1889
|
-
if not template.is_file():
|
|
1890
|
-
raise PrepareError(
|
|
1891
|
-
f"release-handoff input template missing: {template}.{_INSTALL_HINT}"
|
|
1892
|
-
)
|
|
1893
|
-
out = (task_dir(project_root, inp.task_group, inp.task_id)
|
|
1894
|
-
/ "release-handoff-input.md")
|
|
1895
|
-
out.parent.mkdir(parents=True, exist_ok=True)
|
|
1896
|
-
render_template_with_ctx(str(template), str(out), {
|
|
1897
|
-
"TASK_KEY": f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
|
|
1898
|
-
"TASK_ID": inp.task_id,
|
|
1899
|
-
"TASK_GROUP": inp.task_group,
|
|
1900
|
-
"PROJECT_ID": inp.project_id,
|
|
1901
|
-
"TASK_TYPE": inp.task_type,
|
|
1902
|
-
"TASK_DATE": _now_task_date(),
|
|
1903
|
-
"HANDOFF_STAGES": stages_csv,
|
|
1904
|
-
"HANDOFF_SOURCE_REPORTS": source_rows,
|
|
1905
|
-
"HANDOFF_CONDITIONS": source_conditions,
|
|
1906
|
-
})
|
|
1907
|
-
inp.brief_path = out
|
|
1908
|
-
return {"HANDOFF_STAGES": stages_csv}
|
|
1909
1659
|
|
|
1910
1660
|
|
|
1911
1661
|
QA_COMMAND_EXECUTING_TASK_TYPES = ("implementation", "final-verification")
|
|
@@ -2650,28 +2400,11 @@ def _read_model_defaults(path: Path) -> dict[str, tuple[str, ...]]:
|
|
|
2650
2400
|
return defaults
|
|
2651
2401
|
|
|
2652
2402
|
|
|
2653
|
-
def _validate_option_selection_roster(
|
|
2654
|
-
task_type
|
|
2655
|
-
)
|
|
2656
|
-
"""Require independent analysis from three workers before option selection."""
|
|
2657
|
-
if task_type != "implementation-option-selection":
|
|
2658
|
-
return
|
|
2659
|
-
analyser_count = sum(worker != "report-writer" for worker in workers)
|
|
2660
|
-
if analyser_count < 3:
|
|
2661
|
-
raise PrepareError(
|
|
2662
|
-
"implementation-option-selection requires at least 3 analyser workers"
|
|
2663
|
-
)
|
|
2403
|
+
def _validate_option_selection_roster(task_type: str, workers: Sequence[str]) -> None:
|
|
2404
|
+
if task_type == "implementation-option-selection":
|
|
2405
|
+
validate_analyser_roster(workers)
|
|
2664
2406
|
|
|
2665
2407
|
|
|
2666
|
-
def _resolve_pr_template(inp: PrepareInputs) -> tuple[str, str]:
|
|
2667
|
-
"""release-handoff 전용 PR 본문 템플릿 경로 + 출처를 해소한다 (그 외엔 빈 값)."""
|
|
2668
|
-
if inp.task_type != "release-handoff":
|
|
2669
|
-
return "", ""
|
|
2670
|
-
try:
|
|
2671
|
-
resolved_tpl = resolve_pr_template_path(Path(inp.project_root), inp.pr_template_path)
|
|
2672
|
-
except PrTemplateError as exc:
|
|
2673
|
-
raise PrepareError(f"PR template resolution failed: {exc}") from exc
|
|
2674
|
-
return str(resolved_tpl.path), resolved_tpl.source
|
|
2675
2408
|
|
|
2676
2409
|
|
|
2677
2410
|
@dataclass
|
|
@@ -3460,139 +3193,6 @@ def _git_out(cwd, *args) -> str:
|
|
|
3460
3193
|
return r.stdout.strip() if r.returncode == 0 else ""
|
|
3461
3194
|
|
|
3462
3195
|
|
|
3463
|
-
def _single_stage_final_verification_worktree(inp: "PrepareInputs") -> WorktreeProvision:
|
|
3464
|
-
"""Placeholder until the selected stage registry row is resolved."""
|
|
3465
|
-
return WorktreeProvision(
|
|
3466
|
-
status="deferred-final-verification",
|
|
3467
|
-
note=(
|
|
3468
|
-
"final-verification single-stage uses the selected implementation "
|
|
3469
|
-
"stage worktree from the registry"
|
|
3470
|
-
),
|
|
3471
|
-
)
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
def _format_integration(integ) -> str:
|
|
3475
|
-
"""stage 자동 통합 결과를 사람이 읽을 한국어 마크다운 블록으로 만든다."""
|
|
3476
|
-
def _csv(xs):
|
|
3477
|
-
return ", ".join(str(x) for x in xs) if xs else "없음"
|
|
3478
|
-
|
|
3479
|
-
skipped = "; ".join(f"stage {n}({why})" for n, why in integ.teardown_skipped)
|
|
3480
|
-
lines = [
|
|
3481
|
-
"- **Stage 자동 통합 결과** (whole-task final-verification):",
|
|
3482
|
-
"- **머지된 stage:** " + _csv(integ.merged),
|
|
3483
|
-
"- **이미 머지됨:** " + _csv(integ.already_merged),
|
|
3484
|
-
"- **정리(teardown)된 stage:** " + _csv(integ.torn_down),
|
|
3485
|
-
"- **정리 보류:** " + (skipped or "없음"),
|
|
3486
|
-
]
|
|
3487
|
-
for w in integ.warnings:
|
|
3488
|
-
lines.append("- **경고:** " + w)
|
|
3489
|
-
return "\n".join(lines)
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
def _final_verification_target_request(
|
|
3493
|
-
inp: "PrepareInputs",
|
|
3494
|
-
ctx_stage_map: list[dict],
|
|
3495
|
-
) -> _stage_targets.FinalVerificationTargetRequest:
|
|
3496
|
-
stage = (
|
|
3497
|
-
int(inp.stage)
|
|
3498
|
-
if inp.stage and inp.stage != "auto"
|
|
3499
|
-
else None
|
|
3500
|
-
)
|
|
3501
|
-
return _stage_targets.FinalVerificationTargetRequest(
|
|
3502
|
-
project_root=Path(inp.project_root),
|
|
3503
|
-
project_id=inp.project_id,
|
|
3504
|
-
task_group=inp.task_group,
|
|
3505
|
-
task_id=inp.task_id,
|
|
3506
|
-
work_category=inp.work_category,
|
|
3507
|
-
approved_plan_path=Path(inp.approved_plan_path),
|
|
3508
|
-
stage=stage,
|
|
3509
|
-
stage_map=tuple(ctx_stage_map),
|
|
3510
|
-
)
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
def _apply_final_verification_target(
|
|
3514
|
-
ctx: dict,
|
|
3515
|
-
acquisition: _stage_targets.FinalVerificationTargetAcquisition,
|
|
3516
|
-
) -> None:
|
|
3517
|
-
"""Translate acquired domain facts into render-context fields."""
|
|
3518
|
-
target = acquisition.target
|
|
3519
|
-
if target.scope == "single-stage":
|
|
3520
|
-
stage = target.stages[0]
|
|
3521
|
-
ctx["EXECUTOR_WORKTREE_PATH"] = target.worktree_path
|
|
3522
|
-
ctx["EXECUTOR_WORKTREE_BRANCH"] = acquisition.worktree_branch
|
|
3523
|
-
ctx["EXECUTOR_WORKTREE_BASE_REF"] = target.base
|
|
3524
|
-
ctx["EXECUTOR_WORKTREE_STATUS"] = "reused-stage"
|
|
3525
|
-
ctx["EXECUTOR_WORKTREE_NOTE"] = (
|
|
3526
|
-
f"final-verification uses implementation stage {stage} worktree"
|
|
3527
|
-
)
|
|
3528
|
-
if acquisition.integration_result is not None:
|
|
3529
|
-
ctx["STAGE_INTEGRATION"] = _format_integration(
|
|
3530
|
-
acquisition.integration_result
|
|
3531
|
-
)
|
|
3532
|
-
if acquisition.relocations:
|
|
3533
|
-
moved = "\n".join(
|
|
3534
|
-
f"- stage {row['stage']}: `{row['from']}` → `{row['to']}`"
|
|
3535
|
-
for row in acquisition.relocations
|
|
3536
|
-
)
|
|
3537
|
-
ctx["STAGE_INTEGRATION"] = (
|
|
3538
|
-
f"{ctx.get('STAGE_INTEGRATION', '')}\n"
|
|
3539
|
-
"Nested stage worktrees moved out of the verification worktree "
|
|
3540
|
-
"(registry updated; contents and branches unchanged):\n"
|
|
3541
|
-
f"{moved}"
|
|
3542
|
-
).strip()
|
|
3543
|
-
diff_stat = _git_out(target.worktree_path, "diff", "--stat",
|
|
3544
|
-
f"{target.base}..{target.head}")
|
|
3545
|
-
ctx["VERIFICATION_SCOPE"] = target.scope
|
|
3546
|
-
ctx["VERIFICATION_WORKTREE_PATH"] = target.worktree_path
|
|
3547
|
-
ctx["VERIFICATION_BASE_REF"] = target.base
|
|
3548
|
-
ctx["VERIFICATION_HEAD_REF"] = target.head
|
|
3549
|
-
ctx["VERIFICATION_TARGET"] = _format_verification_target(target, diff_stat)
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
def _format_verification_target(
|
|
3553
|
-
target: _stage_targets.FinalVerificationTarget,
|
|
3554
|
-
diff_stat: str,
|
|
3555
|
-
) -> str:
|
|
3556
|
-
reports = "\n".join(
|
|
3557
|
-
f" - stage {s}: `{rp or '(report_path unrecorded)'}`"
|
|
3558
|
-
for s, rp in zip(target.stages, target.reports)
|
|
3559
|
-
)
|
|
3560
|
-
return (
|
|
3561
|
-
f"- **Verification scope:** `{target.scope}`\n"
|
|
3562
|
-
f"- **Worktree:** `{target.worktree_path}`\n"
|
|
3563
|
-
f"- **Verification base ref:** `{target.base}`\n"
|
|
3564
|
-
f"- **Verification head ref:** `{target.head}`\n"
|
|
3565
|
-
f"- **Stages under verification:** {target.stages}\n"
|
|
3566
|
-
f"- **Source implementation reports:**\n{reports}\n"
|
|
3567
|
-
f"- **Verification diff stat:**\n```\n{diff_stat}\n```"
|
|
3568
|
-
)
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
def write_verification_target_snapshot(
|
|
3572
|
-
instruction_set: Path,
|
|
3573
|
-
target_markdown: str,
|
|
3574
|
-
) -> tuple[Path, str]:
|
|
3575
|
-
"""Persist one normalized final-verification target and return its digest."""
|
|
3576
|
-
normalized = target_markdown.replace("\r\n", "\n").replace("\r", "\n")
|
|
3577
|
-
normalized = normalized.rstrip() + "\n"
|
|
3578
|
-
digest = "sha256:" + hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
|
3579
|
-
path = instruction_set / "verification-target.md"
|
|
3580
|
-
# 계산 규칙을 값과 함께 적는다. 워커는 이 다이제스트를 프롬프트 앵커로도
|
|
3581
|
-
# 받는데, 규칙을 모르면 파일 전체 바이트로 재고 반드시 불일치를 본다 —
|
|
3582
|
-
# 실측(2026-08-26): 그렇게 잰 검증자가 스냅샷이 변조됐다고 보고했다.
|
|
3583
|
-
# 다이제스트 줄은 줄머리 앵커로 잘리므로(`_TARGET_DIGEST_RE`) 그 뒤에
|
|
3584
|
-
# 붙는 이 줄은 본문에 들어가지 않는다.
|
|
3585
|
-
path.write_text(
|
|
3586
|
-
normalized
|
|
3587
|
-
+ f"- **Verification target digest:** `{digest}`\n"
|
|
3588
|
-
+ "- **Digest rule:** `sha256` of the text above this line, with CRLF "
|
|
3589
|
-
"and CR normalized to LF, trailing whitespace stripped, and exactly "
|
|
3590
|
-
"one closing newline. This line and the digest line are not covered.\n",
|
|
3591
|
-
encoding="utf-8",
|
|
3592
|
-
)
|
|
3593
|
-
return path, digest
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
3196
|
def _write_verification_target_artifact(
|
|
3597
3197
|
inp: PrepareInputs,
|
|
3598
3198
|
ctx: dict,
|
|
@@ -3686,6 +3286,9 @@ def _write_instruction_set_sources(
|
|
|
3686
3286
|
profile_rendered = profile_content
|
|
3687
3287
|
if inp.task_type == "implementation":
|
|
3688
3288
|
profile_rendered += "\n\n{{DESIGN_PREP_CONTEXT}}\n\n{{FIX_RUN_CONTEXT}}"
|
|
3289
|
+
profile_rendered = _implementation_stage.resolve_instruction_paths(
|
|
3290
|
+
profile_rendered, Path(ctx["WORKSPACE_ROOT"])
|
|
3291
|
+
)
|
|
3689
3292
|
profile_tokens = (
|
|
3690
3293
|
"EXECUTOR_WORKER_ID",
|
|
3691
3294
|
"EXECUTOR_PROVIDER",
|
|
@@ -3708,13 +3311,14 @@ def _write_instruction_set_sources(
|
|
|
3708
3311
|
profile_rendered = profile_rendered.replace("{{" + key + "}}", ctx.get(key, ""))
|
|
3709
3312
|
(instruction_set / "analysis-profile.md").write_text(profile_rendered, encoding="utf-8")
|
|
3710
3313
|
if inp.task_type == "implementation":
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3314
|
+
from .phases.catalog import profile_sidecar
|
|
3315
|
+
|
|
3316
|
+
executor_source = profile_sidecar(
|
|
3317
|
+
Path(ctx["WORKSPACE_ROOT"]), "prompts/profiles/_implementation-executor.md"
|
|
3318
|
+
)
|
|
3319
|
+
executor_rendered = _implementation_stage.resolve_instruction_paths(
|
|
3320
|
+
executor_source.read_text(encoding="utf-8"), Path(ctx["WORKSPACE_ROOT"])
|
|
3716
3321
|
)
|
|
3717
|
-
executor_rendered = executor_source.read_text(encoding="utf-8")
|
|
3718
3322
|
for key in profile_tokens:
|
|
3719
3323
|
executor_rendered = executor_rendered.replace(
|
|
3720
3324
|
"{{" + key + "}}", ctx.get(key, "")
|
|
@@ -3824,17 +3428,10 @@ def _write_instruction_set_sources(
|
|
|
3824
3428
|
),
|
|
3825
3429
|
)
|
|
3826
3430
|
if inp.task_type == "technical-verification":
|
|
3827
|
-
|
|
3828
|
-
|
|
3431
|
+
packet += materialize_technical_input_packet(
|
|
3432
|
+
ctx["TECHNICAL_VERIFICATION_INPUT_JSON"],
|
|
3829
3433
|
Path(ctx["RUN_DIR"]), ctx["RUN_MANIFESTS_SEQ"],
|
|
3830
3434
|
)
|
|
3831
|
-
packet += (
|
|
3832
|
-
"\n## Technical Verification Input\n\n"
|
|
3833
|
-
f"- Frozen input: `{verification_path}`\n"
|
|
3834
|
-
f"- Experiment copies only: `{ctx['RUN_DIR']}/experiments/{ctx['RUN_MANIFESTS_SEQ']}/<worker-id>/`\n"
|
|
3835
|
-
"- Read the input before planning experiments; copy each fact identity verbatim into results.\n"
|
|
3836
|
-
+ "\n```json\n" + ctx["TECHNICAL_VERIFICATION_INPUT_JSON"] + "\n```\n"
|
|
3837
|
-
)
|
|
3838
3435
|
if inp.task_type in ANALYSIS_TASK_TYPES:
|
|
3839
3436
|
packet += (
|
|
3840
3437
|
"\n## Analysis Evidence\n\n"
|
|
@@ -4145,7 +3742,7 @@ def _record_fix_cycle_events(inp: PrepareInputs, ctx: dict) -> str:
|
|
|
4145
3742
|
|
|
4146
3743
|
def _finalize_status_and_render_manifests(
|
|
4147
3744
|
inp: PrepareInputs, ctx: dict, task_index_template: Path,
|
|
4148
|
-
|
|
3745
|
+
boundary: dict[str, str],
|
|
4149
3746
|
) -> None:
|
|
4150
3747
|
"""최종 task/run status 를 확정하고 workflow state 를 재계산한 뒤 team-state,
|
|
4151
3748
|
task-manifest, task-index, run-manifest, timeline, discovery 산출물을 렌더한다."""
|
|
@@ -4162,7 +3759,7 @@ def _finalize_status_and_render_manifests(
|
|
|
4162
3759
|
current_run_status=ctx["CURRENT_RUN_STATUS"],
|
|
4163
3760
|
current_task_status=ctx["CURRENT_TASK_STATUS"],
|
|
4164
3761
|
render_only=inp.render_only,
|
|
4165
|
-
|
|
3762
|
+
boundary=boundary,
|
|
4166
3763
|
work_category=inp.work_category,
|
|
4167
3764
|
))
|
|
4168
3765
|
render_team_state(ctx["TEAM_STATE_PATH"], ctx)
|
|
@@ -4453,7 +4050,7 @@ def _reconcile_prior_runs(ctx: dict) -> None:
|
|
|
4453
4050
|
|
|
4454
4051
|
|
|
4455
4052
|
def _initial_workflow_ctx(
|
|
4456
|
-
inp: PrepareInputs,
|
|
4053
|
+
inp: PrepareInputs, boundary: dict
|
|
4457
4054
|
) -> dict[str, str]:
|
|
4458
4055
|
"""Workflow tokens for a run that has been prepared but not started.
|
|
4459
4056
|
|
|
@@ -4470,7 +4067,7 @@ def _initial_workflow_ctx(
|
|
|
4470
4067
|
current_run_status=run_status,
|
|
4471
4068
|
current_task_status=task_status,
|
|
4472
4069
|
render_only=inp.render_only,
|
|
4473
|
-
|
|
4070
|
+
boundary=boundary,
|
|
4474
4071
|
),
|
|
4475
4072
|
}
|
|
4476
4073
|
|
|
@@ -4595,7 +4192,7 @@ def _provision_task_and_stage(
|
|
|
4595
4192
|
okstra_home(), inp.project_id, task_group_segment, task_id_segment,
|
|
4596
4193
|
):
|
|
4597
4194
|
if inp.task_type == "final-verification" and inp.stage and inp.stage != "auto":
|
|
4598
|
-
worktree =
|
|
4195
|
+
worktree = single_stage_worktree()
|
|
4599
4196
|
# Single-stage final-verification namespaces its run path under
|
|
4600
4197
|
# runs/final-verification/stage-<N> (same isolation as
|
|
4601
4198
|
# implementation) so concurrent per-stage verifications never
|
|
@@ -4824,16 +4421,11 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
4824
4421
|
technical_input = None
|
|
4825
4422
|
from .paths import task_dir
|
|
4826
4423
|
if inp.task_type == "technical-verification":
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
task_dir(project_root, inp.task_group, inp.task_id),
|
|
4833
|
-
f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
|
|
4834
|
-
)
|
|
4835
|
-
except ValueError as exc:
|
|
4836
|
-
raise PrepareError(str(exc)) from exc
|
|
4424
|
+
technical_input = prepare_technical_verification_input(
|
|
4425
|
+
inp.clarification_response_path, project_root,
|
|
4426
|
+
task_dir(project_root, inp.task_group, inp.task_id),
|
|
4427
|
+
f"{inp.project_id}:{inp.task_group}:{inp.task_id}",
|
|
4428
|
+
)
|
|
4837
4429
|
if inp.task_type == "implementation":
|
|
4838
4430
|
ctx_stage_map = _prepare_implementation_approved_plan(inp)
|
|
4839
4431
|
|
|
@@ -4841,11 +4433,13 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
4841
4433
|
# 이후의 모든 brief 소비 경로(material/instruction-set 복사)는 그대로 동작한다.
|
|
4842
4434
|
handoff_tokens = {"HANDOFF_STAGES": ""}
|
|
4843
4435
|
if inp.task_type == "release-handoff":
|
|
4844
|
-
handoff_tokens =
|
|
4845
|
-
workspace_root, project_root, inp)
|
|
4436
|
+
handoff_tokens = materialize_handoff_input(
|
|
4437
|
+
workspace_root, project_root, inp, install_hint=_INSTALL_HINT)
|
|
4846
4438
|
|
|
4847
4439
|
workers, selected_reviewers = _resolve_roster(inp, profile_file)
|
|
4848
|
-
pr_template_path_str, pr_template_source =
|
|
4440
|
+
pr_template_path_str, pr_template_source = (
|
|
4441
|
+
resolve_pr_template(inp) if inp.task_type == "release-handoff" else ("", "")
|
|
4442
|
+
)
|
|
4849
4443
|
|
|
4850
4444
|
assignment_context, assignment_plan = _resolve_prepare_assignments(
|
|
4851
4445
|
inp=inp,
|
|
@@ -5006,7 +4600,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
5006
4600
|
if inp.approved_plan_path else ""
|
|
5007
4601
|
)
|
|
5008
4602
|
|
|
5009
|
-
|
|
4603
|
+
boundary = load_phase_boundary(workspace_root, inp.task_type)
|
|
5010
4604
|
|
|
5011
4605
|
# ---- assemble full ctx (the values render functions expect) ----
|
|
5012
4606
|
ctx.update({
|
|
@@ -5032,7 +4626,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
5032
4626
|
"BRIEF_RELATIVE_PATH": brief_relative,
|
|
5033
4627
|
**_model_ctx(models),
|
|
5034
4628
|
**_related_tasks_ctx(ctx, inp),
|
|
5035
|
-
**_initial_workflow_ctx(inp,
|
|
4629
|
+
**_initial_workflow_ctx(inp, boundary),
|
|
5036
4630
|
"VALIDATION_STATUS": "not-run",
|
|
5037
4631
|
"VALIDATION_UPDATED_AT": "",
|
|
5038
4632
|
"VALIDATION_FAILURES_JSON": "[]",
|
|
@@ -5053,10 +4647,26 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
5053
4647
|
ctx["CLAUDE_RESUME_COMMAND_RELATIVE_PATH"] = ""
|
|
5054
4648
|
ctx["CLAUDE_RESUME_COMMAND_FILENAME"] = ""
|
|
5055
4649
|
if inp.task_type == "final-verification":
|
|
5056
|
-
acquisition =
|
|
5057
|
-
|
|
4650
|
+
acquisition = acquire_final_verification_target(
|
|
4651
|
+
verification_target_request(
|
|
4652
|
+
project_root=inp.project_root,
|
|
4653
|
+
project_id=inp.project_id,
|
|
4654
|
+
task_group=inp.task_group,
|
|
4655
|
+
task_id=inp.task_id,
|
|
4656
|
+
work_category=inp.work_category,
|
|
4657
|
+
approved_plan_path=inp.approved_plan_path,
|
|
4658
|
+
stage=inp.stage,
|
|
4659
|
+
stage_map=ctx_stage_map,
|
|
4660
|
+
)
|
|
4661
|
+
)
|
|
4662
|
+
target = acquisition.target
|
|
4663
|
+
diff_stat = _git_out(
|
|
4664
|
+
target.worktree_path,
|
|
4665
|
+
"diff",
|
|
4666
|
+
"--stat",
|
|
4667
|
+
f"{target.base}..{target.head}",
|
|
5058
4668
|
)
|
|
5059
|
-
|
|
4669
|
+
apply_verification_target(ctx, acquisition, diff_stat)
|
|
5060
4670
|
|
|
5061
4671
|
_write_bundle_artifacts(
|
|
5062
4672
|
inp, ctx, project_root, lead_runtime, claude_session_id,
|
|
@@ -5083,7 +4693,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
5083
4693
|
|
|
5084
4694
|
# ---- final status + manifest/discovery renders ----
|
|
5085
4695
|
_finalize_status_and_render_manifests(
|
|
5086
|
-
inp, ctx, task_index_template,
|
|
4696
|
+
inp, ctx, task_index_template, boundary
|
|
5087
4697
|
)
|
|
5088
4698
|
persist_static_execution_identity(
|
|
5089
4699
|
run_manifest_path=Path(ctx["RUN_MANIFEST_PATH"]),
|