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
|
@@ -12,7 +12,6 @@ from okstra_ctl.analysis_inputs import (
|
|
|
12
12
|
AnalysisReportCandidate,
|
|
13
13
|
list_evidence_candidates,
|
|
14
14
|
load_analysis_report_candidate,
|
|
15
|
-
resolve_analysis_target,
|
|
16
15
|
resolve_evidence_inputs,
|
|
17
16
|
)
|
|
18
17
|
from okstra_ctl.design_prep import (
|
|
@@ -25,13 +24,9 @@ from okstra_ctl.design_prep import (
|
|
|
25
24
|
from .ids import (
|
|
26
25
|
PICK_SKIP,
|
|
27
26
|
PICK_TYPE_CUSTOM,
|
|
28
|
-
S_ANALYSIS_TARGET,
|
|
29
|
-
S_ANALYSIS_TARGET_PICK,
|
|
30
27
|
S_DESIGN_PREP_CONFIRM,
|
|
31
28
|
S_DESIGN_PREP_DECISION,
|
|
32
29
|
S_DESIGN_PREP_OVERRIDES,
|
|
33
|
-
S_FEATURE_EVIDENCE,
|
|
34
|
-
S_FEATURE_EVIDENCE_PICK,
|
|
35
30
|
S_PROJECT_EVIDENCE,
|
|
36
31
|
S_PROJECT_EVIDENCE_PICK,
|
|
37
32
|
_RESUME_REUSE_PHASES,
|
|
@@ -128,32 +123,6 @@ def _submit_analysis_evidence_pick(
|
|
|
128
123
|
return t["echo_variants"]["selected"].format(path=candidate.report_path)
|
|
129
124
|
|
|
130
125
|
|
|
131
|
-
def _build_feature_evidence_pick(state: WizardState) -> Prompt:
|
|
132
|
-
return _build_analysis_evidence_pick(
|
|
133
|
-
state, step=S_FEATURE_EVIDENCE_PICK, relation="feature-baseline"
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def _submit_feature_evidence_pick(state: WizardState, value: str) -> Optional[str]:
|
|
138
|
-
return _submit_analysis_evidence_pick(
|
|
139
|
-
state, value, step=S_FEATURE_EVIDENCE_PICK,
|
|
140
|
-
field_name="feature_evidence_path",
|
|
141
|
-
pending_name="feature_evidence_pending_text",
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _build_feature_evidence(state: WizardState) -> Prompt:
|
|
146
|
-
t = _p(state.workspace_root, S_FEATURE_EVIDENCE)
|
|
147
|
-
return Prompt(step=S_FEATURE_EVIDENCE, kind="text", label=t["label"],
|
|
148
|
-
echo_template=t["echo_template"])
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _submit_feature_evidence(state: WizardState, value: str) -> Optional[str]:
|
|
152
|
-
candidate = _accept_analysis_evidence(state, value, "feature_evidence_path")
|
|
153
|
-
state.feature_evidence_pending_text = False
|
|
154
|
-
return f"feature-evidence: {candidate.report_path}"
|
|
155
|
-
|
|
156
|
-
|
|
157
126
|
def _build_project_evidence_pick(state: WizardState) -> Prompt:
|
|
158
127
|
return _build_analysis_evidence_pick(
|
|
159
128
|
state, step=S_PROJECT_EVIDENCE_PICK, relation="project-context"
|
|
@@ -181,72 +150,6 @@ def _submit_project_evidence(state: WizardState, value: str) -> Optional[str]:
|
|
|
181
150
|
return f"project-evidence: {candidate.report_path}"
|
|
182
151
|
|
|
183
152
|
|
|
184
|
-
def _feature_description(feature: dict[object, object]) -> str:
|
|
185
|
-
name = str(feature.get("name") or "")
|
|
186
|
-
summary = str(feature.get("summary") or "")
|
|
187
|
-
entry_point = str(
|
|
188
|
-
feature.get("entryPoint") or feature.get("entrypoint") or ""
|
|
189
|
-
)
|
|
190
|
-
return " · ".join(value for value in (name, summary, entry_point) if value)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def _build_analysis_target_pick(state: WizardState) -> Prompt:
|
|
194
|
-
t = _p(state.workspace_root, S_ANALYSIS_TARGET_PICK)
|
|
195
|
-
try:
|
|
196
|
-
candidate = load_analysis_report_candidate(
|
|
197
|
-
Path(state.project_root), Path(state.project_evidence_path)
|
|
198
|
-
)
|
|
199
|
-
except AnalysisInputError as exc:
|
|
200
|
-
raise WizardError(str(exc)) from exc
|
|
201
|
-
options = [
|
|
202
|
-
_opt(str(feature["id"]), str(feature["id"]), _feature_description(feature))
|
|
203
|
-
for feature in candidate.feature_index
|
|
204
|
-
if isinstance(feature, dict) and isinstance(feature.get("id"), str)
|
|
205
|
-
][:3]
|
|
206
|
-
options.append(_opt(PICK_TYPE_CUSTOM, t["options"][PICK_TYPE_CUSTOM]))
|
|
207
|
-
return Prompt(step=S_ANALYSIS_TARGET_PICK, kind="pick", label=t["label"],
|
|
208
|
-
options=options, echo_template=t["echo_template"])
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def _accept_analysis_target(state: WizardState, value: str) -> str:
|
|
212
|
-
candidates: dict[Path, AnalysisReportCandidate] = {}
|
|
213
|
-
for path in _analysis_evidence_paths(state):
|
|
214
|
-
try:
|
|
215
|
-
candidate = load_analysis_report_candidate(Path(state.project_root), path)
|
|
216
|
-
except AnalysisInputError as exc:
|
|
217
|
-
raise WizardError(str(exc)) from exc
|
|
218
|
-
candidates[candidate.report_path] = candidate
|
|
219
|
-
try:
|
|
220
|
-
target = resolve_analysis_target(
|
|
221
|
-
value, _resolve_analysis_evidence(state), candidates
|
|
222
|
-
)
|
|
223
|
-
except AnalysisInputError as exc:
|
|
224
|
-
raise WizardError(str(exc)) from exc
|
|
225
|
-
requested_value = target["requestedValue"]
|
|
226
|
-
if not isinstance(requested_value, str):
|
|
227
|
-
raise WizardError("analysis target resolver returned an invalid requestedValue")
|
|
228
|
-
state.analysis_target = requested_value
|
|
229
|
-
state.analysis_target_pending_text = False
|
|
230
|
-
return requested_value
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _submit_analysis_target_pick(state: WizardState, value: str) -> Optional[str]:
|
|
234
|
-
if value == PICK_TYPE_CUSTOM:
|
|
235
|
-
state.analysis_target_pending_text = True
|
|
236
|
-
return None
|
|
237
|
-
return f"analysis-target: {_accept_analysis_target(state, value)}"
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _build_analysis_target(state: WizardState) -> Prompt:
|
|
241
|
-
t = _p(state.workspace_root, S_ANALYSIS_TARGET)
|
|
242
|
-
return Prompt(step=S_ANALYSIS_TARGET, kind="text", label=t["label"],
|
|
243
|
-
echo_template=t["echo_template"])
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def _submit_analysis_target(state: WizardState, value: str) -> Optional[str]:
|
|
247
|
-
return f"analysis-target: {_accept_analysis_target(state, value)}"
|
|
248
|
-
|
|
249
|
-
|
|
250
153
|
def _design_prep_items_by_id(state: WizardState) -> dict[str, dict[str, Any]]:
|
|
251
154
|
try:
|
|
252
155
|
items = load_design_prep_items(Path(state.approved_plan_path))
|
|
@@ -3,18 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from collections.abc import Mapping
|
|
7
6
|
from typing import Optional
|
|
8
7
|
|
|
9
8
|
from okstra_ctl.clarification_items import scan_approval_gate
|
|
10
9
|
from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object
|
|
11
10
|
from okstra_ctl.incremental_scope import CARRY_ALL_SCOPE
|
|
12
|
-
from okstra_ctl.implementation_direction import (
|
|
13
|
-
DirectionSelectionError,
|
|
14
|
-
lexical_absolute_path,
|
|
15
|
-
resolve_selected_direction,
|
|
16
|
-
validate_task_artifact_path,
|
|
17
|
-
)
|
|
18
11
|
from okstra_ctl.final_report_paths import final_report_data_path
|
|
19
12
|
from okstra_ctl.run import (
|
|
20
13
|
PrepareError,
|
|
@@ -24,191 +17,24 @@ from okstra_ctl.run import (
|
|
|
24
17
|
_reject_blocking_plan_body_gate,
|
|
25
18
|
_set_data_json_approved_true_if_present,
|
|
26
19
|
)
|
|
20
|
+
from okstra_ctl.phases.implementation.wizard import _impl_stage_marker, _impl_chosen_stages
|
|
27
21
|
from okstra_ctl.stage_map import StageMapError, parse_stage_map_file, stage_map_records
|
|
28
22
|
from okstra_ctl.user_response import PlanDecisionRecord, parse_plan_decision
|
|
29
23
|
from okstra_ctl.wizard_stage_intent import WHOLE_TASK_STAGE
|
|
30
24
|
from okstra_ctl import fix_cycles
|
|
31
|
-
from okstra_ctl.paths import task_dir
|
|
25
|
+
from okstra_ctl.paths import task_dir
|
|
26
|
+
from okstra_ctl.phases.final_verification.wizard import (
|
|
27
|
+
StageAnswerError,
|
|
28
|
+
validate_stage_answer,
|
|
29
|
+
whole_task_verification_allowed,
|
|
30
|
+
)
|
|
32
31
|
from okstra_project.state import read_task_manifest
|
|
33
32
|
|
|
34
|
-
from .ids import
|
|
35
|
-
ALL_STAGES,
|
|
36
|
-
PICK_OTHER,
|
|
37
|
-
PICK_TYPE_CUSTOM,
|
|
38
|
-
PICK_USE_DEFAULT,
|
|
39
|
-
SCOPE_SHAPE_LINKED,
|
|
40
|
-
SCOPE_SHAPE_NO_ANSWERS,
|
|
41
|
-
SCOPE_SHAPE_UNLINKED,
|
|
42
|
-
S_APPROVED_PLAN,
|
|
43
|
-
S_APPROVED_PLAN_PICK,
|
|
44
|
-
S_APPROVE_PLAN_CONFIRM,
|
|
45
|
-
S_FIX_CYCLE_CONFIRM,
|
|
46
|
-
S_HANDOFF_STAGE_PICK,
|
|
47
|
-
S_REVERIFY_SCOPE_PICK,
|
|
48
|
-
S_REVERIFY_SCOPE_STAGES,
|
|
49
|
-
S_SELECTED_DIRECTION_PICK,
|
|
50
|
-
S_STAGE_PICK,
|
|
51
|
-
_REPORT_PREFIX,
|
|
52
|
-
)
|
|
33
|
+
from .ids import ALL_STAGES, PICK_OTHER, PICK_TYPE_CUSTOM, PICK_USE_DEFAULT, SCOPE_SHAPE_LINKED, SCOPE_SHAPE_NO_ANSWERS, SCOPE_SHAPE_UNLINKED, S_APPROVED_PLAN, S_APPROVED_PLAN_PICK, S_APPROVE_PLAN_CONFIRM, S_FIX_CYCLE_CONFIRM, S_REVERIFY_SCOPE_PICK, S_REVERIFY_SCOPE_STAGES, S_STAGE_PICK, _REPORT_PREFIX
|
|
53
34
|
from .state import Option, Prompt, WizardError, WizardState
|
|
54
35
|
from .prompts import _opt, _p
|
|
55
|
-
from .sources import
|
|
56
|
-
_latest_implementation_planning_report,
|
|
57
|
-
_list_implementation_planning_reports,
|
|
58
|
-
_parse_stage_objects,
|
|
59
|
-
_prior_stage_numbers,
|
|
60
|
-
_project_relative_path,
|
|
61
|
-
_require_file,
|
|
62
|
-
_reserved_stage_numbers,
|
|
63
|
-
_reverify_scope_pick_required,
|
|
64
|
-
_reverify_scope_preview,
|
|
65
|
-
_reverify_scope_shape,
|
|
66
|
-
_stage_lifecycle_snapshot,
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# 후보는 리포트 **기록**으로 찾는다. 전체 열람본(`.md`)은 `2bcd575` 이후 완료
|
|
71
|
-
# 산출물이 아니라 요청 시 렌더라, `.md` 를 글롭하는 동안 이 목록은 항상 비었고
|
|
72
|
-
# 방향 선택 단계 자체가 뜨지 않았다 — 비교가 끝나도 계획으로 갈 길이 없었다.
|
|
73
|
-
_SELECTION_REPORT_RE = re.compile(
|
|
74
|
-
r"^final-report-implementation-option-selection-(?P<seq>\d{3,})\.data\.json$"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _selected_direction_candidates(state: WizardState) -> list[str]:
|
|
79
|
-
if not state.project_root or not state.task_group or not state.task_id:
|
|
80
|
-
return []
|
|
81
|
-
project_root = Path(state.project_root).resolve()
|
|
82
|
-
task_root = lexical_absolute_path(
|
|
83
|
-
task_dir(project_root, state.task_group, state.task_id)
|
|
84
|
-
)
|
|
85
|
-
reports = (
|
|
86
|
-
task_runs_dir(project_root, state.task_group, state.task_id)
|
|
87
|
-
/ "implementation-option-selection"
|
|
88
|
-
/ "reports"
|
|
89
|
-
)
|
|
90
|
-
candidates: list[tuple[int, Path]] = []
|
|
91
|
-
for report in reports.glob(
|
|
92
|
-
"final-report-implementation-option-selection-*.data.json"
|
|
93
|
-
):
|
|
94
|
-
match = _SELECTION_REPORT_RE.fullmatch(report.name)
|
|
95
|
-
if match is None:
|
|
96
|
-
continue
|
|
97
|
-
try:
|
|
98
|
-
validated = validate_task_artifact_path(
|
|
99
|
-
report, task_root, "selection report"
|
|
100
|
-
)
|
|
101
|
-
except DirectionSelectionError:
|
|
102
|
-
continue
|
|
103
|
-
candidates.append((int(match.group("seq")), validated))
|
|
104
|
-
return [
|
|
105
|
-
_project_relative_path(path, project_root)
|
|
106
|
-
for _, path in sorted(candidates, reverse=True)[:3]
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _planning_rerun_selected(state: WizardState) -> bool:
|
|
111
|
-
if (
|
|
112
|
-
not state.clarification_response_path
|
|
113
|
-
or not state.project_root
|
|
114
|
-
or not state.task_group
|
|
115
|
-
or not state.task_id
|
|
116
|
-
):
|
|
117
|
-
return False
|
|
118
|
-
project_root = Path(state.project_root).resolve()
|
|
119
|
-
raw_path = Path(state.clarification_response_path).expanduser()
|
|
120
|
-
path = lexical_absolute_path(
|
|
121
|
-
raw_path if raw_path.is_absolute() else project_root / raw_path
|
|
122
|
-
)
|
|
123
|
-
task_root = lexical_absolute_path(
|
|
124
|
-
task_dir(project_root, state.task_group, state.task_id)
|
|
125
|
-
)
|
|
126
|
-
reports = lexical_absolute_path(
|
|
127
|
-
task_runs_dir(project_root, state.task_group, state.task_id)
|
|
128
|
-
/ "implementation-planning"
|
|
129
|
-
/ "reports"
|
|
130
|
-
)
|
|
131
|
-
try:
|
|
132
|
-
validate_task_artifact_path(path, task_root, "planning report")
|
|
133
|
-
except DirectionSelectionError:
|
|
134
|
-
return False
|
|
135
|
-
return (
|
|
136
|
-
path.is_file()
|
|
137
|
-
and not path.is_symlink()
|
|
138
|
-
and re.fullmatch(
|
|
139
|
-
r"final-report-implementation-planning-\d{3,}\.(?:md|data\.json)", path.name
|
|
140
|
-
)
|
|
141
|
-
is not None
|
|
142
|
-
and path.parent == reports
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def _build_selected_direction_pick(state: WizardState) -> Prompt:
|
|
147
|
-
candidates = _selected_direction_candidates(state)
|
|
148
|
-
t = _p(state.workspace_root, S_SELECTED_DIRECTION_PICK)
|
|
149
|
-
if not candidates:
|
|
150
|
-
raise WizardError(t["errors"]["none"])
|
|
151
|
-
return Prompt(
|
|
152
|
-
step=S_SELECTED_DIRECTION_PICK,
|
|
153
|
-
kind="pick",
|
|
154
|
-
label=t["label"],
|
|
155
|
-
options=[
|
|
156
|
-
_opt(path, _selection_option_label(state, path, t))
|
|
157
|
-
for path in candidates
|
|
158
|
-
],
|
|
159
|
-
echo_template=t["echo_template"],
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def _selection_option_label(state: WizardState, rel_path: str, t: dict) -> str:
|
|
164
|
-
"""후보 경로에 그 리포트가 실제로 무엇을 담았는지 붙인다.
|
|
36
|
+
from .sources import _latest_implementation_planning_report, _list_implementation_planning_reports, _parse_stage_objects, _prior_stage_numbers, _require_file, _reserved_stage_numbers, _reverify_scope_pick_required, _reverify_scope_preview, _reverify_scope_shape, _stage_lifecycle_snapshot
|
|
165
37
|
|
|
166
|
-
목록은 최신순이라 후보가 0건인 최근 재실행이 맨 위에 온다. 경로만 보이면
|
|
167
|
-
사용자가 그것을 고르고, 방향 없는 리포트로 계획을 열려다 막힌다.
|
|
168
|
-
"""
|
|
169
|
-
record = Path(state.project_root) / rel_path
|
|
170
|
-
try:
|
|
171
|
-
data = load_owned_object(record, artifact="implementation option data")
|
|
172
|
-
except (OSError, JsonBoundaryError):
|
|
173
|
-
return rel_path
|
|
174
|
-
selection = data.get("implementationOptionSelection")
|
|
175
|
-
if not isinstance(selection, Mapping):
|
|
176
|
-
return rel_path
|
|
177
|
-
rows = selection.get("rankedOptions")
|
|
178
|
-
ids = [
|
|
179
|
-
str(row.get("id"))
|
|
180
|
-
for row in rows
|
|
181
|
-
if isinstance(row, Mapping) and row.get("id")
|
|
182
|
-
] if isinstance(rows, list) else []
|
|
183
|
-
if not ids:
|
|
184
|
-
return t["labels"]["no_options"].format(path=rel_path)
|
|
185
|
-
return t["labels"]["options"].format(path=rel_path, ids=", ".join(ids))
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def _submit_selected_direction_pick(
|
|
189
|
-
state: WizardState, value: str
|
|
190
|
-
) -> Optional[str]:
|
|
191
|
-
t = _p(state.workspace_root, S_SELECTED_DIRECTION_PICK)
|
|
192
|
-
candidates = _selected_direction_candidates(state)
|
|
193
|
-
if value not in candidates:
|
|
194
|
-
raise WizardError(t["errors"]["unknown"].format(value=value))
|
|
195
|
-
try:
|
|
196
|
-
resolve_selected_direction(
|
|
197
|
-
Path(state.project_root) / value,
|
|
198
|
-
expected_task_key=f"{state.project_id}:{state.task_group}:{state.task_id}",
|
|
199
|
-
)
|
|
200
|
-
except DirectionSelectionError as exc:
|
|
201
|
-
raise WizardError(str(exc)) from exc
|
|
202
|
-
state.selected_direction_path = value
|
|
203
|
-
# 두 입력은 상호 배타다 — 방향이 정해진 순간 이 런은 새 계획이고,
|
|
204
|
-
# clarification 자리에 남은 값은 render-bundle 이 거절할 이유일 뿐이다.
|
|
205
|
-
# 거절을 여기서 앞당기는 대신 값을 비우고, 무엇이 비워졌는지 echo 로
|
|
206
|
-
# 알린다. 사용자 답변은 손실되지 않는다: 그 사이드카는
|
|
207
|
-
# `--selected-direction` 경로로 계획 런에 첨부된다(`run.py` 참조).
|
|
208
|
-
if state.clarification_response_path:
|
|
209
|
-
state.clarification_response_path = ""
|
|
210
|
-
return t["echo_variants"]["cleared_clarification"].format(value=value)
|
|
211
|
-
return f"selected-direction: {value}"
|
|
212
38
|
|
|
213
39
|
|
|
214
40
|
def _classify_approved_plan(path_str: str, project_root: Path) -> tuple[Path, bool]:
|
|
@@ -422,7 +248,10 @@ def _whole_task_allowed(
|
|
|
422
248
|
return False
|
|
423
249
|
if done is None:
|
|
424
250
|
done = _stage_lifecycle_snapshot(state, stages).done_stages
|
|
425
|
-
return
|
|
251
|
+
return whole_task_verification_allowed(
|
|
252
|
+
stage_numbers=tuple(stage.stage_number for stage in stages),
|
|
253
|
+
done_stages=done,
|
|
254
|
+
)
|
|
426
255
|
|
|
427
256
|
|
|
428
257
|
def _build_approved_plan_pick(state: WizardState) -> Prompt:
|
|
@@ -608,35 +437,19 @@ def _build_stage_pick(state: WizardState) -> Prompt:
|
|
|
608
437
|
# Presentation keys are mapped explicitly rather than interpolated from the
|
|
609
438
|
# status, so renaming a Stage Lifecycle status is a compile-time-visible edit
|
|
610
439
|
# here instead of a KeyError raised while drawing the picker.
|
|
611
|
-
_STAGE_MARKER_KEYS = {
|
|
612
|
-
"done": "mark_done",
|
|
613
|
-
"active": "mark_active",
|
|
614
|
-
"ready": "mark_ready",
|
|
615
|
-
"blocked": "mark_blocked",
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
def _impl_stage_marker(t, lifecycle) -> str:
|
|
620
|
-
return t["options"][_STAGE_MARKER_KEYS[lifecycle.status]]
|
|
621
440
|
|
|
622
441
|
|
|
623
442
|
def _submit_stage_pick(state: WizardState, answer: str) -> Optional[str]:
|
|
624
443
|
if state.task_type == "implementation":
|
|
625
444
|
return _submit_impl_stage_pick(state, answer)
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
else:
|
|
635
|
-
try:
|
|
636
|
-
int(answer)
|
|
637
|
-
except ValueError:
|
|
638
|
-
raise WizardError(
|
|
639
|
-
f"answer must be whole-task or a stage number, got {answer!r}")
|
|
445
|
+
try:
|
|
446
|
+
validate_stage_answer(
|
|
447
|
+
answer,
|
|
448
|
+
whole_task_token=WHOLE_TASK_STAGE,
|
|
449
|
+
whole_task_allowed=answer == WHOLE_TASK_STAGE and _whole_task_allowed(state),
|
|
450
|
+
)
|
|
451
|
+
except StageAnswerError as exc:
|
|
452
|
+
raise WizardError(str(exc)) from exc
|
|
640
453
|
state.selected_stage = answer
|
|
641
454
|
return f"stage: {answer}"
|
|
642
455
|
|
|
@@ -668,34 +481,6 @@ def _submit_impl_stage_pick(state: WizardState, answer: str) -> Optional[str]:
|
|
|
668
481
|
return t["echo_variants"]["plain"].format(stages=state.selected_stages)
|
|
669
482
|
|
|
670
483
|
|
|
671
|
-
def _impl_chosen_stages(t, picks, answer, all_nums, occupied) -> set:
|
|
672
|
-
if ALL_STAGES in picks:
|
|
673
|
-
if len(picks) > 1:
|
|
674
|
-
raise WizardError(t["errors"]["all_exclusive"])
|
|
675
|
-
chosen = {n for n in all_nums if n not in occupied}
|
|
676
|
-
if not chosen:
|
|
677
|
-
raise WizardError(t["errors"]["nothing_selectable"])
|
|
678
|
-
return chosen
|
|
679
|
-
try:
|
|
680
|
-
nums = {int(p) for p in picks}
|
|
681
|
-
except ValueError:
|
|
682
|
-
raise WizardError(t["errors"]["bad_number"].format(answer=answer))
|
|
683
|
-
unknown = sorted(nums - all_nums)
|
|
684
|
-
if unknown:
|
|
685
|
-
raise WizardError(t["errors"]["unknown_stage"].format(
|
|
686
|
-
bad=", ".join(map(str, unknown))))
|
|
687
|
-
bad = sorted(nums & occupied)
|
|
688
|
-
if bad:
|
|
689
|
-
raise WizardError(t["errors"]["occupied"].format(
|
|
690
|
-
bad=", ".join(map(str, bad))))
|
|
691
|
-
return nums
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
def _handoff_msgs(state: WizardState) -> dict:
|
|
695
|
-
"""handoff_stage_pick 의 JSON 텍스트 묶음 (label 미사용 조회용)."""
|
|
696
|
-
return _p(state.workspace_root, "handoff_stage_pick", blocked="")
|
|
697
|
-
|
|
698
|
-
|
|
699
484
|
def _resolve_handoff_plan(state: WizardState) -> Path:
|
|
700
485
|
"""release-handoff 의 approved plan 을 질문 없이 자동 해소한다.
|
|
701
486
|
|
|
@@ -717,7 +502,7 @@ def _resolve_handoff_plan(state: WizardState) -> Path:
|
|
|
717
502
|
|
|
718
503
|
def _handoff_eligibility(state: WizardState) -> list:
|
|
719
504
|
"""stage 별 PR 자격 — okstra_ctl.handoff 의 SSOT 판정을 그대로 재사용한다."""
|
|
720
|
-
from okstra_ctl.
|
|
505
|
+
from okstra_ctl.phases.release_handoff.operations import compute_eligibility
|
|
721
506
|
from okstra_ctl.consumers import read_consumers
|
|
722
507
|
plan = _resolve_handoff_plan(state)
|
|
723
508
|
try:
|
|
@@ -731,53 +516,17 @@ def _handoff_eligibility(state: WizardState) -> list:
|
|
|
731
516
|
# 자격 stage 전체를 한 번에 고르는 값. stage 번호는 숫자뿐이라 충돌하지 않는다.
|
|
732
517
|
# CLI 는 `--stages` 를 비우면 전체를 쓰지만, 위저드 pick 은 빈 선택을 거절하므로
|
|
733
518
|
# 그 뜻을 표현할 값이 따로 필요하다.
|
|
734
|
-
|
|
519
|
+
from okstra_ctl.phases.release_handoff.wizard import (
|
|
520
|
+
_handoff_msgs, build_handoff_stage_pick, submit_handoff_stage_pick,
|
|
521
|
+
)
|
|
735
522
|
|
|
736
523
|
|
|
737
524
|
def _build_handoff_stage_pick(state: WizardState) -> Prompt:
|
|
738
|
-
|
|
739
|
-
eligible = [e for e in elig if e["eligible"]]
|
|
740
|
-
blocked = [e for e in elig if not e["eligible"]]
|
|
741
|
-
msgs = _handoff_msgs(state)
|
|
742
|
-
blocked_summary = ("; ".join(
|
|
743
|
-
f"stage {e['stage']} ({', '.join(e['reasons'])})" for e in blocked)
|
|
744
|
-
or msgs["labels"]["blocked_none"])
|
|
745
|
-
if not eligible:
|
|
746
|
-
raise WizardError(
|
|
747
|
-
msgs["errors"]["nothing_eligible"].format(blocked=blocked_summary))
|
|
748
|
-
t = _p(state.workspace_root, "handoff_stage_pick", blocked=blocked_summary)
|
|
749
|
-
stage_label = t["labels"]["stage"]
|
|
750
|
-
options: list[Option] = []
|
|
751
|
-
if len(eligible) > 1:
|
|
752
|
-
options.append(_opt(ALL_ELIGIBLE_STAGES, t["labels"]["all_stages"].format(
|
|
753
|
-
stages=", ".join(str(e["stage"]) for e in eligible))))
|
|
754
|
-
for e in eligible:
|
|
755
|
-
deps = ", ".join(str(d) for d in e["depends_on"]) or "-"
|
|
756
|
-
options.append(_opt(str(e["stage"]),
|
|
757
|
-
stage_label.format(stage=e["stage"], deps=deps)))
|
|
758
|
-
return Prompt(
|
|
759
|
-
step=S_HANDOFF_STAGE_PICK, kind="pick", multi=True,
|
|
760
|
-
label=t["label"], options=options,
|
|
761
|
-
echo_template=t["echo_template"],
|
|
762
|
-
)
|
|
525
|
+
return build_handoff_stage_pick(state, _handoff_eligibility(state))
|
|
763
526
|
|
|
764
527
|
|
|
765
528
|
def _submit_handoff_stage_pick(state: WizardState, value: str) -> Optional[str]:
|
|
766
|
-
|
|
767
|
-
picks = [v.strip() for v in (value or "").split(",") if v.strip()]
|
|
768
|
-
if not picks:
|
|
769
|
-
raise WizardError(t["errors"]["none_selected"])
|
|
770
|
-
eligible = {str(e["stage"]) for e in _handoff_eligibility(state)
|
|
771
|
-
if e["eligible"]}
|
|
772
|
-
if ALL_ELIGIBLE_STAGES in picks:
|
|
773
|
-
picks = sorted(eligible, key=int)
|
|
774
|
-
bad = [p for p in picks if p not in eligible]
|
|
775
|
-
if bad:
|
|
776
|
-
raise WizardError(t["errors"]["not_eligible"].format(
|
|
777
|
-
bad=", ".join(bad), eligible=", ".join(sorted(eligible))))
|
|
778
|
-
nums = sorted({int(p) for p in picks})
|
|
779
|
-
state.handoff_stages = ",".join(str(n) for n in nums)
|
|
780
|
-
return t["echo_variants"]["stages"].format(stages=state.handoff_stages)
|
|
529
|
+
return submit_handoff_stage_pick(state, value, lambda: _handoff_eligibility(state))
|
|
781
530
|
|
|
782
531
|
|
|
783
532
|
def _reverify_scope_step_pending(state: WizardState) -> bool:
|
|
@@ -12,7 +12,8 @@ from okstra_ctl.domain.host import (
|
|
|
12
12
|
from okstra_ctl.registry.host_registry import default_host_registry
|
|
13
13
|
from okstra_ctl.registry.provider_registry import default_provider_registry
|
|
14
14
|
from okstra_ctl.ids import slugify_task_segment
|
|
15
|
-
from okstra_ctl.analysis_inputs import AnalysisInputError
|
|
15
|
+
from okstra_ctl.analysis_inputs import AnalysisInputError
|
|
16
|
+
from okstra_ctl.phases.feature_analysis.entry import resolve_analysis_target
|
|
16
17
|
from okstra_ctl.models import (
|
|
17
18
|
UnknownModelError,
|
|
18
19
|
UnknownProviderError,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
profile / validators / wizard 는 이 모듈에서 enum 을 import 해야 한다. 다른 곳에서
|
|
4
4
|
재정의하면 single-reference-point 위반이며 tests/test_work_categories.py 가 거부한다.
|
|
5
5
|
|
|
6
|
-
다섯째 값은 `ops` 가 canonical 이다(`--work-category` CLI help 및
|
|
6
|
+
다섯째 값은 `ops` 가 canonical 이다(`--work-category` CLI help 및 phases.catalog
|
|
7
7
|
와 일치). 구버전 비표준 철자는 폐기된다.
|
|
8
8
|
"""
|
|
9
9
|
from __future__ import annotations
|
|
@@ -23,6 +23,7 @@ from .worker_prompt_policy import (
|
|
|
23
23
|
PromptPlan,
|
|
24
24
|
is_plan_verify_dispatch_kind,
|
|
25
25
|
resolve_prompt_plan_for_manifest,
|
|
26
|
+
verification_dispatch_round,
|
|
26
27
|
)
|
|
27
28
|
from .worker_prompt_headers import EVIDENCE_LEDGER_HEADER
|
|
28
29
|
|
|
@@ -367,6 +368,41 @@ def validate_reverify_prompt(
|
|
|
367
368
|
return errors
|
|
368
369
|
|
|
369
370
|
|
|
371
|
+
def validate_plan_verify_dispatch_identity(
|
|
372
|
+
text: str, *, dispatch_kind: str, result_name: str,
|
|
373
|
+
) -> list[str]:
|
|
374
|
+
"""Tie a plan item queue to the dispatch kind and result name validate-run counts.
|
|
375
|
+
|
|
376
|
+
`validate_reverify_prompt` only runs for a verification kind, so a queue
|
|
377
|
+
rendered by `okstra plan-items prompt` under any other kind skipped every
|
|
378
|
+
check and dispatched. validate-run then counts plan-body votes only from
|
|
379
|
+
`plan-verify-r<N>` rows whose result file carries `-plan-verify-r<N>-`
|
|
380
|
+
(`_plan_verify_dispatched_workers`), so the round's votes surfaced later as
|
|
381
|
+
unbacked. Observed 2026-09-24 (jobs dev-9065 implementation-planning 001):
|
|
382
|
+
kinds `plan-body-r1` / `plan-body-r2`, results `*-plan-body-r2-*.md`.
|
|
383
|
+
"""
|
|
384
|
+
normalized = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
385
|
+
if not is_plan_verify_dispatch_kind(dispatch_kind):
|
|
386
|
+
instructions = normalized[_task_instructions_offset(normalized):]
|
|
387
|
+
if PLAN_VERIFY_RENDERED_BY_LINE not in instructions:
|
|
388
|
+
return []
|
|
389
|
+
return [
|
|
390
|
+
"the instruction is an `okstra plan-items prompt` queue but the "
|
|
391
|
+
f"dispatch kind is `{dispatch_kind}` — a plan-body round takes "
|
|
392
|
+
"--dispatch-kind plan-verify-r<N>, matching the round in its result "
|
|
393
|
+
"file name; validate-run counts no plan-body vote from any other kind"
|
|
394
|
+
]
|
|
395
|
+
token = f"-plan-verify-r{verification_dispatch_round(dispatch_kind)}-"
|
|
396
|
+
if token in result_name:
|
|
397
|
+
return []
|
|
398
|
+
return [
|
|
399
|
+
f"`{dispatch_kind}` result file `{result_name}` does not carry `{token}` "
|
|
400
|
+
"— name it <role>-worker-plan-verify-r<N>-<task-type>-<seq>.md with the "
|
|
401
|
+
"same <N> as the dispatch kind; validate-run counts no plan-body vote "
|
|
402
|
+
"from another result name"
|
|
403
|
+
]
|
|
404
|
+
|
|
405
|
+
|
|
370
406
|
def complete_reverify_instruction(
|
|
371
407
|
body: str, *, task_type: str, forbidden_actions: str,
|
|
372
408
|
model: str, output_contract: str,
|