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,118 @@
|
|
|
1
|
+
"""CLI 워커 프로세스 그룹의 메모리 상한과 종료 뒤 잔여 프로세스 정리.
|
|
2
|
+
|
|
3
|
+
워커가 실행한 빌드는 워커와 같은 프로세스 그룹에 들어간다(`worker_runner` 가
|
|
4
|
+
`start_new_session=True` 로 띄운다). fontsninja-v3-site 에서 Turbopack PostCSS 워커가
|
|
5
|
+
57GB, `next build` 가 78GB 까지 커져 커널 패닉(watchdog timeout)이 났고, 본 프로세스가
|
|
6
|
+
죽은 뒤에도 PostCSS 워커가 남아 계속 커졌다(2026-09-27).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import signal
|
|
12
|
+
import subprocess
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from okstra_project.dirs import okstra_home, project_json_path
|
|
16
|
+
|
|
17
|
+
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
18
|
+
|
|
19
|
+
CONFIG_KEY = "workerMemoryCapMb"
|
|
20
|
+
# 이 프로젝트의 정상 `next build` 는 5.4–5.6GB 였다. 상한은 그보다 넉넉하되, 물리
|
|
21
|
+
# 메모리가 작은 기기에서는 절반을 넘지 않게 한다.
|
|
22
|
+
_DEFAULT_CEILING_BYTES = 8 * 1024 ** 3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MemoryCapConfigError(ValueError):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def physical_memory_bytes() -> int:
|
|
30
|
+
return os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def memory_cap_bytes(project_root: Path, global_config_path: Path | None = None) -> int:
|
|
34
|
+
"""워커 그룹 RSS 합계 상한. 0 은 감시하지 않음.
|
|
35
|
+
|
|
36
|
+
프로젝트 `.okstra/project.json` → `~/.okstra/config.json` → 기본값 순서다.
|
|
37
|
+
"""
|
|
38
|
+
for label, path in (
|
|
39
|
+
("project", project_json_path(project_root)),
|
|
40
|
+
("global", global_config_path or okstra_home() / "config.json"),
|
|
41
|
+
):
|
|
42
|
+
value = _configured_cap_mb(label, path)
|
|
43
|
+
if value is not None:
|
|
44
|
+
return value * 1024 ** 2
|
|
45
|
+
return min(_DEFAULT_CEILING_BYTES, physical_memory_bytes() // 2)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _configured_cap_mb(label: str, path: Path) -> int | None:
|
|
49
|
+
if not path.is_file():
|
|
50
|
+
return None
|
|
51
|
+
try:
|
|
52
|
+
payload = load_owned_object(path, artifact=f"{label} configuration")
|
|
53
|
+
except JsonBoundaryError as exc:
|
|
54
|
+
raise MemoryCapConfigError(f"{label} config is unreadable: {path}") from exc
|
|
55
|
+
value = payload.get(CONFIG_KEY) if isinstance(payload, dict) else None
|
|
56
|
+
if value is None:
|
|
57
|
+
return None
|
|
58
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
59
|
+
raise MemoryCapConfigError(
|
|
60
|
+
f"{CONFIG_KEY} must be a non-negative integer (MB), got {value!r}: {path}")
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def group_rss_bytes(pgid: int) -> int:
|
|
65
|
+
"""프로세스 그룹 `pgid` 에 속한 프로세스들의 RSS 합계."""
|
|
66
|
+
try:
|
|
67
|
+
output = subprocess.run(
|
|
68
|
+
["ps", "-A", "-o", "pgid=,rss="],
|
|
69
|
+
capture_output=True, text=True, check=True,
|
|
70
|
+
).stdout
|
|
71
|
+
except (OSError, subprocess.CalledProcessError):
|
|
72
|
+
return 0
|
|
73
|
+
total_kb = 0
|
|
74
|
+
for row in output.splitlines():
|
|
75
|
+
fields = row.split()
|
|
76
|
+
if len(fields) == 2 and fields[0] == str(pgid) and fields[1].isdigit():
|
|
77
|
+
total_kb += int(fields[1])
|
|
78
|
+
return total_kb * 1024
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def kill_group(pgid: int) -> bool:
|
|
82
|
+
"""그룹에 남은 프로세스를 SIGKILL 한다. 보낼 대상이 있었으면 True."""
|
|
83
|
+
try:
|
|
84
|
+
os.killpg(pgid, signal.SIGKILL)
|
|
85
|
+
except ProcessLookupError:
|
|
86
|
+
return False
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class MemoryWatch:
|
|
91
|
+
"""주기적으로 그룹 RSS 를 재고, 상한을 넘으면 그룹을 죽인다."""
|
|
92
|
+
|
|
93
|
+
interval_seconds = 2.0
|
|
94
|
+
|
|
95
|
+
def __init__(self, cap_bytes: int) -> None:
|
|
96
|
+
self.cap_bytes = cap_bytes
|
|
97
|
+
self.exceeded_bytes = 0
|
|
98
|
+
self._next_sample = 0.0
|
|
99
|
+
|
|
100
|
+
def check(self, pgid: int, now: float) -> bool:
|
|
101
|
+
"""상한을 넘어 그룹을 죽였으면 True."""
|
|
102
|
+
if not self.cap_bytes or self.exceeded_bytes or now < self._next_sample:
|
|
103
|
+
return False
|
|
104
|
+
self._next_sample = now + self.interval_seconds
|
|
105
|
+
rss = group_rss_bytes(pgid)
|
|
106
|
+
if rss <= self.cap_bytes:
|
|
107
|
+
return False
|
|
108
|
+
self.exceeded_bytes = rss
|
|
109
|
+
kill_group(pgid)
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
def failure(self) -> str:
|
|
113
|
+
mb = 1024 ** 2
|
|
114
|
+
return (
|
|
115
|
+
f"memory-cap exceeded: worker process group used {self.exceeded_bytes // mb} MB, "
|
|
116
|
+
f"cap {self.cap_bytes // mb} MB ({CONFIG_KEY} in .okstra/project.json "
|
|
117
|
+
"or ~/.okstra/config.json)"
|
|
118
|
+
)
|
|
@@ -19,7 +19,7 @@ import sys
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
21
|
from .paths import find_asset_root
|
|
22
|
-
from .phases.catalog import PhaseAssetError, UnknownTaskType, profile_markdown
|
|
22
|
+
from .phases.catalog import PhaseAssetError, UnknownTaskType, profile_markdown, profile_sidecar
|
|
23
23
|
from .role_requirements import RoleProfileError, load_role_profile
|
|
24
24
|
from .run import PrepareError, _expand_profile_includes
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ _PROFILES_REL = ("prompts", "profiles")
|
|
|
29
29
|
# `prompts/profiles/_implementation-executor.md`. Reading the list from the
|
|
30
30
|
# profile body rather than hard-coding it here is deliberate: a list in code
|
|
31
31
|
# goes stale the moment a profile adds a sidecar, and does so silently.
|
|
32
|
-
_SIDECAR_RE = re.compile(r"`(prompts/profiles/_[\w-]+\.md)`")
|
|
32
|
+
_SIDECAR_RE = re.compile(r"`((?:prompts/profiles|scripts/okstra_ctl/phases/implementation/instructions)/_[\w-]+\.md)`")
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def workspace_root(start: Path | None = None) -> Path:
|
|
@@ -77,7 +77,7 @@ def sidecar_bodies(root: Path, profile_text: str) -> list[str]:
|
|
|
77
77
|
if relative in seen:
|
|
78
78
|
continue
|
|
79
79
|
seen.add(relative)
|
|
80
|
-
path = root
|
|
80
|
+
path = profile_sidecar(root, relative)
|
|
81
81
|
if not path.is_file():
|
|
82
82
|
continue
|
|
83
83
|
body = _expand_profile_includes(path)
|
|
@@ -17,6 +17,8 @@ session id 등) 를 덧붙여 전달한다.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
from .phases.catalog import profile_sidecar
|
|
21
|
+
|
|
20
22
|
import hashlib
|
|
21
23
|
import json
|
|
22
24
|
import re
|
|
@@ -2632,7 +2634,7 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2632
2634
|
home_prompts / "lead" / "convergence.md"
|
|
2633
2635
|
)
|
|
2634
2636
|
plan_body_verification_path = ctx.get("OKSTRA_PLAN_BODY_VERIFICATION_PATH") or str(
|
|
2635
|
-
home_prompts /
|
|
2637
|
+
profile_sidecar(home_prompts.parent, "prompts/lead/plan-body-verification.md")
|
|
2636
2638
|
)
|
|
2637
2639
|
report_writer_path = ctx.get("OKSTRA_REPORT_WRITER_PATH") or str(
|
|
2638
2640
|
home_prompts / "lead" / "report-writer.md"
|
|
@@ -2785,8 +2787,12 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2785
2787
|
"`shutdown_workers`, `record_lead_event`, and `collect_usage` only "
|
|
2786
2788
|
"through its mapping.\n"
|
|
2787
2789
|
"- Do not call a primitive documented by an unselected adapter.\n"
|
|
2788
|
-
"- Record the adapter-required Phase 3 state
|
|
2789
|
-
"`
|
|
2790
|
+
"- Record the adapter-required Phase 3 state. When team-state has "
|
|
2791
|
+
"no `teamCreate` yet, do not append `phase-3-team-create`: the "
|
|
2792
|
+
"first `team dispatch` (cmux-pane run) or `worker-dispatch` writes "
|
|
2793
|
+
"the implicit-team marker, records the checkpoint and prints the "
|
|
2794
|
+
"line to emit. Otherwise append `PROGRESS: phase-3-team-create ...` "
|
|
2795
|
+
"before dispatch.\n"
|
|
2790
2796
|
f"{adapter_setup_block}\n"
|
|
2791
2797
|
"- The run manifest is authoritative for concurrent-run metadata, "
|
|
2792
2798
|
"dispatch backend, and artifact paths."
|
|
@@ -2971,7 +2977,12 @@ def render_template_with_ctx(template_path: str, output_path: str, ctx: dict) ->
|
|
|
2971
2977
|
Optional defaults (VALIDATION_STATUS etc.) should be filled by calling
|
|
2972
2978
|
`apply_lead_prompt_defaults(ctx)` in the same setup step.
|
|
2973
2979
|
"""
|
|
2974
|
-
|
|
2980
|
+
from .phases.catalog import migrated_report_template
|
|
2981
|
+
|
|
2982
|
+
source = Path(template_path)
|
|
2983
|
+
if source.parent.name == "reports" and source.parent.parent.name == "templates":
|
|
2984
|
+
source = migrated_report_template(source.parent.parent.parent, source.name) or source
|
|
2985
|
+
template = source.read_text(encoding="utf-8")
|
|
2975
2986
|
|
|
2976
2987
|
fm_ctx = dict(ctx)
|
|
2977
2988
|
fm_ctx.setdefault("DOC_TYPE", _doc_type_from_template_path(template_path))
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import copy
|
|
5
|
-
import json
|
|
6
5
|
import os
|
|
7
6
|
import tempfile
|
|
7
|
+
from okstra_ctl.phases.implementation_planning.validation import validate_selected_direction_plan
|
|
8
|
+
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any, Callable, Mapping, Sequence
|
|
@@ -25,22 +26,24 @@ from .report_synthesis_packet import (
|
|
|
25
26
|
)
|
|
26
27
|
from .conformance import strip_stage_declaration_label
|
|
27
28
|
from .design_prep import DesignPrepError, materialize_design_prep_requests
|
|
28
|
-
from .
|
|
29
|
-
from .technical_verification import validate_technical_verification_report
|
|
29
|
+
from .phases.implementation_option_selection.validation import validate_blocked_answer_channel
|
|
30
|
+
from .phases.technical_verification.validation import validate_technical_verification_report
|
|
31
|
+
from .report_routing import technical_verification_routing_errors
|
|
30
32
|
from .implementation_direction import (
|
|
31
33
|
load_selected_direction_snapshot,
|
|
32
|
-
validate_selected_direction_plan,
|
|
33
34
|
)
|
|
34
35
|
from .paths import task_dir
|
|
35
36
|
from .phases.final_verification.report import verification_scope
|
|
36
37
|
from .report_projections import (
|
|
37
38
|
ReportProjectionError,
|
|
38
39
|
project_convergence,
|
|
39
|
-
project_design,
|
|
40
40
|
project_execution,
|
|
41
41
|
project_token_usage,
|
|
42
42
|
)
|
|
43
|
-
from .
|
|
43
|
+
from .phases.implementation_planning.report import (
|
|
44
|
+
attach_design_snapshot, _sync_human_summary_with_plan_body_gate,
|
|
45
|
+
_fill_end_state_coverage,
|
|
46
|
+
)
|
|
44
47
|
|
|
45
48
|
from .write_policy import planned_path_declaration_errors
|
|
46
49
|
|
|
@@ -160,12 +163,21 @@ def _clarifications(
|
|
|
160
163
|
_clarification_row(row, activity_by_id, path)
|
|
161
164
|
for row in active
|
|
162
165
|
]
|
|
163
|
-
|
|
166
|
+
superseded = {
|
|
167
|
+
cid
|
|
168
|
+
for row in active
|
|
169
|
+
if isinstance(row.get("resolutionInput"), Mapping)
|
|
170
|
+
for cid in row.get("supersedes") or []
|
|
171
|
+
}
|
|
172
|
+
rows.extend(_carried_clarification_rows(
|
|
173
|
+
ledger, {row.get("id") for row in rows}, path, superseded,
|
|
174
|
+
))
|
|
164
175
|
return rows
|
|
165
176
|
|
|
166
177
|
|
|
167
178
|
def _carried_clarification_rows(
|
|
168
179
|
ledger: Mapping[str, Any], seen_ids: set[object], path: Path,
|
|
180
|
+
superseded: set[str],
|
|
169
181
|
) -> list[dict[str, Any]]:
|
|
170
182
|
"""이월 결정은 active 질문이 아니다. 이번 런 활동 원장을 요구하지 않는다."""
|
|
171
183
|
carried = ledger.get("carriedDecisions")
|
|
@@ -185,7 +197,13 @@ def _carried_clarification_rows(
|
|
|
185
197
|
cid = decision.get("id")
|
|
186
198
|
if cid in seen:
|
|
187
199
|
continue
|
|
188
|
-
|
|
200
|
+
row = _carried_clarification_row(decision)
|
|
201
|
+
if cid in superseded:
|
|
202
|
+
# 다음 run 의 seed 는 `obsolete` 행을 이월하지 않는다. `answered` 로
|
|
203
|
+
# 남기면 대체된 답과 대체한 답이 함께 이월된다.
|
|
204
|
+
row["status"] = "obsolete"
|
|
205
|
+
row.pop("resolution", None)
|
|
206
|
+
rows.append(row)
|
|
189
207
|
seen.add(cid)
|
|
190
208
|
return rows
|
|
191
209
|
|
|
@@ -689,15 +707,9 @@ def _attach_machine_planning(
|
|
|
689
707
|
if design_row is None:
|
|
690
708
|
return
|
|
691
709
|
try:
|
|
692
|
-
|
|
710
|
+
attach_design_snapshot(planning, _read_json(design_row))
|
|
693
711
|
except ReportProjectionError as exc:
|
|
694
712
|
_fail(design_row.owner, design_row.path, "$", exc)
|
|
695
|
-
planning["designPreparation"] = design["designPreparation"]
|
|
696
|
-
stages = {row.get("stage"): row for row in planning.get("stages") or [] if isinstance(row, dict)}
|
|
697
|
-
for coverage in design["stageCoverage"]:
|
|
698
|
-
stage = stages.get(coverage.get("stage"))
|
|
699
|
-
if stage is not None:
|
|
700
|
-
stage["designSurfaceCoverage"] = coverage.get("rows") or []
|
|
701
713
|
verification_row = inputs.get("plan-body-verification")
|
|
702
714
|
if verification_row is None:
|
|
703
715
|
return
|
|
@@ -717,83 +729,6 @@ def _attach_machine_planning(
|
|
|
717
729
|
_sync_human_summary_with_plan_body_gate(data, projection)
|
|
718
730
|
|
|
719
731
|
|
|
720
|
-
def _sync_human_summary_with_plan_body_gate(
|
|
721
|
-
data: dict[str, Any], projection: Mapping[str, Any],
|
|
722
|
-
) -> None:
|
|
723
|
-
"""게이트가 통과했는데 내러티브가 unseeded 로 남으면 사람용 칸을 맞춘다.
|
|
724
|
-
|
|
725
|
-
report-writer 는 PBV 전에 쓰고, 조립이 게이트를 덮어씌운다. blockers 와
|
|
726
|
-
nextStep 이 그때의 문장을 그대로 두면 통과한 계획이 아직 검증 전으로 보인다.
|
|
727
|
-
"""
|
|
728
|
-
gate = str(projection.get("gateResult") or "")
|
|
729
|
-
if gate not in {"passed", "passed-with-dissent"}:
|
|
730
|
-
return
|
|
731
|
-
summary = data.get("humanSummary")
|
|
732
|
-
if isinstance(summary, dict) and isinstance(summary.get("blockers"), list):
|
|
733
|
-
summary["blockers"] = [
|
|
734
|
-
row for row in summary["blockers"]
|
|
735
|
-
if "unseeded" not in str(row).lower()
|
|
736
|
-
]
|
|
737
|
-
card = data.get("verdictCard")
|
|
738
|
-
if not isinstance(card, dict):
|
|
739
|
-
return
|
|
740
|
-
next_step = str(card.get("nextStep") or "")
|
|
741
|
-
lowered = next_step.lower()
|
|
742
|
-
if "unseeded" not in lowered and "seed" not in lowered:
|
|
743
|
-
return
|
|
744
|
-
next_step = (
|
|
745
|
-
"Plan-body verification passed. Do not start implementation "
|
|
746
|
-
"until `frontmatter.approved` is true."
|
|
747
|
-
)
|
|
748
|
-
card["nextStep"] = next_step
|
|
749
|
-
final = data.get("finalVerdict")
|
|
750
|
-
if isinstance(final, dict):
|
|
751
|
-
final["nextStep"] = next_step
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
def _fill_end_state_coverage(data: dict[str, Any]) -> None:
|
|
755
|
-
"""writer 가 못 쓴 endStateCoverage 를 requirementCoverage brief:EB-001 에서 채운다.
|
|
756
|
-
|
|
757
|
-
내러티브 스키마가 이 칸을 열어 주면 writer 가 쓸 수 있다. 비어 있으면
|
|
758
|
-
validate-run 이 brief id 마다 한 행을 요구하므로 조립이 채운다.
|
|
759
|
-
"""
|
|
760
|
-
existing = data.get("endStateCoverage")
|
|
761
|
-
if isinstance(existing, list) and existing:
|
|
762
|
-
return
|
|
763
|
-
planning = data.get("implementationPlanning")
|
|
764
|
-
if not isinstance(planning, dict):
|
|
765
|
-
return
|
|
766
|
-
derived = _end_state_rows_from_requirement_coverage(planning)
|
|
767
|
-
if derived:
|
|
768
|
-
data["endStateCoverage"] = derived
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
def _end_state_rows_from_requirement_coverage(
|
|
772
|
-
planning: Mapping[str, Any],
|
|
773
|
-
) -> list[dict[str, Any]]:
|
|
774
|
-
rows: list[dict[str, Any]] = []
|
|
775
|
-
seen: set[str] = set()
|
|
776
|
-
for item in planning.get("requirementCoverage") or []:
|
|
777
|
-
if not isinstance(item, Mapping):
|
|
778
|
-
continue
|
|
779
|
-
ref = parse_source(str(item.get("source") or ""))
|
|
780
|
-
if not ref.is_end_state_id or ref.value in seen:
|
|
781
|
-
continue
|
|
782
|
-
seen.add(ref.value)
|
|
783
|
-
status = str(item.get("status") or "")
|
|
784
|
-
row: dict[str, Any] = {
|
|
785
|
-
"id": ref.value,
|
|
786
|
-
"disposition": "addressed" if status == "covered" else "deferred",
|
|
787
|
-
"coveredBy": str(item.get("id") or item.get("coveredBy") or ""),
|
|
788
|
-
}
|
|
789
|
-
if row["disposition"] != "addressed":
|
|
790
|
-
row["rationale"] = (
|
|
791
|
-
f"requirementCoverage `{item.get('id')}` status is `{status or 'empty'}`."
|
|
792
|
-
)
|
|
793
|
-
rows.append(row)
|
|
794
|
-
return rows
|
|
795
|
-
|
|
796
|
-
|
|
797
732
|
def _schema_owner(error: str) -> str:
|
|
798
733
|
field = error.split(" ", 1)[0]
|
|
799
734
|
owners = {
|
|
@@ -897,6 +832,7 @@ def assemble_report(
|
|
|
897
832
|
target = Path(value)
|
|
898
833
|
target = target if target.is_absolute() else project_root / target
|
|
899
834
|
verification_errors = validate_technical_verification_report(data, target, project_root)
|
|
835
|
+
verification_errors.extend(technical_verification_routing_errors(data))
|
|
900
836
|
if verification_errors:
|
|
901
837
|
raise ReportAssemblyError(tuple(
|
|
902
838
|
AssemblyIssue("report-writer", str(target), "technicalVerification", error)
|
|
@@ -62,7 +62,10 @@ from .final_report_paths import (
|
|
|
62
62
|
final_report_markdown_path,
|
|
63
63
|
sidecar_source_rel,
|
|
64
64
|
)
|
|
65
|
-
from .
|
|
65
|
+
from .run_artifact_prune import prune_run_artifacts
|
|
66
|
+
from .handoff import record_verified
|
|
67
|
+
from .handoff_error import HandoffError
|
|
68
|
+
from .paths import RunRef, task_dir, task_manifest_file
|
|
66
69
|
from .report_view_artifacts import html_view_path
|
|
67
70
|
from .release_gate import release_handoff_allowed
|
|
68
71
|
from .report_translation_dispatch import TranslateOutcome, translate_report
|
|
@@ -73,6 +76,7 @@ from .stage_targets import (
|
|
|
73
76
|
integrate_and_teardown_whole_task,
|
|
74
77
|
)
|
|
75
78
|
from .session import observe_lead_session
|
|
79
|
+
from .lead_progress import record_checkpoint
|
|
76
80
|
from .error_log_write import record_runtime_failure
|
|
77
81
|
|
|
78
82
|
# 포인터 값 타입만 쓴다. `okstra_project.phase_pointer` 는 okstra 안의
|
|
@@ -91,10 +95,12 @@ STEP_TRANSLATE = "translate"
|
|
|
91
95
|
STEP_TOKEN_USAGE = "token-usage"
|
|
92
96
|
STEP_RENDER_VIEWS = "render-views"
|
|
93
97
|
STEP_SPAWN_FOLLOWUPS = "spawn-followups"
|
|
98
|
+
STEP_RECORD_VERIFIED = "record-verified"
|
|
94
99
|
STEP_VALIDATE_RUN = "validate-run"
|
|
95
100
|
STEP_RECORD_GROUP_MEMORY = "record-group-memory"
|
|
96
101
|
STEP_TEARDOWN_STAGES = "teardown-stages"
|
|
97
102
|
STEP_PREFLIGHT = "preflight"
|
|
103
|
+
STEP_PRUNE_RUN_ARTIFACTS = "prune-run-artifacts"
|
|
98
104
|
|
|
99
105
|
STEP_ORDER = (
|
|
100
106
|
STEP_PROJECT_ACTIVITY,
|
|
@@ -102,13 +108,15 @@ STEP_ORDER = (
|
|
|
102
108
|
STEP_TOKEN_USAGE,
|
|
103
109
|
STEP_RENDER_VIEWS,
|
|
104
110
|
STEP_SPAWN_FOLLOWUPS,
|
|
111
|
+
STEP_RECORD_VERIFIED,
|
|
105
112
|
STEP_VALIDATE_RUN,
|
|
106
113
|
# After validation: the group's sibling tasks read this run's conclusion
|
|
107
114
|
# from `group-context.md`, and only a validated record is worth handing on.
|
|
108
115
|
STEP_RECORD_GROUP_MEMORY,
|
|
109
|
-
#
|
|
116
|
+
# Only after the run validated: it removes the stage worktrees a
|
|
110
117
|
# blocked verdict would send the user straight back to.
|
|
111
118
|
STEP_TEARDOWN_STAGES,
|
|
119
|
+
STEP_PRUNE_RUN_ARTIFACTS,
|
|
112
120
|
)
|
|
113
121
|
|
|
114
122
|
V3_STEP_ORDER = (
|
|
@@ -119,9 +127,12 @@ V3_STEP_ORDER = (
|
|
|
119
127
|
STEP_TRANSLATE,
|
|
120
128
|
STEP_RENDER_VIEWS,
|
|
121
129
|
STEP_SPAWN_FOLLOWUPS,
|
|
130
|
+
# Needs the assembled record; `validate-run` requires the row it writes.
|
|
131
|
+
STEP_RECORD_VERIFIED,
|
|
122
132
|
STEP_VALIDATE_RUN,
|
|
123
133
|
STEP_RECORD_GROUP_MEMORY,
|
|
124
134
|
STEP_TEARDOWN_STAGES,
|
|
135
|
+
STEP_PRUNE_RUN_ARTIFACTS,
|
|
125
136
|
)
|
|
126
137
|
|
|
127
138
|
# 앞 단계가 실패하면 건너뛰는 단계. 하나는 worktree 를 거두고, 하나는 검증 안 된
|
|
@@ -391,6 +402,10 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
|
|
|
391
402
|
ctx.task_key,
|
|
392
403
|
],
|
|
393
404
|
),
|
|
405
|
+
(
|
|
406
|
+
STEP_RECORD_VERIFIED,
|
|
407
|
+
["<in-process>", "record-verified", str(ctx.data_path)],
|
|
408
|
+
),
|
|
394
409
|
(STEP_VALIDATE_RUN, _validate_run_command(ctx, ctx.data_path)),
|
|
395
410
|
(
|
|
396
411
|
STEP_RECORD_GROUP_MEMORY,
|
|
@@ -400,6 +415,10 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
|
|
|
400
415
|
STEP_TEARDOWN_STAGES,
|
|
401
416
|
["<in-process>", "teardown-stages", str(ctx.data_path)],
|
|
402
417
|
),
|
|
418
|
+
(
|
|
419
|
+
STEP_PRUNE_RUN_ARTIFACTS,
|
|
420
|
+
["<in-process>", "prune-run-artifacts", str(ctx.data_path)],
|
|
421
|
+
),
|
|
403
422
|
]
|
|
404
423
|
if ctx.report_contract_version != "3.0":
|
|
405
424
|
return commands
|
|
@@ -502,6 +521,51 @@ def _next_in_group(steps: Sequence[Mapping[str, Any]]) -> dict[str, str] | None:
|
|
|
502
521
|
return None
|
|
503
522
|
|
|
504
523
|
|
|
524
|
+
def _record_verified_stages(
|
|
525
|
+
ctx: FinalizeContext,
|
|
526
|
+
command: list[str],
|
|
527
|
+
) -> subprocess.CompletedProcess:
|
|
528
|
+
"""Write a `verified` row for every stage a release-ready verdict clears.
|
|
529
|
+
|
|
530
|
+
`handoff record-verified` needs the assembled `data.json` and `validate-run`
|
|
531
|
+
requires its rows, and both happen inside this one call, so the lead had no
|
|
532
|
+
point at which to run it (observed 2026-09-26, dev-11054). Re-recording the
|
|
533
|
+
same report is a no-op, so a resumed finalize may run this again.
|
|
534
|
+
"""
|
|
535
|
+
def _done(payload: Mapping[str, Any]) -> subprocess.CompletedProcess:
|
|
536
|
+
return subprocess.CompletedProcess(command, 0, json.dumps(payload), "")
|
|
537
|
+
|
|
538
|
+
if ctx.task_type != "final-verification":
|
|
539
|
+
return _done({"skipped": "not a final-verification run"})
|
|
540
|
+
try:
|
|
541
|
+
data = load_owned_object(Path(ctx.data_path), artifact="final report record")
|
|
542
|
+
except JsonBoundaryError as exc:
|
|
543
|
+
return subprocess.CompletedProcess(
|
|
544
|
+
command, 1, "", f"cannot read final-report data.json: {exc}")
|
|
545
|
+
if data.get("verificationScope") not in ("single-stage", "whole-task"):
|
|
546
|
+
return _done({"skipped": "report names no verification scope"})
|
|
547
|
+
if not release_handoff_allowed(data):
|
|
548
|
+
return _done({"skipped": "verdict does not clear the work for release"})
|
|
549
|
+
stages = sorted({
|
|
550
|
+
row["stage"]
|
|
551
|
+
for row in (data.get("finalVerification") or {}).get("stageReports") or ()
|
|
552
|
+
if isinstance(row, Mapping) and isinstance(row.get("stage"), int)
|
|
553
|
+
})
|
|
554
|
+
try:
|
|
555
|
+
plan_run_root = (
|
|
556
|
+
RunRef.from_report_path(Path(ctx.data_path).resolve())
|
|
557
|
+
.sibling("implementation-planning").run_dir
|
|
558
|
+
)
|
|
559
|
+
for stage in stages:
|
|
560
|
+
record_verified(
|
|
561
|
+
plan_run_root=plan_run_root, stage=stage,
|
|
562
|
+
report_path=str(ctx.data_path), data_json=str(ctx.data_path),
|
|
563
|
+
)
|
|
564
|
+
except (HandoffError, ValueError, OSError) as exc:
|
|
565
|
+
return subprocess.CompletedProcess(command, 1, "", str(exc))
|
|
566
|
+
return _done({"recorded": stages})
|
|
567
|
+
|
|
568
|
+
|
|
505
569
|
def _teardown_stage_worktrees(
|
|
506
570
|
ctx: FinalizeContext,
|
|
507
571
|
command: list[str],
|
|
@@ -538,6 +602,27 @@ def _teardown_stage_worktrees(
|
|
|
538
602
|
return _done(result)
|
|
539
603
|
|
|
540
604
|
|
|
605
|
+
def _prune_run_artifacts(
|
|
606
|
+
ctx: FinalizeContext,
|
|
607
|
+
command: list[str],
|
|
608
|
+
) -> subprocess.CompletedProcess:
|
|
609
|
+
"""이 run 디렉터리에서 run 종료 뒤 읽는 곳이 없는 산출물을 지운다.
|
|
610
|
+
|
|
611
|
+
실패한 run 에서도 돈다. 빌드 디렉터리는 남은 lockfile 로 다시 만들고, dispatch
|
|
612
|
+
스냅샷은 검증을 통과한 run 의 것만 지운다(`run_artifact_prune`).
|
|
613
|
+
"""
|
|
614
|
+
reports_dir = Path(ctx.data_path).resolve().parent
|
|
615
|
+
if reports_dir.name != "reports":
|
|
616
|
+
return subprocess.CompletedProcess(
|
|
617
|
+
command, 1, "", f"report record is not under a run's reports/: {ctx.data_path}")
|
|
618
|
+
run_dir = reports_dir.parent
|
|
619
|
+
try:
|
|
620
|
+
result = prune_run_artifacts(ctx.project_root, run_dir, apply=True)
|
|
621
|
+
except OSError as exc:
|
|
622
|
+
return subprocess.CompletedProcess(command, 1, "", str(exc))
|
|
623
|
+
return subprocess.CompletedProcess(command, 0, json.dumps(result), "")
|
|
624
|
+
|
|
625
|
+
|
|
541
626
|
def _validate_run_command(ctx: FinalizeContext, report_record_path: Path) -> list[str]:
|
|
542
627
|
command = [
|
|
543
628
|
sys.executable,
|
|
@@ -875,8 +960,12 @@ def _run_finalize_step(
|
|
|
875
960
|
return _run_translate(ctx, command)
|
|
876
961
|
if name == STEP_TEARDOWN_STAGES:
|
|
877
962
|
return _teardown_stage_worktrees(ctx, command)
|
|
963
|
+
if name == STEP_PRUNE_RUN_ARTIFACTS:
|
|
964
|
+
return _prune_run_artifacts(ctx, command)
|
|
878
965
|
if name == STEP_RECORD_GROUP_MEMORY:
|
|
879
966
|
return _record_group_memory(ctx, command)
|
|
967
|
+
if name == STEP_RECORD_VERIFIED:
|
|
968
|
+
return _record_verified_stages(ctx, command)
|
|
880
969
|
if name == STEP_VALIDATE_RUN:
|
|
881
970
|
try:
|
|
882
971
|
_link_lead_result_for_validation(ctx)
|
|
@@ -1066,6 +1155,9 @@ Runs the Phase 7 steps in their contractual order against one final-report:
|
|
|
1066
1155
|
5. spawn-followups turn section 4 rows into task stubs
|
|
1067
1156
|
6. validate-run validate the finished run artifacts
|
|
1068
1157
|
7. record-group-memory / 8. teardown-stages after a clean validation
|
|
1158
|
+
9. prune-run-artifacts remove node_modules and .next under this run
|
|
1159
|
+
directory, and the dispatch snapshots and publication
|
|
1160
|
+
locks of a validated run; runs after a failure too
|
|
1069
1161
|
|
|
1070
1162
|
Every step is idempotent, so re-running after a fixed failure is safe. The
|
|
1071
1163
|
sequence stops at the first non-zero exit and reports which step failed, except
|
|
@@ -1075,6 +1167,10 @@ exits non-zero the command prints the --only flags that resume the sequence
|
|
|
1075
1167
|
from the earliest failing step — follow those rather than rerunning the failing
|
|
1076
1168
|
step alone, which would leave the html rendered before the tokens landed.
|
|
1077
1169
|
|
|
1170
|
+
A full run (no --only) first records the lead's `phase-7-persist` checkpoint
|
|
1171
|
+
and returns it as `progressLines`; the lead emits that line instead of calling
|
|
1172
|
+
`okstra lead-progress append` for it.
|
|
1173
|
+
|
|
1078
1174
|
This is the same code path the Codex lead adapter runs automatically after its
|
|
1079
1175
|
report-writer completes, so a Claude-led and a Codex-led run finalize
|
|
1080
1176
|
identically.
|
|
@@ -1264,7 +1360,15 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
1264
1360
|
# the generations resume and compaction split it into. It runs ahead of the
|
|
1265
1361
|
# sequence because the token-usage step below reads `leadSessionIds`.
|
|
1266
1362
|
observe_lead_session(ctx.project_root, ctx.team_state_path)
|
|
1363
|
+
# 재개(`--only`)는 Phase 7 을 새로 시작하지 않고 다시 들어가는 것이다. 이 행은
|
|
1364
|
+
# 그것을 요구하는 `validate-run` 보다 먼저 있어야 한다.
|
|
1365
|
+
progress_line = None if args.only else record_checkpoint(
|
|
1366
|
+
ctx.project_root, ctx.manifest_path, "phase-7-persist",
|
|
1367
|
+
source="okstra report-finalize",
|
|
1368
|
+
)
|
|
1267
1369
|
result = run_finalize(ctx, only=args.only or None)
|
|
1370
|
+
if progress_line:
|
|
1371
|
+
result["progressLines"] = [progress_line]
|
|
1268
1372
|
order = V3_STEP_ORDER if ctx.report_contract_version == "3.0" else STEP_ORDER
|
|
1269
1373
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
1270
1374
|
print("finalize steps:", file=sys.stderr)
|
|
@@ -247,7 +247,7 @@ def approved_plan_links(
|
|
|
247
247
|
except JsonBoundaryError:
|
|
248
248
|
return {}
|
|
249
249
|
from .common import anchor_index
|
|
250
|
-
from .
|
|
250
|
+
from okstra_ctl.phases.implementation_planning.report import ANCHORED_FIELDS
|
|
251
251
|
|
|
252
252
|
page = html_view_path(record)
|
|
253
253
|
href = Path(os.path.relpath(page, data_path.resolve().parent)).as_posix()
|
|
@@ -4,10 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
from okstra_token_usage.blocks import accounting_workers
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
-
from
|
|
8
|
-
from typing import Any, Mapping, Sequence
|
|
7
|
+
from typing import Any, Mapping
|
|
9
8
|
|
|
10
|
-
from .design_surfaces import DesignSurfaceTrigger, detect_design_surfaces
|
|
11
9
|
from .report_contract import execution_roles_from_manifest
|
|
12
10
|
from .usage_cells import duration_ms_from_bounds
|
|
13
11
|
|
|
@@ -220,39 +218,6 @@ def project_token_usage(team_state: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
|
|
223
|
-
def _trigger_rows(trigger: DesignSurfaceTrigger) -> list[dict[str, Any]]:
|
|
224
|
-
return [
|
|
225
|
-
{"step": item.step, "field": item.field, "match": item.match}
|
|
226
|
-
for item in trigger.evidence
|
|
227
|
-
]
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def project_design(
|
|
231
|
-
planning: Mapping[str, Any], detector_snapshot: Mapping[str, Any],
|
|
232
|
-
) -> dict[str, Any]:
|
|
233
|
-
"""탐지 결과가 실제 계획 표면과 일치할 때만 설계 블록을 반환한다."""
|
|
234
|
-
triggers = detect_design_surfaces(planning)
|
|
235
|
-
expected = {(row.stage, row.kind): _trigger_rows(row) for row in triggers}
|
|
236
|
-
coverage = detector_snapshot.get("stageCoverage")
|
|
237
|
-
coverage = coverage if isinstance(coverage, list) else []
|
|
238
|
-
actual: dict[tuple[int, str], list[dict[str, Any]]] = {}
|
|
239
|
-
for stage in coverage:
|
|
240
|
-
if not isinstance(stage, Mapping):
|
|
241
|
-
raise ReportProjectionError("owner=design-surface-detector invalid stageCoverage row")
|
|
242
|
-
for row in stage.get("rows") or []:
|
|
243
|
-
if isinstance(row, Mapping):
|
|
244
|
-
actual[(int(stage.get("stage") or 0), str(row.get("kind") or ""))] = list(row.get("triggerEvidence") or [])
|
|
245
|
-
if expected != actual:
|
|
246
|
-
raise ReportProjectionError("owner=design-surface-detector trigger coverage mismatch")
|
|
247
|
-
preparation = detector_snapshot.get("designPreparation")
|
|
248
|
-
if not isinstance(preparation, Mapping):
|
|
249
|
-
raise ReportProjectionError("owner=design-surface-detector missing designPreparation")
|
|
250
|
-
return {
|
|
251
|
-
"designPreparation": deepcopy(dict(preparation)),
|
|
252
|
-
"stageCoverage": deepcopy(coverage),
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
221
|
def _round_history(state: Mapping[str, Any]) -> dict[str, Any]:
|
|
257
222
|
if not (state.get("config") or {}).get("enabled", True):
|
|
258
223
|
return {"disabled": True}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""보고서 생산 단계와 분리된 공통 이동 적합성 검사."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def technical_verification_routing_errors(data: Mapping[str, Any]) -> list[str]:
|
|
9
|
+
"""기술 검증의 후속 목적지가 후보 비교인지 검사하며 목적지를 추론하지 않는다."""
|
|
10
|
+
if (data.get("header") or {}).get("taskType") != "technical-verification":
|
|
11
|
+
return []
|
|
12
|
+
block = data.get("technicalVerification") or {}
|
|
13
|
+
if (block.get("routing") or {}).get("nextTaskType") != "implementation-option-selection":
|
|
14
|
+
return ["technical verification returns only to implementation-option-selection"]
|
|
15
|
+
return []
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def fanout_routing_errors(packet_name: str, target: str | None) -> list[str]:
|
|
19
|
+
"""작업 분해 패킷의 후속 단계가 허용된 값인지 검사한다."""
|
|
20
|
+
allowed = ("error-analysis", "implementation-option-selection")
|
|
21
|
+
if target not in allowed:
|
|
22
|
+
return [f"{packet_name}: recommended-next-phase not in {allowed}"]
|
|
23
|
+
return []
|