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
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""run 이 끝난 뒤 읽는 곳이 없는 산출물을 찾고 지운다.
|
|
2
|
+
|
|
3
|
+
- 의존성·빌드 디렉터리(`node_modules`, `.next`): technical-verification 워커는
|
|
4
|
+
`experiments/<seq>/<worker-id>/` 에 사이트 복사본을 만들고 그 안에서 설치·빌드한다.
|
|
5
|
+
증거는 계획·로그·lockfile·diff 이고, 설치·빌드 결과는 lockfile 로 다시 만든다. 태스크
|
|
6
|
+
하나에 `node_modules` 46개와 `.next` 16개(약 22GB)가 남은 사례가 있다(2026-09-27).
|
|
7
|
+
- dispatch 스냅샷(`*.mutation-audit.json`)과 프롬프트 발행 잠금(`*.publish.lock`): 워커
|
|
8
|
+
시도의 종결 판정과 같은 run 의 증거 복구만 읽는다. 결과는 시도 기록의 변경 요약에
|
|
9
|
+
남고, `validate-run` 은 경로 문자열만 본다. 검증을 통과한 run 과, 같은 run 디렉터리에서
|
|
10
|
+
더 뒤의 run 이 검증을 통과한 run 의 dispatch 기록이 가리키는 것만 지운다 — 가장 최근의
|
|
11
|
+
실패한 run 은 같은 run 안에서 워커를 다시 보낼 수 있다.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import shutil
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from okstra_project import resolve_project_root
|
|
24
|
+
|
|
25
|
+
from .fixed_text import line
|
|
26
|
+
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
27
|
+
from .paths import task_dir
|
|
28
|
+
|
|
29
|
+
PRUNED_DIR_NAMES = frozenset({"node_modules", ".next"})
|
|
30
|
+
_DISPATCH_ARRAYS = ("agentDispatches", "workerDispatches")
|
|
31
|
+
_MANIFEST_SEQ = re.compile(r"^run-manifest-.+-(\d{3,})\.json$")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _tree_bytes(root: Path) -> int:
|
|
35
|
+
total = 0
|
|
36
|
+
for current, _dirs, files in os.walk(root):
|
|
37
|
+
for name in files:
|
|
38
|
+
try:
|
|
39
|
+
total += os.lstat(os.path.join(current, name)).st_size
|
|
40
|
+
except OSError:
|
|
41
|
+
continue
|
|
42
|
+
return total
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def find_build_dirs(root: Path) -> list[dict[str, Any]]:
|
|
46
|
+
"""`root` 아래 의존성·빌드 디렉터리. 심볼릭 링크는 따라가지도 지우지도 않는다."""
|
|
47
|
+
found: list[dict[str, Any]] = []
|
|
48
|
+
# `.okstra` 아래는 `Path.glob("**")` 이 숨김 디렉터리를 건너뛰므로 os.walk 로 훑는다.
|
|
49
|
+
for current, dirs, _files in os.walk(root):
|
|
50
|
+
kept = []
|
|
51
|
+
for name in dirs:
|
|
52
|
+
path = Path(current) / name
|
|
53
|
+
if name in PRUNED_DIR_NAMES and not path.is_symlink():
|
|
54
|
+
found.append({"path": str(path), "bytes": _tree_bytes(path)})
|
|
55
|
+
else:
|
|
56
|
+
kept.append(name)
|
|
57
|
+
dirs[:] = kept
|
|
58
|
+
return sorted(found, key=lambda row: row["path"])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _resolve(project_root: Path, value: str) -> Path:
|
|
62
|
+
path = Path(value)
|
|
63
|
+
return path if path.is_absolute() else project_root / path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _settled_manifests(manifests_dir: Path, names: list[str]) -> list[dict[str, Any]]:
|
|
67
|
+
"""검증을 통과했거나, 같은 디렉터리의 더 뒤 run 이 통과한 run 매니페스트.
|
|
68
|
+
|
|
69
|
+
한 `manifests/` 의 seq 는 한 카운터에서 나오므로 숫자 순서가 실행 순서다.
|
|
70
|
+
"""
|
|
71
|
+
loaded: list[tuple[int, dict[str, Any]]] = []
|
|
72
|
+
for name in names:
|
|
73
|
+
match = _MANIFEST_SEQ.match(name)
|
|
74
|
+
if match is None:
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
manifest = load_owned_object(manifests_dir / name, artifact="run manifest")
|
|
78
|
+
except (OSError, JsonBoundaryError):
|
|
79
|
+
continue
|
|
80
|
+
loaded.append((int(match[1]), manifest))
|
|
81
|
+
passed = [
|
|
82
|
+
seq for seq, manifest in loaded
|
|
83
|
+
if (manifest.get("validation") or {}).get("status") == "passed"
|
|
84
|
+
]
|
|
85
|
+
if not passed:
|
|
86
|
+
return []
|
|
87
|
+
return [manifest for seq, manifest in loaded if seq <= max(passed)]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def find_dispatch_leftovers(project_root: Path, root: Path) -> list[dict[str, Any]]:
|
|
91
|
+
"""`root` 아래 끝난 run(`_settled_manifests`)의 dispatch 스냅샷과 발행 잠금."""
|
|
92
|
+
found: dict[str, int] = {}
|
|
93
|
+
for current, dirs, files in os.walk(root):
|
|
94
|
+
dirs[:] = [name for name in dirs if name not in PRUNED_DIR_NAMES]
|
|
95
|
+
if Path(current).name != "manifests":
|
|
96
|
+
continue
|
|
97
|
+
for manifest in _settled_manifests(Path(current), files):
|
|
98
|
+
try:
|
|
99
|
+
team_state = load_owned_object(
|
|
100
|
+
_resolve(project_root, str(manifest.get("teamStatePath") or "")),
|
|
101
|
+
artifact="team state",
|
|
102
|
+
)
|
|
103
|
+
except (OSError, JsonBoundaryError):
|
|
104
|
+
continue
|
|
105
|
+
for key in _DISPATCH_ARRAYS:
|
|
106
|
+
for row in team_state.get(key) or ():
|
|
107
|
+
if not isinstance(row, dict):
|
|
108
|
+
continue
|
|
109
|
+
candidates = [row.get("mutationAuditSnapshotPath")]
|
|
110
|
+
if row.get("promptPath"):
|
|
111
|
+
candidates.append(f"{row['promptPath']}.publish.lock")
|
|
112
|
+
for value in candidates:
|
|
113
|
+
if not isinstance(value, str) or not value:
|
|
114
|
+
continue
|
|
115
|
+
path = _resolve(project_root, value)
|
|
116
|
+
if path.is_file() and not path.is_symlink():
|
|
117
|
+
found[str(path)] = path.stat().st_size
|
|
118
|
+
return [{"path": path, "bytes": size} for path, size in sorted(found.items())]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def prune_run_artifacts(project_root: Path, root: Path, *, apply: bool) -> dict[str, Any]:
|
|
122
|
+
dirs = find_build_dirs(root) if root.is_dir() else []
|
|
123
|
+
files = find_dispatch_leftovers(project_root, root) if root.is_dir() else []
|
|
124
|
+
if apply:
|
|
125
|
+
for row in dirs:
|
|
126
|
+
shutil.rmtree(row["path"])
|
|
127
|
+
for row in files:
|
|
128
|
+
Path(row["path"]).unlink(missing_ok=True)
|
|
129
|
+
return {
|
|
130
|
+
"root": str(root),
|
|
131
|
+
"applied": apply,
|
|
132
|
+
"dirs": dirs,
|
|
133
|
+
"files": files,
|
|
134
|
+
"totalBytes": sum(row["bytes"] for row in (*dirs, *files)),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def render_result_text(result: dict[str, Any]) -> str:
|
|
139
|
+
parts = [
|
|
140
|
+
"# Okstra Run Artifact Prune\n\n",
|
|
141
|
+
line("Root", result.get("root")),
|
|
142
|
+
line("Applied", result.get("applied")),
|
|
143
|
+
line("Directory count", len(result.get("dirs") or [])),
|
|
144
|
+
line("File count", len(result.get("files") or [])),
|
|
145
|
+
line("Total bytes", result.get("totalBytes")),
|
|
146
|
+
]
|
|
147
|
+
for row in result.get("dirs") or []:
|
|
148
|
+
parts.extend(("\n## Directory\n\n", line("Path", row["path"]), line("Bytes", row["bytes"])))
|
|
149
|
+
return "".join(parts)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
_CLI_EPILOG = r"""Usage:
|
|
153
|
+
okstra prune-run-artifacts [--project-root <dir>] [--cwd <dir>]
|
|
154
|
+
[--task-group <group> --task-id <id>] [--apply] [--text]
|
|
155
|
+
|
|
156
|
+
Lists what no reader needs once a run has ended, under
|
|
157
|
+
<project-root>/.okstra/tasks (or one task root), with sizes and a total:
|
|
158
|
+
- every node_modules and .next directory (symbolic links are neither
|
|
159
|
+
followed nor removed);
|
|
160
|
+
- the dispatch snapshots (*.mutation-audit.json) and prompt publication
|
|
161
|
+
locks (*.publish.lock) recorded by a run whose validation passed, or by
|
|
162
|
+
an earlier run in the same run directory as a run that passed.
|
|
163
|
+
Nothing is removed without --apply. Source copies, logs, lockfiles, diffs,
|
|
164
|
+
prompts and results stay. --text lists directories only; files are counted.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def main(argv: list[str] | None = None) -> int:
|
|
169
|
+
parser = argparse.ArgumentParser(
|
|
170
|
+
epilog=_CLI_EPILOG,
|
|
171
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
172
|
+
prog="okstra prune-run-artifacts",
|
|
173
|
+
description="List, and with --apply remove, run artifacts no reader needs after the run.")
|
|
174
|
+
parser.add_argument("--project-root", default="")
|
|
175
|
+
parser.add_argument("--cwd", default=".")
|
|
176
|
+
parser.add_argument("--task-group", default="")
|
|
177
|
+
parser.add_argument("--task-id", default="")
|
|
178
|
+
parser.add_argument("--apply", action="store_true", help="remove the listed paths")
|
|
179
|
+
parser.add_argument("--text", action="store_true", help="emit fixed text fields")
|
|
180
|
+
args = parser.parse_args(argv)
|
|
181
|
+
if bool(args.task_group) != bool(args.task_id):
|
|
182
|
+
parser.error("--task-group and --task-id go together")
|
|
183
|
+
|
|
184
|
+
project_root = resolve_project_root(explicit_root=args.project_root, cwd=args.cwd)
|
|
185
|
+
root = (
|
|
186
|
+
task_dir(project_root, args.task_group, args.task_id)
|
|
187
|
+
if args.task_group else project_root / ".okstra" / "tasks"
|
|
188
|
+
)
|
|
189
|
+
result = {
|
|
190
|
+
"ok": True,
|
|
191
|
+
"projectRoot": str(project_root),
|
|
192
|
+
**prune_run_artifacts(project_root, root, apply=args.apply),
|
|
193
|
+
}
|
|
194
|
+
output = render_result_text(result) if args.text else json.dumps(result, ensure_ascii=False, indent=2)
|
|
195
|
+
print(output, end="" if args.text else "\n")
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
raise SystemExit(main())
|
|
@@ -641,3 +641,16 @@ def _resolve_plan_path(task_root: Path, value: str) -> Path:
|
|
|
641
641
|
# 예전에는 okstra_project.state 가 이 어댑터를 들고 okstra_ctl 을 함수 본문에서
|
|
642
642
|
# 지연 import 했다. 하위 계층이 상위를 되짚는 배치였다. 읽는 대상이 okstra_ctl 의
|
|
643
643
|
# Stage Map 과 consumer 상태이므로 소유자를 여기로 옮긴다.
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def parse_stage_map_for_prepare(plan_path: str) -> list:
|
|
647
|
+
"""Parse the approved plan into context records for execution consumers."""
|
|
648
|
+
from .prepare_error import PrepareError
|
|
649
|
+
|
|
650
|
+
try:
|
|
651
|
+
return stage_map_records(parse_stage_map_file(Path(plan_path)))
|
|
652
|
+
except StageMapError as exc:
|
|
653
|
+
raise PrepareError(
|
|
654
|
+
"approved-plan 의 Stage Map 을 신뢰할 수 없어 거부합니다 "
|
|
655
|
+
f"({plan_path}): {exc.reason}. plan 의 Stage Map 을 점검하세요."
|
|
656
|
+
) from exc
|
|
@@ -36,6 +36,12 @@ from .dispatch_core import (
|
|
|
36
36
|
await_dispatches,
|
|
37
37
|
dispatch_plan,
|
|
38
38
|
)
|
|
39
|
+
from .dispatch_checkpoints import (
|
|
40
|
+
record_collect_checkpoints,
|
|
41
|
+
record_dispatch_checkpoints,
|
|
42
|
+
settled_initial_dispatches,
|
|
43
|
+
)
|
|
44
|
+
from .lead_progress import record_checkpoint, render_progress_line
|
|
39
45
|
from .ports.worker_dispatch import WorkerDispatchPort, WorkerDispatchRequest
|
|
40
46
|
from .registry.host_registry import default_host_registry
|
|
41
47
|
from .registry.provider_registry import default_provider_registry
|
|
@@ -44,6 +50,7 @@ from .json_boundary import load_owned_object
|
|
|
44
50
|
|
|
45
51
|
|
|
46
52
|
_SUPPORTED_WRAPPERS = provider_worker_wrappers(default_provider_registry())
|
|
53
|
+
_SOURCE = "okstra team"
|
|
47
54
|
|
|
48
55
|
|
|
49
56
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
@@ -73,7 +80,7 @@ _CLI_EPILOG = r"""Usage:
|
|
|
73
80
|
[--poll-interval-seconds <n>] [--timeout-seconds <n>] \
|
|
74
81
|
[--heartbeat-seconds <n>] [--json]
|
|
75
82
|
okstra team reclaim --project-root <dir> --run-manifest <path> \
|
|
76
|
-
[--dry-run] [--json]
|
|
83
|
+
[--dry-run] [--gate] [--json]
|
|
77
84
|
okstra team teardown --project-root <dir> --run-manifest <path> \
|
|
78
85
|
[--dry-run] [--json]
|
|
79
86
|
|
|
@@ -84,11 +91,23 @@ descriptor owns the team lifecycle (launch_mode=team) — a worker there is a CL
|
|
|
84
91
|
wrapper subprocess holding no pane, so there is nothing to close.
|
|
85
92
|
|
|
86
93
|
reclaim round boundary: closes the panes of dispatches that have finished and
|
|
87
|
-
leaves the in-progress ones open
|
|
88
|
-
|
|
94
|
+
leaves the in-progress ones open, then on a cmux-pane run records
|
|
95
|
+
`PROGRESS: phase-batch-cleanup panes=<n>`. With --gate it is the
|
|
96
|
+
cleanup before a user gate and prints
|
|
97
|
+
`PROGRESS: phase-gate-cleanup panes=<n>` without recording it.
|
|
98
|
+
|
|
89
99
|
teardown end of run: closes every recorded pane and writes off any dispatch
|
|
90
100
|
that never reached a terminal status.
|
|
91
101
|
|
|
102
|
+
On a cmux-pane run, dispatch records `phase-3-team-create` when it writes the
|
|
103
|
+
implicit-team marker, `phase-4-dispatch` for each `initial` job and
|
|
104
|
+
`phase-6-synthesis` on the first report-writer dispatch; await records
|
|
105
|
+
`phase-5-poll` on entry and again when the counts changed, and
|
|
106
|
+
`phase-5-collect` for each `initial` dispatch it settled. The lead does not
|
|
107
|
+
call `okstra lead-progress append` for those checkpoints; each command prints
|
|
108
|
+
the `PROGRESS:` lines to emit (`progressLines` in JSON output). On any other
|
|
109
|
+
backend the team commands record nothing.
|
|
110
|
+
|
|
92
111
|
--workspace-root and --okstra-bin are owned by this command.
|
|
93
112
|
"""
|
|
94
113
|
_CLI_DESCRIPTION = "Dispatch, await, and close okstra-owned worker panes."
|
|
@@ -148,6 +167,10 @@ def _add_reclaim_parser(sub) -> None:
|
|
|
148
167
|
)
|
|
149
168
|
_add_run_args(parser)
|
|
150
169
|
parser.add_argument("--dry-run", action="store_true")
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
"--gate", action="store_true",
|
|
172
|
+
help="cleanup before a user gate: print phase-gate-cleanup, record nothing",
|
|
173
|
+
)
|
|
151
174
|
parser.add_argument("--json", action="store_true")
|
|
152
175
|
|
|
153
176
|
|
|
@@ -179,11 +202,31 @@ def _dispatch(args) -> int:
|
|
|
179
202
|
if args.dry_run:
|
|
180
203
|
_print_json(backend_plan.to_payload(dry_run=True))
|
|
181
204
|
return 0
|
|
182
|
-
|
|
183
|
-
|
|
205
|
+
lines: list[str] = []
|
|
206
|
+
result = dispatch_plan(
|
|
207
|
+
backend_plan, wait=False,
|
|
208
|
+
before_start=lambda prepared: lines.extend(
|
|
209
|
+
_record_dispatch(args, manifest, prepared)
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
payload = backend_plan.to_payload(dry_run=False)
|
|
213
|
+
payload["progressLines"] = lines
|
|
214
|
+
_print_json(payload)
|
|
184
215
|
return result
|
|
185
216
|
|
|
186
217
|
|
|
218
|
+
def _record_dispatch(args, manifest: Mapping[str, Any], prepared) -> list[str]:
|
|
219
|
+
# cmux-pane run 만 체크포인트를 team 명령에 맡긴다. external 리드는 CLI 워커도
|
|
220
|
+
# `team await` 로 기다리지만 체크포인트는 직접 append 하므로, 여기서 쓰면 행이 둘이 된다.
|
|
221
|
+
if not _is_cmux_run(manifest):
|
|
222
|
+
return []
|
|
223
|
+
return record_dispatch_checkpoints(
|
|
224
|
+
prepared.project_root, args.run_manifest,
|
|
225
|
+
_load_json(prepared.team_state_path, "team-state"), prepared.jobs,
|
|
226
|
+
source=_SOURCE,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
187
230
|
def team_dispatch_port(manifest: Mapping[str, Any]) -> WorkerDispatchPort:
|
|
188
231
|
"""이 run 의 워커를 띄울 포트. cmux run 이면 pane, 아니면 리드 호스트의 포트.
|
|
189
232
|
|
|
@@ -209,19 +252,59 @@ def _await(args) -> int:
|
|
|
209
252
|
_validate_team_manifest(manifest)
|
|
210
253
|
_observe_lead_session_from_manifest(Path(args.project_root).resolve(), manifest)
|
|
211
254
|
plan = _plan_for_existing(Path(args.project_root), Path(args.workspace_root), Path(args.run_manifest), manifest)
|
|
255
|
+
should_record = _is_cmux_run(manifest)
|
|
256
|
+
entry_state = _load_json(plan.team_state_path, "team-state")
|
|
257
|
+
entry = _dispatch_counts(entry_state)
|
|
258
|
+
lines = [_record_poll(args, entry)] if should_record else []
|
|
212
259
|
code = await_dispatches(
|
|
213
260
|
plan,
|
|
214
261
|
poll_interval_seconds=args.poll_interval_seconds,
|
|
215
262
|
timeout_seconds=args.timeout_seconds,
|
|
216
263
|
heartbeat_seconds=0 if args.json else args.heartbeat_seconds,
|
|
217
264
|
)
|
|
265
|
+
if should_record:
|
|
266
|
+
exit_state = _load_json(plan.team_state_path, "team-state")
|
|
267
|
+
lines.extend(record_collect_checkpoints(
|
|
268
|
+
plan.project_root, args.run_manifest,
|
|
269
|
+
settled_initial_dispatches(entry_state),
|
|
270
|
+
settled_initial_dispatches(exit_state),
|
|
271
|
+
source=_SOURCE,
|
|
272
|
+
))
|
|
273
|
+
exit_counts = _dispatch_counts(exit_state)
|
|
274
|
+
if exit_counts != entry:
|
|
275
|
+
lines.append(_record_poll(args, exit_counts))
|
|
276
|
+
lines = [line for line in lines if line]
|
|
218
277
|
if args.json:
|
|
219
|
-
|
|
278
|
+
payload = _await_payload(plan, code == 0)
|
|
279
|
+
payload["progressLines"] = lines
|
|
280
|
+
_print_json(payload)
|
|
220
281
|
else:
|
|
282
|
+
for line in lines:
|
|
283
|
+
print(line)
|
|
221
284
|
print("ALL_WORKERS_DONE" if code == 0 else "POLL_TIMEOUT")
|
|
222
285
|
return code
|
|
223
286
|
|
|
224
287
|
|
|
288
|
+
def _dispatch_counts(team_state: Mapping[str, Any]) -> tuple[int, int]:
|
|
289
|
+
"""이 run 의 dispatch 행 기준 (pending, done) — `phase-5-poll` 의 두 값."""
|
|
290
|
+
statuses = [
|
|
291
|
+
record.get("status")
|
|
292
|
+
for record in team_state.get("workerDispatches", [])
|
|
293
|
+
if isinstance(record, dict)
|
|
294
|
+
]
|
|
295
|
+
done = sum(1 for status in statuses if status in TERMINAL_WORKER_STATUSES)
|
|
296
|
+
return len(statuses) - done, done
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _record_poll(args, counts: tuple[int, int]) -> str | None:
|
|
300
|
+
pending, done = counts
|
|
301
|
+
return record_checkpoint(
|
|
302
|
+
Path(args.project_root), args.run_manifest, "phase-5-poll",
|
|
303
|
+
source=_SOURCE,
|
|
304
|
+
fields=(("pending", str(pending)), ("done", str(done))),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
225
308
|
def _teardown(args) -> int:
|
|
226
309
|
manifest = _load_manifest(args.project_root, args.run_manifest)
|
|
227
310
|
_validate_team_manifest(manifest)
|
|
@@ -259,7 +342,16 @@ def _reclaim(args) -> int:
|
|
|
259
342
|
_emit_panes(args.json, panes)
|
|
260
343
|
return 0
|
|
261
344
|
_close_panes(manifest, panes)
|
|
262
|
-
|
|
345
|
+
count = (("panes", str(len(panes))),)
|
|
346
|
+
line: str | None = None
|
|
347
|
+
if args.gate:
|
|
348
|
+
line = render_progress_line("phase-gate-cleanup", count)
|
|
349
|
+
elif _is_cmux_run(manifest):
|
|
350
|
+
line = record_checkpoint(
|
|
351
|
+
Path(args.project_root), args.run_manifest, "phase-batch-cleanup",
|
|
352
|
+
source=_SOURCE, fields=count,
|
|
353
|
+
)
|
|
354
|
+
_emit_panes(args.json, panes, line)
|
|
263
355
|
return 0
|
|
264
356
|
|
|
265
357
|
|
|
@@ -434,12 +526,19 @@ def _mark_teardown_errors(team_state_path: Path) -> None:
|
|
|
434
526
|
mutate_team_state(team_state_path, mark)
|
|
435
527
|
|
|
436
528
|
|
|
437
|
-
def _emit_panes(
|
|
529
|
+
def _emit_panes(
|
|
530
|
+
as_json: bool, panes: list[dict[str, str]], progress_line: str | None = None
|
|
531
|
+
) -> None:
|
|
438
532
|
if as_json:
|
|
439
|
-
|
|
533
|
+
payload: dict[str, Any] = {"panes": panes}
|
|
534
|
+
if progress_line:
|
|
535
|
+
payload["progressLines"] = [progress_line]
|
|
536
|
+
_print_json(payload)
|
|
440
537
|
return
|
|
441
538
|
for pane in panes:
|
|
442
539
|
print(f"{pane['paneId']}\t{pane['kind']}")
|
|
540
|
+
if progress_line:
|
|
541
|
+
print(progress_line)
|
|
443
542
|
|
|
444
543
|
|
|
445
544
|
def _manifest_backend(manifest: Mapping[str, Any]) -> str:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""방향 비교와 기술 검증이 함께 읽는 미확정 사실 계약."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Any, TypedDict
|
|
6
|
+
|
|
7
|
+
from .clarification_items import USER_INPUT_BLOCKS, progress_blocking_ids
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VerificationFact(TypedDict):
|
|
11
|
+
id: str
|
|
12
|
+
candidateId: str
|
|
13
|
+
factIndex: int
|
|
14
|
+
fact: str
|
|
15
|
+
whyItMatters: str
|
|
16
|
+
evidence: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TechnicalVerificationError(ValueError):
|
|
20
|
+
"""시험 입력 또는 관측 결과가 원본 사실과 일치하지 않는다."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def technical_verification_facts(data: Mapping[str, Any]) -> list[VerificationFact]:
|
|
24
|
+
"""안전 차단이 없는 후보의 명시적인 기술 검증 사실만 추출한다."""
|
|
25
|
+
blockers = progress_blocking_ids(
|
|
26
|
+
data.get("clarificationItems"), USER_INPUT_BLOCKS, report_data=data
|
|
27
|
+
)
|
|
28
|
+
if blockers:
|
|
29
|
+
raise TechnicalVerificationError(
|
|
30
|
+
"unresolved user decisions: " + ", ".join(blockers)
|
|
31
|
+
)
|
|
32
|
+
selection = data.get("implementationOptionSelection") or {}
|
|
33
|
+
facts: list[VerificationFact] = []
|
|
34
|
+
for candidate in selection.get("candidateAudit") or []:
|
|
35
|
+
if candidate.get("safetyBlockers"):
|
|
36
|
+
continue
|
|
37
|
+
for index, fact in enumerate(candidate.get("unresolvedFeasibilityFacts") or []):
|
|
38
|
+
if fact.get("resolutionKind") != "technical-verification":
|
|
39
|
+
continue
|
|
40
|
+
facts.append(
|
|
41
|
+
{
|
|
42
|
+
"id": f"TV-{len(facts) + 1:03d}",
|
|
43
|
+
"candidateId": candidate["id"],
|
|
44
|
+
"factIndex": index,
|
|
45
|
+
"fact": fact["fact"],
|
|
46
|
+
"whyItMatters": fact["whyItMatters"],
|
|
47
|
+
"evidence": fact["evidence"],
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
if not facts:
|
|
51
|
+
raise TechnicalVerificationError("no eligible technical-verification facts")
|
|
52
|
+
return facts
|
|
@@ -15,11 +15,11 @@ Public surface (re-exported here; the skill calls it through the
|
|
|
15
15
|
- ``render_args()`` - final args for ``okstra render-bundle`` (``render``).
|
|
16
16
|
- ``wizard_outcome()`` - final args plus persistence actions and confirmation (``render``).
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
``tests/contract/test_no_import_cycles.py`` keeps it that way.
|
|
18
|
+
Package initialization loads identifiers and state types only. Public execution
|
|
19
|
+
functions are composed by ``api`` when requested. Phase modules can import state
|
|
20
|
+
and prompt readers without initializing the registry that imports those phases.
|
|
22
21
|
"""
|
|
22
|
+
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
25
|
from .ids import (
|
|
@@ -118,31 +118,31 @@ from .state import (
|
|
|
118
118
|
WizardError,
|
|
119
119
|
WizardState,
|
|
120
120
|
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
next_prompt,
|
|
138
|
-
prompt_payload,
|
|
139
|
-
submit,
|
|
140
|
-
)
|
|
141
|
-
from .render import (
|
|
142
|
-
render_args,
|
|
143
|
-
render_role_args,
|
|
144
|
-
)
|
|
145
|
-
from .outcome import wizard_outcome
|
|
146
|
-
from .cli import (
|
|
147
|
-
main,
|
|
121
|
+
|
|
122
|
+
_API_EXPORTS = (
|
|
123
|
+
"next_role_prompt",
|
|
124
|
+
"load_state_file",
|
|
125
|
+
"save_state_file",
|
|
126
|
+
"confirmation_block",
|
|
127
|
+
"STEPS",
|
|
128
|
+
"STEP_BY_ID",
|
|
129
|
+
"init_state",
|
|
130
|
+
"next_prompt",
|
|
131
|
+
"prompt_payload",
|
|
132
|
+
"submit",
|
|
133
|
+
"render_args",
|
|
134
|
+
"render_role_args",
|
|
135
|
+
"wizard_outcome",
|
|
136
|
+
"main",
|
|
148
137
|
)
|
|
138
|
+
|
|
139
|
+
__all__ = [name for name in globals() if not name.startswith("_")] + list(_API_EXPORTS)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def __getattr__(name: str):
|
|
143
|
+
"""기존 공개 이름을 실행 조립 모듈에 연결한다."""
|
|
144
|
+
if name not in _API_EXPORTS:
|
|
145
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
146
|
+
from . import api
|
|
147
|
+
|
|
148
|
+
return getattr(api, name)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""질문 실행기의 공개 조립 지점. 단계의 값·프롬프트 판독에서는 적재하지 않는다."""
|
|
2
|
+
|
|
3
|
+
from .roles import next_role_prompt as next_role_prompt
|
|
4
|
+
from .statefile import (
|
|
5
|
+
load_state_file as load_state_file,
|
|
6
|
+
save_state_file as save_state_file,
|
|
7
|
+
)
|
|
8
|
+
from .confirmation import confirmation_block as confirmation_block
|
|
9
|
+
from .registry import STEPS as STEPS, STEP_BY_ID as STEP_BY_ID
|
|
10
|
+
from .engine import (
|
|
11
|
+
init_state as init_state,
|
|
12
|
+
next_prompt as next_prompt,
|
|
13
|
+
prompt_payload as prompt_payload,
|
|
14
|
+
submit as submit,
|
|
15
|
+
)
|
|
16
|
+
from .render import render_args as render_args, render_role_args as render_role_args
|
|
17
|
+
from .outcome import wizard_outcome as wizard_outcome
|
|
18
|
+
from .cli import main as main
|
|
@@ -25,18 +25,9 @@ def _render_argv(rendered: dict[str, Any], *, host_runtime: str) -> list[str]:
|
|
|
25
25
|
def _wizard_persist_actions(state: WizardState) -> list[dict[str, str]]:
|
|
26
26
|
if state.task_type != "release-handoff":
|
|
27
27
|
return []
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return []
|
|
32
|
-
return [
|
|
33
|
-
{
|
|
34
|
-
"command": "config.set",
|
|
35
|
-
"key": "pr-template-path",
|
|
36
|
-
"scope": state.pr_template_scope,
|
|
37
|
-
"value": state.pr_template_path,
|
|
38
|
-
}
|
|
39
|
-
]
|
|
28
|
+
from okstra_ctl.phases.release_handoff.wizard import pr_template_persist_actions
|
|
29
|
+
|
|
30
|
+
return pr_template_persist_actions(state)
|
|
40
31
|
|
|
41
32
|
|
|
42
33
|
def wizard_outcome(state: WizardState) -> dict[str, Any]:
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
"""단계 등록표 — 순서가 곧 질문 순서인 `STEPS`, `STEP_BY_ID`, 등록표를 되감는 `_reset_from` 과 되감기를 일으키는 단계(edit target, brief carry)."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
from okstra_ctl.phases.change_impact_analysis.entry import (
|
|
5
|
+
_build_feature_evidence,
|
|
6
|
+
_build_feature_evidence_pick,
|
|
7
|
+
_submit_feature_evidence,
|
|
8
|
+
_submit_feature_evidence_pick,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from okstra_ctl.phases.implementation_planning.wizard import (
|
|
12
|
+
_selected_direction_candidates,
|
|
13
|
+
_planning_rerun_selected,
|
|
14
|
+
_build_selected_direction_pick,
|
|
15
|
+
_submit_selected_direction_pick,
|
|
16
|
+
)
|
|
17
|
+
|
|
4
18
|
from typing import Optional
|
|
5
19
|
|
|
6
20
|
from .ids import (
|
|
@@ -119,27 +133,25 @@ from .steps_identity import (
|
|
|
119
133
|
_submit_task_type,
|
|
120
134
|
_submit_task_type_text,
|
|
121
135
|
)
|
|
122
|
-
from .
|
|
123
|
-
_analysis_inputs_after_reuse,
|
|
136
|
+
from okstra_ctl.phases.feature_analysis.wizard import (
|
|
124
137
|
_build_analysis_target,
|
|
125
138
|
_build_analysis_target_pick,
|
|
139
|
+
_submit_analysis_target,
|
|
140
|
+
_submit_analysis_target_pick,
|
|
141
|
+
)
|
|
142
|
+
from .steps_analysis import (
|
|
143
|
+
_analysis_inputs_after_reuse,
|
|
126
144
|
_build_design_prep_confirm,
|
|
127
145
|
_build_design_prep_decision,
|
|
128
146
|
_build_design_prep_overrides,
|
|
129
|
-
_build_feature_evidence,
|
|
130
|
-
_build_feature_evidence_pick,
|
|
131
147
|
_build_project_evidence,
|
|
132
148
|
_build_project_evidence_pick,
|
|
133
149
|
_design_prep_confirm_applies,
|
|
134
150
|
_design_prep_decision_applies,
|
|
135
151
|
_design_prep_overrides_applies,
|
|
136
|
-
_submit_analysis_target,
|
|
137
|
-
_submit_analysis_target_pick,
|
|
138
152
|
_submit_design_prep_confirm,
|
|
139
153
|
_submit_design_prep_decision,
|
|
140
154
|
_submit_design_prep_overrides,
|
|
141
|
-
_submit_feature_evidence,
|
|
142
|
-
_submit_feature_evidence_pick,
|
|
143
155
|
_submit_project_evidence,
|
|
144
156
|
_submit_project_evidence_pick,
|
|
145
157
|
)
|
|
@@ -151,12 +163,9 @@ from .steps_plan import (
|
|
|
151
163
|
_build_handoff_stage_pick,
|
|
152
164
|
_build_reverify_scope_pick,
|
|
153
165
|
_build_reverify_scope_stages,
|
|
154
|
-
_build_selected_direction_pick,
|
|
155
166
|
_build_stage_pick,
|
|
156
167
|
_fix_cycle_confirm_required,
|
|
157
|
-
_planning_rerun_selected,
|
|
158
168
|
_reverify_scope_step_pending,
|
|
159
|
-
_selected_direction_candidates,
|
|
160
169
|
_submit_approve_plan_confirm,
|
|
161
170
|
_submit_approved_plan,
|
|
162
171
|
_submit_approved_plan_pick,
|
|
@@ -164,7 +173,6 @@ from .steps_plan import (
|
|
|
164
173
|
_submit_handoff_stage_pick,
|
|
165
174
|
_submit_reverify_scope_pick,
|
|
166
175
|
_submit_reverify_scope_stages,
|
|
167
|
-
_submit_selected_direction_pick,
|
|
168
176
|
_submit_stage_pick,
|
|
169
177
|
)
|
|
170
178
|
from .steps_options import (
|