okstra 0.206.0 → 0.207.0
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 +3 -3
- package/dist/cli-registry.mjs +7 -1
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +1 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -0
- package/docs/architecture.md +40 -16
- package/docs/cli.md +17 -15
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +43 -20
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/operations/code-review.json +1 -1
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/bin/okstra-compact-reminder.sh +1 -1
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/duties/direction-selection-worker.json +1 -1
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/adapters/cmux.md +4 -3
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +44 -12
- package/runtime/prompts/lead/okstra-lead-contract.md +44 -73
- package/runtime/prompts/lead/phase-routing.md +64 -0
- package/runtime/prompts/lead/report-writer.md +10 -8
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +4 -4
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/prompts/wizard/prompts.ko.json +2 -1
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +5 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +17 -26
- package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +183 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +60 -10
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/jobs.py +21 -4
- 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/approval_decisions.py +32 -2
- package/runtime/python/okstra_ctl/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +8 -0
- package/runtime/python/okstra_ctl/blocking_checks.py +7 -0
- package/runtime/python/okstra_ctl/code_review_target.py +92 -6
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/dispatch_checkpoints.py +121 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +54 -32
- package/runtime/python/okstra_ctl/dispatch_state.py +34 -5
- package/runtime/python/okstra_ctl/doctor.py +2 -1
- package/runtime/python/okstra_ctl/domain/provider.py +5 -0
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -2
- package/runtime/python/okstra_ctl/domain/write_policy.py +2 -1
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +46 -9
- 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 +0 -477
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +13 -1
- package/runtime/python/okstra_ctl/lead_progress.py +33 -1
- package/runtime/python/okstra_ctl/manager_view.py +26 -19
- package/runtime/python/okstra_ctl/model_io/lines.py +21 -4
- package/runtime/python/okstra_ctl/models.py +4 -1
- package/runtime/python/okstra_ctl/operation_invocation.py +11 -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 +8 -6
- package/runtime/python/okstra_ctl/phases/catalog.py +56 -12
- 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/runtime/python/okstra_ctl/phases/error_analysis/spec.md +118 -0
- 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/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -2
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/spec.md +1 -1
- 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 +18 -7
- 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/runtime/python/okstra_ctl/phases/implementation/spec.md +238 -0
- 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} +3 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +83 -0
- 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/runtime/python/okstra_ctl/phases/implementation_planning/spec.md +204 -0
- 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/runtime/python/okstra_ctl/phases/release_handoff/spec.md +233 -0
- 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/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +132 -0
- 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} +2 -2
- 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/process_group.py +118 -0
- package/runtime/python/okstra_ctl/profile_show.py +3 -3
- package/runtime/python/okstra_ctl/render.py +15 -4
- package/runtime/python/okstra_ctl/report_assembly.py +28 -92
- package/runtime/python/okstra_ctl/report_finalize.py +106 -2
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- 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_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +1 -1
- package/runtime/python/okstra_ctl/run.py +68 -350
- package/runtime/python/okstra_ctl/run_artifact_prune.py +200 -0
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/team.py +108 -9
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- 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/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_options.py +8 -0
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +10 -263
- 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_dispatch.py +44 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -0
- package/runtime/python/okstra_ctl/worker_runner.py +21 -3
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/python/okstra_ctl/write_policy.py +57 -7
- package/runtime/python/okstra_project/dirs.py +14 -0
- package/runtime/python/okstra_project/resolver.py +2 -1
- package/runtime/schemas/execution-manifest-v2.schema.json +2 -1
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-code-review/SKILL.md +70 -32
- package/runtime/skills/okstra-code-review/references/review-calibration.md +26 -6
- package/runtime/skills/okstra-run/SKILL.md +3 -3
- package/runtime/templates/manager/view.template.html +18 -1
- 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 +299 -3940
- package/runtime/validators/validate_analysis_report.py +14 -126
- 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 → 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))
|
|
@@ -206,12 +206,20 @@ def _suggest_sibling_task_ids(state: WizardState) -> str:
|
|
|
206
206
|
return ",".join(siblings)
|
|
207
207
|
|
|
208
208
|
|
|
209
|
+
def _sibling_count_note(state: WizardState, suggestion: str) -> str:
|
|
210
|
+
"""라벨은 CSV 앞부분만 보여 주므로, 선택하면 몇 개가 들어가는지 따로 적는다."""
|
|
211
|
+
count = len([tid for tid in suggestion.split(",") if tid])
|
|
212
|
+
note = _p(state.workspace_root, "related_tasks_pick")["labels"]["siblings_count"]
|
|
213
|
+
return note.format(count=count)
|
|
214
|
+
|
|
215
|
+
|
|
209
216
|
_RELATED_TASKS_PICK_SPEC = _OptionalCachedPickSpec(
|
|
210
217
|
step=S_RELATED_TASKS_PICK, prompt_key="related_tasks_pick",
|
|
211
218
|
recommend_token=_SIBLINGS_TOKEN, label_key="siblings", echo_suffix_key="siblings",
|
|
212
219
|
suggest=_suggest_sibling_task_ids, snippet_style="prefix",
|
|
213
220
|
cache_attr="last_siblings_cached", target_attr="related_tasks_raw",
|
|
214
221
|
pending_attr="related_tasks_pending_text",
|
|
222
|
+
recommend_note=_sibling_count_note,
|
|
215
223
|
)
|
|
216
224
|
|
|
217
225
|
|
|
@@ -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,11 +17,12 @@ 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
|
|
32
26
|
from okstra_ctl.phases.final_verification.wizard import (
|
|
33
27
|
StageAnswerError,
|
|
34
28
|
validate_stage_answer,
|
|
@@ -36,184 +30,11 @@ from okstra_ctl.phases.final_verification.wizard import (
|
|
|
36
30
|
)
|
|
37
31
|
from okstra_project.state import read_task_manifest
|
|
38
32
|
|
|
39
|
-
from .ids import
|
|
40
|
-
ALL_STAGES,
|
|
41
|
-
PICK_OTHER,
|
|
42
|
-
PICK_TYPE_CUSTOM,
|
|
43
|
-
PICK_USE_DEFAULT,
|
|
44
|
-
SCOPE_SHAPE_LINKED,
|
|
45
|
-
SCOPE_SHAPE_NO_ANSWERS,
|
|
46
|
-
SCOPE_SHAPE_UNLINKED,
|
|
47
|
-
S_APPROVED_PLAN,
|
|
48
|
-
S_APPROVED_PLAN_PICK,
|
|
49
|
-
S_APPROVE_PLAN_CONFIRM,
|
|
50
|
-
S_FIX_CYCLE_CONFIRM,
|
|
51
|
-
S_HANDOFF_STAGE_PICK,
|
|
52
|
-
S_REVERIFY_SCOPE_PICK,
|
|
53
|
-
S_REVERIFY_SCOPE_STAGES,
|
|
54
|
-
S_SELECTED_DIRECTION_PICK,
|
|
55
|
-
S_STAGE_PICK,
|
|
56
|
-
_REPORT_PREFIX,
|
|
57
|
-
)
|
|
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
|
|
58
34
|
from .state import Option, Prompt, WizardError, WizardState
|
|
59
35
|
from .prompts import _opt, _p
|
|
60
|
-
from .sources import
|
|
61
|
-
_latest_implementation_planning_report,
|
|
62
|
-
_list_implementation_planning_reports,
|
|
63
|
-
_parse_stage_objects,
|
|
64
|
-
_prior_stage_numbers,
|
|
65
|
-
_project_relative_path,
|
|
66
|
-
_require_file,
|
|
67
|
-
_reserved_stage_numbers,
|
|
68
|
-
_reverify_scope_pick_required,
|
|
69
|
-
_reverify_scope_preview,
|
|
70
|
-
_reverify_scope_shape,
|
|
71
|
-
_stage_lifecycle_snapshot,
|
|
72
|
-
)
|
|
73
|
-
|
|
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
|
|
74
37
|
|
|
75
|
-
# 후보는 리포트 **기록**으로 찾는다. 전체 열람본(`.md`)은 `2bcd575` 이후 완료
|
|
76
|
-
# 산출물이 아니라 요청 시 렌더라, `.md` 를 글롭하는 동안 이 목록은 항상 비었고
|
|
77
|
-
# 방향 선택 단계 자체가 뜨지 않았다 — 비교가 끝나도 계획으로 갈 길이 없었다.
|
|
78
|
-
_SELECTION_REPORT_RE = re.compile(
|
|
79
|
-
r"^final-report-implementation-option-selection-(?P<seq>\d{3,})\.data\.json$"
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def _selected_direction_candidates(state: WizardState) -> list[str]:
|
|
84
|
-
if not state.project_root or not state.task_group or not state.task_id:
|
|
85
|
-
return []
|
|
86
|
-
project_root = Path(state.project_root).resolve()
|
|
87
|
-
task_root = lexical_absolute_path(
|
|
88
|
-
task_dir(project_root, state.task_group, state.task_id)
|
|
89
|
-
)
|
|
90
|
-
reports = (
|
|
91
|
-
task_runs_dir(project_root, state.task_group, state.task_id)
|
|
92
|
-
/ "implementation-option-selection"
|
|
93
|
-
/ "reports"
|
|
94
|
-
)
|
|
95
|
-
candidates: list[tuple[int, Path]] = []
|
|
96
|
-
for report in reports.glob(
|
|
97
|
-
"final-report-implementation-option-selection-*.data.json"
|
|
98
|
-
):
|
|
99
|
-
match = _SELECTION_REPORT_RE.fullmatch(report.name)
|
|
100
|
-
if match is None:
|
|
101
|
-
continue
|
|
102
|
-
try:
|
|
103
|
-
validated = validate_task_artifact_path(
|
|
104
|
-
report, task_root, "selection report"
|
|
105
|
-
)
|
|
106
|
-
except DirectionSelectionError:
|
|
107
|
-
continue
|
|
108
|
-
candidates.append((int(match.group("seq")), validated))
|
|
109
|
-
return [
|
|
110
|
-
_project_relative_path(path, project_root)
|
|
111
|
-
for _, path in sorted(candidates, reverse=True)[:3]
|
|
112
|
-
]
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _planning_rerun_selected(state: WizardState) -> bool:
|
|
116
|
-
if (
|
|
117
|
-
not state.clarification_response_path
|
|
118
|
-
or not state.project_root
|
|
119
|
-
or not state.task_group
|
|
120
|
-
or not state.task_id
|
|
121
|
-
):
|
|
122
|
-
return False
|
|
123
|
-
project_root = Path(state.project_root).resolve()
|
|
124
|
-
raw_path = Path(state.clarification_response_path).expanduser()
|
|
125
|
-
path = lexical_absolute_path(
|
|
126
|
-
raw_path if raw_path.is_absolute() else project_root / raw_path
|
|
127
|
-
)
|
|
128
|
-
task_root = lexical_absolute_path(
|
|
129
|
-
task_dir(project_root, state.task_group, state.task_id)
|
|
130
|
-
)
|
|
131
|
-
reports = lexical_absolute_path(
|
|
132
|
-
task_runs_dir(project_root, state.task_group, state.task_id)
|
|
133
|
-
/ "implementation-planning"
|
|
134
|
-
/ "reports"
|
|
135
|
-
)
|
|
136
|
-
try:
|
|
137
|
-
validate_task_artifact_path(path, task_root, "planning report")
|
|
138
|
-
except DirectionSelectionError:
|
|
139
|
-
return False
|
|
140
|
-
return (
|
|
141
|
-
path.is_file()
|
|
142
|
-
and not path.is_symlink()
|
|
143
|
-
and re.fullmatch(
|
|
144
|
-
r"final-report-implementation-planning-\d{3,}\.(?:md|data\.json)", path.name
|
|
145
|
-
)
|
|
146
|
-
is not None
|
|
147
|
-
and path.parent == reports
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _build_selected_direction_pick(state: WizardState) -> Prompt:
|
|
152
|
-
candidates = _selected_direction_candidates(state)
|
|
153
|
-
t = _p(state.workspace_root, S_SELECTED_DIRECTION_PICK)
|
|
154
|
-
if not candidates:
|
|
155
|
-
raise WizardError(t["errors"]["none"])
|
|
156
|
-
return Prompt(
|
|
157
|
-
step=S_SELECTED_DIRECTION_PICK,
|
|
158
|
-
kind="pick",
|
|
159
|
-
label=t["label"],
|
|
160
|
-
options=[
|
|
161
|
-
_opt(path, _selection_option_label(state, path, t))
|
|
162
|
-
for path in candidates
|
|
163
|
-
],
|
|
164
|
-
echo_template=t["echo_template"],
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def _selection_option_label(state: WizardState, rel_path: str, t: dict) -> str:
|
|
169
|
-
"""후보 경로에 그 리포트가 실제로 무엇을 담았는지 붙인다.
|
|
170
|
-
|
|
171
|
-
목록은 최신순이라 후보가 0건인 최근 재실행이 맨 위에 온다. 경로만 보이면
|
|
172
|
-
사용자가 그것을 고르고, 방향 없는 리포트로 계획을 열려다 막힌다.
|
|
173
|
-
"""
|
|
174
|
-
record = Path(state.project_root) / rel_path
|
|
175
|
-
try:
|
|
176
|
-
data = load_owned_object(record, artifact="implementation option data")
|
|
177
|
-
except (OSError, JsonBoundaryError):
|
|
178
|
-
return rel_path
|
|
179
|
-
selection = data.get("implementationOptionSelection")
|
|
180
|
-
if not isinstance(selection, Mapping):
|
|
181
|
-
return rel_path
|
|
182
|
-
rows = selection.get("rankedOptions")
|
|
183
|
-
ids = [
|
|
184
|
-
str(row.get("id"))
|
|
185
|
-
for row in rows
|
|
186
|
-
if isinstance(row, Mapping) and row.get("id")
|
|
187
|
-
] if isinstance(rows, list) else []
|
|
188
|
-
if not ids:
|
|
189
|
-
return t["labels"]["no_options"].format(path=rel_path)
|
|
190
|
-
return t["labels"]["options"].format(path=rel_path, ids=", ".join(ids))
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def _submit_selected_direction_pick(
|
|
194
|
-
state: WizardState, value: str
|
|
195
|
-
) -> Optional[str]:
|
|
196
|
-
t = _p(state.workspace_root, S_SELECTED_DIRECTION_PICK)
|
|
197
|
-
candidates = _selected_direction_candidates(state)
|
|
198
|
-
if value not in candidates:
|
|
199
|
-
raise WizardError(t["errors"]["unknown"].format(value=value))
|
|
200
|
-
try:
|
|
201
|
-
resolve_selected_direction(
|
|
202
|
-
Path(state.project_root) / value,
|
|
203
|
-
expected_task_key=f"{state.project_id}:{state.task_group}:{state.task_id}",
|
|
204
|
-
)
|
|
205
|
-
except DirectionSelectionError as exc:
|
|
206
|
-
raise WizardError(str(exc)) from exc
|
|
207
|
-
state.selected_direction_path = value
|
|
208
|
-
# 두 입력은 상호 배타다 — 방향이 정해진 순간 이 런은 새 계획이고,
|
|
209
|
-
# clarification 자리에 남은 값은 render-bundle 이 거절할 이유일 뿐이다.
|
|
210
|
-
# 거절을 여기서 앞당기는 대신 값을 비우고, 무엇이 비워졌는지 echo 로
|
|
211
|
-
# 알린다. 사용자 답변은 손실되지 않는다: 그 사이드카는
|
|
212
|
-
# `--selected-direction` 경로로 계획 런에 첨부된다(`run.py` 참조).
|
|
213
|
-
if state.clarification_response_path:
|
|
214
|
-
state.clarification_response_path = ""
|
|
215
|
-
return t["echo_variants"]["cleared_clarification"].format(value=value)
|
|
216
|
-
return f"selected-direction: {value}"
|
|
217
38
|
|
|
218
39
|
|
|
219
40
|
def _classify_approved_plan(path_str: str, project_root: Path) -> tuple[Path, bool]:
|
|
@@ -616,16 +437,6 @@ def _build_stage_pick(state: WizardState) -> Prompt:
|
|
|
616
437
|
# Presentation keys are mapped explicitly rather than interpolated from the
|
|
617
438
|
# status, so renaming a Stage Lifecycle status is a compile-time-visible edit
|
|
618
439
|
# here instead of a KeyError raised while drawing the picker.
|
|
619
|
-
_STAGE_MARKER_KEYS = {
|
|
620
|
-
"done": "mark_done",
|
|
621
|
-
"active": "mark_active",
|
|
622
|
-
"ready": "mark_ready",
|
|
623
|
-
"blocked": "mark_blocked",
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
def _impl_stage_marker(t, lifecycle) -> str:
|
|
628
|
-
return t["options"][_STAGE_MARKER_KEYS[lifecycle.status]]
|
|
629
440
|
|
|
630
441
|
|
|
631
442
|
def _submit_stage_pick(state: WizardState, answer: str) -> Optional[str]:
|
|
@@ -670,34 +481,6 @@ def _submit_impl_stage_pick(state: WizardState, answer: str) -> Optional[str]:
|
|
|
670
481
|
return t["echo_variants"]["plain"].format(stages=state.selected_stages)
|
|
671
482
|
|
|
672
483
|
|
|
673
|
-
def _impl_chosen_stages(t, picks, answer, all_nums, occupied) -> set:
|
|
674
|
-
if ALL_STAGES in picks:
|
|
675
|
-
if len(picks) > 1:
|
|
676
|
-
raise WizardError(t["errors"]["all_exclusive"])
|
|
677
|
-
chosen = {n for n in all_nums if n not in occupied}
|
|
678
|
-
if not chosen:
|
|
679
|
-
raise WizardError(t["errors"]["nothing_selectable"])
|
|
680
|
-
return chosen
|
|
681
|
-
try:
|
|
682
|
-
nums = {int(p) for p in picks}
|
|
683
|
-
except ValueError:
|
|
684
|
-
raise WizardError(t["errors"]["bad_number"].format(answer=answer))
|
|
685
|
-
unknown = sorted(nums - all_nums)
|
|
686
|
-
if unknown:
|
|
687
|
-
raise WizardError(t["errors"]["unknown_stage"].format(
|
|
688
|
-
bad=", ".join(map(str, unknown))))
|
|
689
|
-
bad = sorted(nums & occupied)
|
|
690
|
-
if bad:
|
|
691
|
-
raise WizardError(t["errors"]["occupied"].format(
|
|
692
|
-
bad=", ".join(map(str, bad))))
|
|
693
|
-
return nums
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
def _handoff_msgs(state: WizardState) -> dict:
|
|
697
|
-
"""handoff_stage_pick 의 JSON 텍스트 묶음 (label 미사용 조회용)."""
|
|
698
|
-
return _p(state.workspace_root, "handoff_stage_pick", blocked="")
|
|
699
|
-
|
|
700
|
-
|
|
701
484
|
def _resolve_handoff_plan(state: WizardState) -> Path:
|
|
702
485
|
"""release-handoff 의 approved plan 을 질문 없이 자동 해소한다.
|
|
703
486
|
|
|
@@ -719,7 +502,7 @@ def _resolve_handoff_plan(state: WizardState) -> Path:
|
|
|
719
502
|
|
|
720
503
|
def _handoff_eligibility(state: WizardState) -> list:
|
|
721
504
|
"""stage 별 PR 자격 — okstra_ctl.handoff 의 SSOT 판정을 그대로 재사용한다."""
|
|
722
|
-
from okstra_ctl.
|
|
505
|
+
from okstra_ctl.phases.release_handoff.operations import compute_eligibility
|
|
723
506
|
from okstra_ctl.consumers import read_consumers
|
|
724
507
|
plan = _resolve_handoff_plan(state)
|
|
725
508
|
try:
|
|
@@ -733,53 +516,17 @@ def _handoff_eligibility(state: WizardState) -> list:
|
|
|
733
516
|
# 자격 stage 전체를 한 번에 고르는 값. stage 번호는 숫자뿐이라 충돌하지 않는다.
|
|
734
517
|
# CLI 는 `--stages` 를 비우면 전체를 쓰지만, 위저드 pick 은 빈 선택을 거절하므로
|
|
735
518
|
# 그 뜻을 표현할 값이 따로 필요하다.
|
|
736
|
-
|
|
519
|
+
from okstra_ctl.phases.release_handoff.wizard import (
|
|
520
|
+
_handoff_msgs, build_handoff_stage_pick, submit_handoff_stage_pick,
|
|
521
|
+
)
|
|
737
522
|
|
|
738
523
|
|
|
739
524
|
def _build_handoff_stage_pick(state: WizardState) -> Prompt:
|
|
740
|
-
|
|
741
|
-
eligible = [e for e in elig if e["eligible"]]
|
|
742
|
-
blocked = [e for e in elig if not e["eligible"]]
|
|
743
|
-
msgs = _handoff_msgs(state)
|
|
744
|
-
blocked_summary = ("; ".join(
|
|
745
|
-
f"stage {e['stage']} ({', '.join(e['reasons'])})" for e in blocked)
|
|
746
|
-
or msgs["labels"]["blocked_none"])
|
|
747
|
-
if not eligible:
|
|
748
|
-
raise WizardError(
|
|
749
|
-
msgs["errors"]["nothing_eligible"].format(blocked=blocked_summary))
|
|
750
|
-
t = _p(state.workspace_root, "handoff_stage_pick", blocked=blocked_summary)
|
|
751
|
-
stage_label = t["labels"]["stage"]
|
|
752
|
-
options: list[Option] = []
|
|
753
|
-
if len(eligible) > 1:
|
|
754
|
-
options.append(_opt(ALL_ELIGIBLE_STAGES, t["labels"]["all_stages"].format(
|
|
755
|
-
stages=", ".join(str(e["stage"]) for e in eligible))))
|
|
756
|
-
for e in eligible:
|
|
757
|
-
deps = ", ".join(str(d) for d in e["depends_on"]) or "-"
|
|
758
|
-
options.append(_opt(str(e["stage"]),
|
|
759
|
-
stage_label.format(stage=e["stage"], deps=deps)))
|
|
760
|
-
return Prompt(
|
|
761
|
-
step=S_HANDOFF_STAGE_PICK, kind="pick", multi=True,
|
|
762
|
-
label=t["label"], options=options,
|
|
763
|
-
echo_template=t["echo_template"],
|
|
764
|
-
)
|
|
525
|
+
return build_handoff_stage_pick(state, _handoff_eligibility(state))
|
|
765
526
|
|
|
766
527
|
|
|
767
528
|
def _submit_handoff_stage_pick(state: WizardState, value: str) -> Optional[str]:
|
|
768
|
-
|
|
769
|
-
picks = [v.strip() for v in (value or "").split(",") if v.strip()]
|
|
770
|
-
if not picks:
|
|
771
|
-
raise WizardError(t["errors"]["none_selected"])
|
|
772
|
-
eligible = {str(e["stage"]) for e in _handoff_eligibility(state)
|
|
773
|
-
if e["eligible"]}
|
|
774
|
-
if ALL_ELIGIBLE_STAGES in picks:
|
|
775
|
-
picks = sorted(eligible, key=int)
|
|
776
|
-
bad = [p for p in picks if p not in eligible]
|
|
777
|
-
if bad:
|
|
778
|
-
raise WizardError(t["errors"]["not_eligible"].format(
|
|
779
|
-
bad=", ".join(bad), eligible=", ".join(sorted(eligible))))
|
|
780
|
-
nums = sorted({int(p) for p in picks})
|
|
781
|
-
state.handoff_stages = ",".join(str(n) for n in nums)
|
|
782
|
-
return t["echo_variants"]["stages"].format(stages=state.handoff_stages)
|
|
529
|
+
return submit_handoff_stage_pick(state, value, lambda: _handoff_eligibility(state))
|
|
783
530
|
|
|
784
531
|
|
|
785
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
|
|
@@ -10,12 +10,18 @@ from typing import Any, Mapping, Sequence
|
|
|
10
10
|
from . import dispatch_core
|
|
11
11
|
from .adapters.dispatch.cli_wrapper import CliWrapperDispatchPort
|
|
12
12
|
from .application.dispatch_assignments import dispatch_assignments
|
|
13
|
-
from .
|
|
13
|
+
from .dispatch_checkpoints import (
|
|
14
|
+
record_collect_checkpoints,
|
|
15
|
+
record_dispatch_checkpoints,
|
|
16
|
+
settled_initial_dispatches,
|
|
17
|
+
)
|
|
18
|
+
from .dispatch_state import DispatchError, load_json_object
|
|
14
19
|
from .models import provider_wrappers
|
|
15
20
|
from .ports.worker_dispatch import WorkerDispatchRequest
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
SUPPORTED_CLI_WORKERS = provider_wrappers("analyser")
|
|
24
|
+
_SOURCE = "okstra worker-dispatch"
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
@@ -38,14 +44,42 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
38
44
|
if args.dry_run:
|
|
39
45
|
_print_json(_backend_payload(backend_plan, dry_run=True))
|
|
40
46
|
return 0
|
|
41
|
-
code =
|
|
42
|
-
_print_json({
|
|
47
|
+
code, lines = _dispatch_recording_checkpoints(backend_plan)
|
|
48
|
+
_print_json({
|
|
49
|
+
**_backend_payload(backend_plan, dry_run=False),
|
|
50
|
+
"exitCode": code,
|
|
51
|
+
"progressLines": lines,
|
|
52
|
+
})
|
|
43
53
|
return code
|
|
44
54
|
except DispatchError as exc:
|
|
45
55
|
print(f"error: {exc}", file=sys.stderr)
|
|
46
56
|
return 2
|
|
47
57
|
|
|
48
58
|
|
|
59
|
+
def _dispatch_recording_checkpoints(backend_plan) -> tuple[int, list[str]]:
|
|
60
|
+
if not backend_plan.jobs:
|
|
61
|
+
return dispatch_core.dispatch_cli_wrapper_plan(backend_plan), []
|
|
62
|
+
before = load_json_object(backend_plan.team_state_path, "team-state")
|
|
63
|
+
lines: list[str] = []
|
|
64
|
+
code = dispatch_core.dispatch_cli_wrapper_plan(
|
|
65
|
+
backend_plan,
|
|
66
|
+
before_start=lambda prepared: lines.extend(record_dispatch_checkpoints(
|
|
67
|
+
prepared.project_root, prepared.manifest_path,
|
|
68
|
+
load_json_object(prepared.team_state_path, "team-state"),
|
|
69
|
+
prepared.jobs, source=_SOURCE,
|
|
70
|
+
)),
|
|
71
|
+
)
|
|
72
|
+
lines.extend(record_collect_checkpoints(
|
|
73
|
+
backend_plan.project_root, backend_plan.manifest_path,
|
|
74
|
+
settled_initial_dispatches(before),
|
|
75
|
+
settled_initial_dispatches(
|
|
76
|
+
load_json_object(backend_plan.team_state_path, "team-state")
|
|
77
|
+
),
|
|
78
|
+
source=_SOURCE,
|
|
79
|
+
))
|
|
80
|
+
return code, lines
|
|
81
|
+
|
|
82
|
+
|
|
49
83
|
def _dispatch_request(args: argparse.Namespace) -> WorkerDispatchRequest:
|
|
50
84
|
return WorkerDispatchRequest(
|
|
51
85
|
project_root=Path(args.project_root),
|
|
@@ -75,6 +109,13 @@ whose persisted runner is 'cli-wrapper'. It verifies each invocation contract
|
|
|
75
109
|
before execution and uses the persisted provider, model, and registered CLI.
|
|
76
110
|
Native-session assignments remain owned by the active host session.
|
|
77
111
|
|
|
112
|
+
It records the lead checkpoints it owns and prints them as `progressLines`:
|
|
113
|
+
`phase-3-team-create` when it writes the implicit-team marker,
|
|
114
|
+
`phase-4-dispatch` for each `initial` job, `phase-6-synthesis` on the first
|
|
115
|
+
report-writer dispatch, and `phase-5-collect` for each `initial` dispatch it
|
|
116
|
+
settled. The lead emits those lines instead of calling
|
|
117
|
+
`okstra lead-progress append` for them.
|
|
118
|
+
|
|
78
119
|
Missing worker prompt files are generated automatically from the immutable run snapshot.
|
|
79
120
|
Existing prompt and metadata files are never overwritten.
|
|
80
121
|
When report-writer completes, this command also runs the idempotent post-report
|
|
@@ -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,
|
|
@@ -35,6 +35,10 @@ ERRORS_PATH_HEADERS = (
|
|
|
35
35
|
# itself: the lead's launch prompt carries them, and no worker reads that.
|
|
36
36
|
APPROVED_PLAN_HEADER = "**Approved plan:**"
|
|
37
37
|
IMPLEMENTATION_STAGE_HEADER = "**Stage for this implementation run:**"
|
|
38
|
+
# A stage's verifiers share one worktree and run as one batch. The Tier 3 script
|
|
39
|
+
# (builds, fixed ports) and the self-mock mutation probe (edits sources in place)
|
|
40
|
+
# must each run once, by the verifier named here.
|
|
41
|
+
STAGE_QA_OWNER_HEADER = "**Stage QA owner:**"
|
|
38
42
|
IMPLEMENTATION_HEADERS = (
|
|
39
43
|
"**Worktree:**",
|
|
40
44
|
APPROVED_PLAN_HEADER,
|
|
@@ -295,6 +299,21 @@ def worker_assignment(
|
|
|
295
299
|
return None
|
|
296
300
|
|
|
297
301
|
|
|
302
|
+
def stage_qa_owner(manifest: Mapping[str, Any]) -> str:
|
|
303
|
+
"""The first verifier in roster order, or '' when the roster has none.
|
|
304
|
+
|
|
305
|
+
Derived from the manifest alone: prompts are published once and reused, and
|
|
306
|
+
the lead may materialize or re-dispatch one verifier at a time, so an owner
|
|
307
|
+
taken from the dispatch batch could disagree with a published prompt.
|
|
308
|
+
"""
|
|
309
|
+
roster = manifest.get("recommendedWorkers")
|
|
310
|
+
for worker_id in roster if isinstance(roster, list) else []:
|
|
311
|
+
row = worker_assignment(manifest, worker_id) if isinstance(worker_id, str) else None
|
|
312
|
+
if row is not None and row.get("role") == "verifier":
|
|
313
|
+
return worker_id
|
|
314
|
+
return ""
|
|
315
|
+
|
|
316
|
+
|
|
298
317
|
def _plan(
|
|
299
318
|
audience: PromptAudience,
|
|
300
319
|
*,
|