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,183 @@
|
|
|
1
|
+
"""dispatch 배치 하나를 한 번의 호출로 materialize 하고, 원하면 jobs 파일까지 쓴다.
|
|
2
|
+
|
|
3
|
+
항목마다 단일 `materialize` 호출과 같은 파서와 `_materialize` 를 거치므로, 항목은
|
|
4
|
+
그 호출이 낼 오류와 똑같이 실패한다. 여기에는 반복과 jobs 넘기기만 있다.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Callable
|
|
11
|
+
|
|
12
|
+
from ..invocation import AgentInvocationError
|
|
13
|
+
from ...dispatch_state import DispatchError
|
|
14
|
+
from ...json_boundary import JsonBoundaryError, load_owned_object
|
|
15
|
+
from ...worker_prompt_headers import WorkerPromptHeaderError
|
|
16
|
+
from .emit import _prepared_payload
|
|
17
|
+
from .inputs import AgentPromptCliError, _project_input, _project_root
|
|
18
|
+
from .jobs import check_jobs_target, generate_jobs
|
|
19
|
+
from .materialize import _materialize
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_ENTRY_FAILURES = (
|
|
23
|
+
AgentPromptCliError, AgentInvocationError, DispatchError,
|
|
24
|
+
WorkerPromptHeaderError, OSError, ValueError,
|
|
25
|
+
)
|
|
26
|
+
_REQUIRED_FLAGS = ("invocation-id", "audience", "instruction", "prompt")
|
|
27
|
+
_STANDALONE_FLAGS = frozenset({
|
|
28
|
+
"host-runtime", "terminal-backend", "provider", "model-role", "model", "purpose",
|
|
29
|
+
})
|
|
30
|
+
_ENTRY_FLAGS = frozenset({
|
|
31
|
+
"invocation-id", "audience", "instruction", "prompt", "worker-id",
|
|
32
|
+
"dispatch-kind", "assignment-ref", "source-role-execution-ref", "result",
|
|
33
|
+
"corrections", "audit-source", "replace-undispatched",
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def materialize_batch(
|
|
38
|
+
args: argparse.Namespace,
|
|
39
|
+
parser_factory: Callable[[], argparse.ArgumentParser],
|
|
40
|
+
) -> dict[str, object]:
|
|
41
|
+
entries, dispatch_kind = _validated_batch(args, parser_factory)
|
|
42
|
+
prepared = _publish(entries)
|
|
43
|
+
jobs_path = write_jobs(args, prepared, dispatch_kind) if args.jobs_out else None
|
|
44
|
+
return {"ok": True, "invocations": prepared, "jobsPath": jobs_path}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _validated_batch(
|
|
48
|
+
args: argparse.Namespace,
|
|
49
|
+
parser_factory: Callable[[], argparse.ArgumentParser],
|
|
50
|
+
) -> tuple[list[argparse.Namespace], str]:
|
|
51
|
+
"""배치 전체를 발행 전에 검사한다 — 어느 항목도 쓰기 전에 거부할 수 있는 것은 전부 여기서."""
|
|
52
|
+
if not args.run_manifest:
|
|
53
|
+
raise AgentPromptCliError("materialize --batch requires --run-manifest")
|
|
54
|
+
stray = sorted(
|
|
55
|
+
"--" + flag for flag in _ENTRY_FLAGS | _STANDALONE_FLAGS
|
|
56
|
+
if getattr(args, flag.replace("-", "_"))
|
|
57
|
+
)
|
|
58
|
+
if stray:
|
|
59
|
+
raise AgentPromptCliError(
|
|
60
|
+
"with --batch pass only --project-root, --run-manifest, --jobs-out "
|
|
61
|
+
"and --json; per-invocation flags belong in the batch entries: "
|
|
62
|
+
+ ", ".join(stray)
|
|
63
|
+
)
|
|
64
|
+
project = _project_root(args.project_root)
|
|
65
|
+
entries = [
|
|
66
|
+
_entry_args(args, index, entry, parser_factory)
|
|
67
|
+
for index, entry in enumerate(_load_entries(project, args.batch), start=1)
|
|
68
|
+
]
|
|
69
|
+
ids = [entry.invocation_id for entry in entries]
|
|
70
|
+
if len(set(ids)) != len(ids):
|
|
71
|
+
raise AgentPromptCliError("batch repeats an invocation id")
|
|
72
|
+
kinds = {entry.dispatch_kind for entry in entries}
|
|
73
|
+
if args.jobs_out and len(kinds) != 1:
|
|
74
|
+
raise AgentPromptCliError(
|
|
75
|
+
"--jobs-out needs one dispatch kind; this batch has "
|
|
76
|
+
+ ", ".join(sorted(str(kind) for kind in kinds))
|
|
77
|
+
)
|
|
78
|
+
if args.jobs_out:
|
|
79
|
+
check_jobs_target(args.project_root, args.run_manifest, args.jobs_out)
|
|
80
|
+
return entries, next(iter(kinds))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _publish(entries: list[argparse.Namespace]) -> list[dict[str, object]]:
|
|
84
|
+
prepared: list[dict[str, object]] = []
|
|
85
|
+
for index, entry in enumerate(entries, start=1):
|
|
86
|
+
try:
|
|
87
|
+
prepared.append(_prepared_payload(_materialize(entry), entry.audience))
|
|
88
|
+
except _ENTRY_FAILURES as exc:
|
|
89
|
+
raise AgentPromptCliError(
|
|
90
|
+
f"batch entry {index} ({entry.invocation_id}): {exc}; "
|
|
91
|
+
+ _published_note(prepared)
|
|
92
|
+
) from exc
|
|
93
|
+
return prepared
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def write_jobs(
|
|
97
|
+
args: argparse.Namespace,
|
|
98
|
+
prepared: list[dict[str, object]],
|
|
99
|
+
dispatch_kind: str,
|
|
100
|
+
) -> str:
|
|
101
|
+
"""materialize 된 호출들을 그대로 `agent-prompt jobs` 에 넘긴다."""
|
|
102
|
+
try:
|
|
103
|
+
return str(generate_jobs(argparse.Namespace(
|
|
104
|
+
project_root=args.project_root,
|
|
105
|
+
run_manifest=args.run_manifest,
|
|
106
|
+
dispatch_kind=dispatch_kind,
|
|
107
|
+
metadata=[str(row["metadataPath"]) for row in prepared],
|
|
108
|
+
out=args.jobs_out,
|
|
109
|
+
))["jobsPath"])
|
|
110
|
+
except _ENTRY_FAILURES as exc:
|
|
111
|
+
raise AgentPromptCliError(
|
|
112
|
+
f"jobs file not written: {exc}; " + _published_note(prepared)
|
|
113
|
+
) from exc
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _published_note(prepared: list[dict[str, object]]) -> str:
|
|
117
|
+
if not prepared:
|
|
118
|
+
return "no entry was materialized"
|
|
119
|
+
# 발행된 항목은 발행된 채 남는다. 같은 배치를 다시 돌리면 새로 쓰지 않고
|
|
120
|
+
# 동일한 프롬프트·메타데이터·예약을 재사용한다.
|
|
121
|
+
return (
|
|
122
|
+
"already materialized and reused unchanged by an identical rerun: "
|
|
123
|
+
+ ", ".join(str(row["invocationId"]) for row in prepared)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _load_entries(project: Path, raw: str) -> list[object]:
|
|
128
|
+
path = _project_input(project, raw, "batch")
|
|
129
|
+
try:
|
|
130
|
+
payload = load_owned_object(path, artifact="agent prompt batch")
|
|
131
|
+
except JsonBoundaryError as exc:
|
|
132
|
+
raise AgentPromptCliError(f"batch file is invalid: {exc}") from exc
|
|
133
|
+
entries = payload.get("invocations")
|
|
134
|
+
if set(payload) != {"invocations"} or not isinstance(entries, list) or not entries:
|
|
135
|
+
raise AgentPromptCliError(
|
|
136
|
+
'batch file must be {"invocations": [...]} with at least one entry'
|
|
137
|
+
)
|
|
138
|
+
return entries
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _entry_args(
|
|
142
|
+
args: argparse.Namespace,
|
|
143
|
+
index: int,
|
|
144
|
+
entry: object,
|
|
145
|
+
parser_factory: Callable[[], argparse.ArgumentParser],
|
|
146
|
+
) -> argparse.Namespace:
|
|
147
|
+
if not isinstance(entry, dict):
|
|
148
|
+
raise AgentPromptCliError(f"batch entry {index} must be an object")
|
|
149
|
+
unknown = sorted(set(entry) - _ENTRY_FLAGS)
|
|
150
|
+
if unknown:
|
|
151
|
+
raise AgentPromptCliError(
|
|
152
|
+
f"batch entry {index} has keys outside the materialize flags: "
|
|
153
|
+
+ ", ".join(unknown)
|
|
154
|
+
)
|
|
155
|
+
argv = [
|
|
156
|
+
"materialize",
|
|
157
|
+
"--project-root", str(args.project_root),
|
|
158
|
+
"--run-manifest", str(args.run_manifest),
|
|
159
|
+
]
|
|
160
|
+
for key, value in entry.items():
|
|
161
|
+
if value is True and key == "replace-undispatched":
|
|
162
|
+
argv.append(f"--{key}")
|
|
163
|
+
elif isinstance(value, str) and key != "replace-undispatched":
|
|
164
|
+
argv.extend([f"--{key}", value])
|
|
165
|
+
else:
|
|
166
|
+
raise AgentPromptCliError(f"batch entry {index}: invalid value for {key}")
|
|
167
|
+
try:
|
|
168
|
+
parsed = parser_factory().parse_args(argv)
|
|
169
|
+
except SystemExit as exc:
|
|
170
|
+
raise AgentPromptCliError(
|
|
171
|
+
f"batch entry {index}: invalid materialize arguments"
|
|
172
|
+
) from exc
|
|
173
|
+
require_invocation_flags(parsed, f"batch entry {index}")
|
|
174
|
+
return parsed
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def require_invocation_flags(args: argparse.Namespace, label: str) -> None:
|
|
178
|
+
missing = [
|
|
179
|
+
"--" + flag for flag in _REQUIRED_FLAGS
|
|
180
|
+
if not getattr(args, flag.replace("-", "_"))
|
|
181
|
+
]
|
|
182
|
+
if missing:
|
|
183
|
+
raise AgentPromptCliError(f"{label} requires: " + ", ".join(missing))
|
|
@@ -16,6 +16,8 @@ from ...error_log_write import record_runtime_failure
|
|
|
16
16
|
from ...dispatch_state import BACKEND_CLI_WRAPPER, BACKEND_CMUX_PANE, DispatchError
|
|
17
17
|
from .emit import _emit, _prepared_payload
|
|
18
18
|
from .inputs import AgentPromptCliError
|
|
19
|
+
from .batch import materialize_batch, require_invocation_flags, write_jobs
|
|
20
|
+
from .jobs import check_jobs_target
|
|
19
21
|
from .materialize import _apply_corrections, _check_corrections, _materialize
|
|
20
22
|
from .jobs import generate_jobs
|
|
21
23
|
from .results import (
|
|
@@ -31,7 +33,8 @@ from .results import (
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
_CLI_EPILOG = r"""Usage:
|
|
34
|
-
okstra agent-prompt materialize [options]
|
|
36
|
+
okstra agent-prompt materialize [options] [--jobs-out <path>]
|
|
37
|
+
okstra agent-prompt materialize --project-root <dir> --run-manifest <path> --batch <file> [--jobs-out <path>]
|
|
35
38
|
okstra agent-prompt jobs --project-root <dir> --run-manifest <path> --dispatch-kind <kind> --metadata <path> [--metadata <path>] --out <path>
|
|
36
39
|
okstra agent-prompt check-corrections --project-root <dir> --run-manifest <path> --corrections <file>
|
|
37
40
|
okstra agent-prompt apply-corrections --project-root <dir> --run-manifest <path> --corrections <file>
|
|
@@ -63,6 +66,10 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
63
66
|
help="resolve the role, worker count and models an operation requires",
|
|
64
67
|
)
|
|
65
68
|
operation.add_argument("--operation", required=True)
|
|
69
|
+
operation.add_argument(
|
|
70
|
+
"--count", type=int, default=None,
|
|
71
|
+
help="slots to open, from 1 to the operation contract's count (default: the contract's count)",
|
|
72
|
+
)
|
|
66
73
|
operation.add_argument("--json", action="store_true")
|
|
67
74
|
jobs = commands.add_parser("jobs", help="generate a verified jobs file without dispatching")
|
|
68
75
|
_common_paths(jobs)
|
|
@@ -77,10 +84,11 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
77
84
|
def _add_materialize_parser(commands: argparse._SubParsersAction) -> None:
|
|
78
85
|
materialize = commands.add_parser("materialize")
|
|
79
86
|
_common_paths(materialize)
|
|
80
|
-
|
|
81
|
-
materialize.add_argument("--
|
|
82
|
-
materialize.add_argument("--
|
|
83
|
-
materialize.add_argument("--
|
|
87
|
+
single_only = "required unless --batch is given"
|
|
88
|
+
materialize.add_argument("--invocation-id", help=single_only)
|
|
89
|
+
materialize.add_argument("--audience", help=single_only)
|
|
90
|
+
materialize.add_argument("--instruction", help=single_only)
|
|
91
|
+
materialize.add_argument("--prompt", help=single_only)
|
|
84
92
|
# 두 갈래의 인자를 한 파서가 받는다. 어느 갈래의 것인지 help 에 적지 않으면
|
|
85
93
|
# `--help` 가 조건 없이 나열한 인자를 run 갈래가 거절해 놀라게 된다.
|
|
86
94
|
run_only = "run mode only (with --run-manifest)"
|
|
@@ -130,6 +138,20 @@ def _add_materialize_parser(commands: argparse._SubParsersAction) -> None:
|
|
|
130
138
|
"reservations are append-only, so a rewrite needs a fresh "
|
|
131
139
|
"invocation id and prompt path",
|
|
132
140
|
)
|
|
141
|
+
materialize.add_argument(
|
|
142
|
+
"--batch",
|
|
143
|
+
help="run mode only: a JSON file {\"invocations\": [...]} whose "
|
|
144
|
+
"entries map the per-invocation flags above (without the leading "
|
|
145
|
+
"--) to values, one entry per worker of the batch; each entry is "
|
|
146
|
+
"materialized exactly as a single call would be",
|
|
147
|
+
)
|
|
148
|
+
materialize.add_argument(
|
|
149
|
+
"--jobs-out",
|
|
150
|
+
help="run mode only: also write the verified jobs file for "
|
|
151
|
+
"`okstra team dispatch --jobs-file` from the materialized "
|
|
152
|
+
"invocations, as `okstra agent-prompt jobs` would; every batch "
|
|
153
|
+
"entry must share one --dispatch-kind",
|
|
154
|
+
)
|
|
133
155
|
materialize.add_argument("--json", action="store_true")
|
|
134
156
|
|
|
135
157
|
check = commands.add_parser(
|
|
@@ -277,7 +299,7 @@ def _refreeze_contracts(args: argparse.Namespace) -> dict[str, object]:
|
|
|
277
299
|
}
|
|
278
300
|
|
|
279
301
|
|
|
280
|
-
def _resolve_operation(operation_id: str) -> dict[str, object]:
|
|
302
|
+
def _resolve_operation(operation_id: str, count: int | None) -> dict[str, object]:
|
|
281
303
|
"""이 작업이 열 칸과 각 칸의 모델. 기술은 작업 이름만 알면 된다."""
|
|
282
304
|
from ...model_pool import ModelPool
|
|
283
305
|
from ...operation_invocation import (
|
|
@@ -298,6 +320,7 @@ def _resolve_operation(operation_id: str) -> dict[str, object]:
|
|
|
298
320
|
operation_id,
|
|
299
321
|
contract_root=root,
|
|
300
322
|
pool=ModelPool.from_registry(default_provider_registry()),
|
|
323
|
+
count=count,
|
|
301
324
|
)
|
|
302
325
|
except OperationPreparationError as exc:
|
|
303
326
|
raise AgentPromptCliError(str(exc)) from exc
|
|
@@ -318,11 +341,38 @@ def _resolve_operation(operation_id: str) -> dict[str, object]:
|
|
|
318
341
|
}
|
|
319
342
|
|
|
320
343
|
|
|
344
|
+
def _materialize_batch_command(args: argparse.Namespace) -> int:
|
|
345
|
+
payload = materialize_batch(args, _parser)
|
|
346
|
+
if args.json:
|
|
347
|
+
_emit(payload, True)
|
|
348
|
+
return 0
|
|
349
|
+
for row in payload["invocations"]:
|
|
350
|
+
print(row["promptPath"])
|
|
351
|
+
if payload["jobsPath"]:
|
|
352
|
+
print(payload["jobsPath"])
|
|
353
|
+
return 0
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _materialize_single_command(args: argparse.Namespace) -> int:
|
|
357
|
+
require_invocation_flags(args, "materialize")
|
|
358
|
+
if args.jobs_out:
|
|
359
|
+
if not args.run_manifest:
|
|
360
|
+
raise AgentPromptCliError("--jobs-out requires --run-manifest")
|
|
361
|
+
check_jobs_target(args.project_root, args.run_manifest, args.jobs_out)
|
|
362
|
+
payload = _prepared_payload(_materialize(args), args.audience)
|
|
363
|
+
if args.jobs_out:
|
|
364
|
+
payload["jobsPath"] = write_jobs(args, [payload], args.dispatch_kind)
|
|
365
|
+
_emit(payload, args.json)
|
|
366
|
+
if args.jobs_out and not args.json:
|
|
367
|
+
print(payload["jobsPath"])
|
|
368
|
+
return 0
|
|
369
|
+
|
|
370
|
+
|
|
321
371
|
def main(argv: list[str] | None = None) -> int:
|
|
322
372
|
try:
|
|
323
373
|
args = _parser().parse_args(argv)
|
|
324
374
|
if args.command == "resolve-operation":
|
|
325
|
-
payload = _resolve_operation(args.operation)
|
|
375
|
+
payload = _resolve_operation(args.operation, args.count)
|
|
326
376
|
if args.json:
|
|
327
377
|
_emit(payload, True)
|
|
328
378
|
else:
|
|
@@ -346,9 +396,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
346
396
|
print(payload["jobsPath"])
|
|
347
397
|
return 0
|
|
348
398
|
if args.command == "materialize":
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return
|
|
399
|
+
if args.batch:
|
|
400
|
+
return _materialize_batch_command(args)
|
|
401
|
+
return _materialize_single_command(args)
|
|
352
402
|
if args.command == "check-corrections":
|
|
353
403
|
payload = _check_corrections(args)
|
|
354
404
|
if args.json:
|
|
@@ -25,7 +25,7 @@ from ..activity import (
|
|
|
25
25
|
)
|
|
26
26
|
from ...final_report_schema import load_schema_version
|
|
27
27
|
from ...json_boundary import load_owned_object
|
|
28
|
-
from ...
|
|
28
|
+
from ...phases.implementation_option_selection.validation import validate_implementation_option_selection
|
|
29
29
|
from ...report_contract import TASK_TYPE_DATA_PROPERTY
|
|
30
30
|
from ...report_assembly import validate_plan_draft
|
|
31
31
|
from ...report_corrections import (
|
|
@@ -19,13 +19,30 @@ from .inputs import (
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
def check_jobs_target(project_root: str, run_manifest: str, out: str) -> None:
|
|
23
|
+
"""jobs 파일을 쓸 수 있는 run 인지 — `materialize --jobs-out` 이 발행 전에 부른다.
|
|
24
|
+
|
|
25
|
+
발행 뒤에 이 검사에서 거부되면 프롬프트만 발행된 채 남는다.
|
|
26
|
+
"""
|
|
27
|
+
_jobs_target(project_root, run_manifest, out)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _jobs_target(
|
|
31
|
+
project_root: str, run_manifest: str, out: str,
|
|
32
|
+
) -> tuple[Path, Path, dict, Path]:
|
|
33
|
+
project = _project_root(project_root)
|
|
34
|
+
manifest_path = _project_input(project, run_manifest, "run manifest")
|
|
25
35
|
manifest = _read_json_object(manifest_path, "run manifest")
|
|
26
36
|
if manifest.get("executionIdentityVersion") != 2:
|
|
27
37
|
raise AgentPromptCliError("agent-prompt jobs requires a v2 run manifest")
|
|
28
|
-
output = _authorized_path(project,
|
|
38
|
+
output = _authorized_path(project, out, [manifest["runDirectoryPath"]], "jobs", must_exist=False)
|
|
39
|
+
return project, manifest_path, manifest, output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def generate_jobs(args: argparse.Namespace) -> dict[str, object]:
|
|
43
|
+
project, manifest_path, manifest, output = _jobs_target(
|
|
44
|
+
args.project_root, args.run_manifest, args.out,
|
|
45
|
+
)
|
|
29
46
|
paths = [_project_input(project, value, "metadata") for value in args.metadata]
|
|
30
47
|
rows = [_metadata_job(project, manifest_path, manifest, path, args.dispatch_kind) for path in paths]
|
|
31
48
|
refs = [(row["invocationRef"], row["attempt"]) for row in rows]
|
|
@@ -34,7 +34,11 @@ from ...assignment_environment import load_assignment_context
|
|
|
34
34
|
from ...assignment_resolver import AssignmentContext, resolve_dispatch_assignment
|
|
35
35
|
from ...path_hints import hydrate_active_run_context
|
|
36
36
|
from ...worker_prompt_headers import worker_prompt_headers
|
|
37
|
-
from ...worker_prompt_contract import
|
|
37
|
+
from ...worker_prompt_contract import (
|
|
38
|
+
complete_reverify_instruction,
|
|
39
|
+
validate_plan_verify_dispatch_identity,
|
|
40
|
+
validate_reverify_prompt,
|
|
41
|
+
)
|
|
38
42
|
from ...worker_prompt_policy import (
|
|
39
43
|
critic_assignment_ref,
|
|
40
44
|
is_plan_critic_verification,
|
|
@@ -340,6 +344,11 @@ def _materialize_run(
|
|
|
340
344
|
active_context=active_context,
|
|
341
345
|
))
|
|
342
346
|
body = instruction_path.read_text(encoding="utf-8")
|
|
347
|
+
identity_errors = validate_plan_verify_dispatch_identity(
|
|
348
|
+
body, dispatch_kind=args.dispatch_kind, result_name=result_path.name,
|
|
349
|
+
)
|
|
350
|
+
if identity_errors:
|
|
351
|
+
raise AgentPromptCliError("; ".join(identity_errors))
|
|
343
352
|
is_reverify = is_verification_dispatch_kind(args.dispatch_kind)
|
|
344
353
|
if is_reverify:
|
|
345
354
|
body = _complete_run_reverify_body(args, manifest, active_context, assignment, body, instruction_path)
|
|
@@ -73,7 +73,6 @@ _EVIDENCE_RELATION_BY_TYPES = {
|
|
|
73
73
|
_REPORT_NAME_RE = re.compile(
|
|
74
74
|
r"^final-report-.+-(?P<run_seq>[^-]+)\.data\.json$"
|
|
75
75
|
)
|
|
76
|
-
_FEATURE_ID_RE = re.compile(r"PF-\d{3}")
|
|
77
76
|
_FULL_COMMIT_RE = re.compile(r"[0-9a-f]{40}")
|
|
78
77
|
_RUN_SEQ_RE = re.compile(r"[0-9]{3}")
|
|
79
78
|
|
|
@@ -347,41 +346,3 @@ def resolve_evidence_inputs(
|
|
|
347
346
|
freshness="exact" if candidate.source_commit == current_commit else "stale",
|
|
348
347
|
))
|
|
349
348
|
return tuple(resolved)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
def resolve_analysis_target(
|
|
353
|
-
raw_target: str,
|
|
354
|
-
evidence_inputs: Sequence[ResolvedEvidenceInput],
|
|
355
|
-
candidates: Mapping[Path, AnalysisReportCandidate],
|
|
356
|
-
) -> dict[str, object]:
|
|
357
|
-
"""Normalize a feature-index identifier or a non-empty free-text target."""
|
|
358
|
-
target = raw_target.strip()
|
|
359
|
-
if not target:
|
|
360
|
-
raise AnalysisInputError("analysis target must not be empty")
|
|
361
|
-
if not _FEATURE_ID_RE.fullmatch(target):
|
|
362
|
-
return {"inputMode": "free-text", "requestedValue": target}
|
|
363
|
-
matches: list[dict[object, object]] = []
|
|
364
|
-
for evidence in evidence_inputs:
|
|
365
|
-
if evidence.relation != "project-context":
|
|
366
|
-
continue
|
|
367
|
-
candidate = candidates.get(evidence.report_path)
|
|
368
|
-
if candidate is None:
|
|
369
|
-
candidate = next(
|
|
370
|
-
(value for path, value in candidates.items() if path.resolve() == evidence.report_path),
|
|
371
|
-
None,
|
|
372
|
-
)
|
|
373
|
-
if candidate is None:
|
|
374
|
-
continue
|
|
375
|
-
matches.extend(
|
|
376
|
-
feature for feature in candidate.feature_index
|
|
377
|
-
if isinstance(feature, dict) and feature.get("id") == target
|
|
378
|
-
)
|
|
379
|
-
if len(matches) != 1:
|
|
380
|
-
raise AnalysisInputError(
|
|
381
|
-
f"exactly one project-context feature must match {target}"
|
|
382
|
-
)
|
|
383
|
-
return {
|
|
384
|
-
"inputMode": "feature-index",
|
|
385
|
-
"requestedValue": target,
|
|
386
|
-
"feature": dict(matches[0]),
|
|
387
|
-
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""분석 증거와 단계 내용 검증이 공유하는 프로젝트 상대 경로 판독."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _normalized_project_path(path: object) -> PurePosixPath | None:
|
|
8
|
+
if not isinstance(path, str):
|
|
9
|
+
return None
|
|
10
|
+
raw = path.strip().rstrip("/")
|
|
11
|
+
if not raw or raw == "." or raw.startswith("/"):
|
|
12
|
+
return None
|
|
13
|
+
if any(part in {"", ".", ".."} for part in raw.split("/")):
|
|
14
|
+
return None
|
|
15
|
+
normalized = PurePosixPath(raw)
|
|
16
|
+
if normalized.is_absolute():
|
|
17
|
+
return None
|
|
18
|
+
return normalized
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _path_is_included(path: object, included_paths: list[object]) -> bool:
|
|
22
|
+
candidate = _normalized_project_path(path)
|
|
23
|
+
if candidate is None:
|
|
24
|
+
return False
|
|
25
|
+
for raw in included_paths:
|
|
26
|
+
included = _normalized_project_path(raw)
|
|
27
|
+
if included is not None and (
|
|
28
|
+
candidate == included or included in candidate.parents
|
|
29
|
+
):
|
|
30
|
+
return True
|
|
31
|
+
return False
|
|
@@ -222,7 +222,7 @@ def open_decision(
|
|
|
222
222
|
clarification_id: str, ticket_id: str, statement: str, expected_form: str,
|
|
223
223
|
classification: str, origin: str, user_confirmation: str,
|
|
224
224
|
unblock_condition: str, recommended_disposition: str,
|
|
225
|
-
options: Sequence[DecisionOption],
|
|
225
|
+
options: Sequence[DecisionOption], supersedes: Sequence[str] = (),
|
|
226
226
|
) -> None:
|
|
227
227
|
row = _decision_row(
|
|
228
228
|
clarification_id=clarification_id, ticket_id=ticket_id,
|
|
@@ -237,10 +237,30 @@ def open_decision(
|
|
|
237
237
|
raise ApprovalDecisionError("activeClarifications must be an array")
|
|
238
238
|
if any(item.get("id") == clarification_id for item in active if isinstance(item, Mapping)):
|
|
239
239
|
raise ApprovalDecisionError(f"duplicate active clarification: {clarification_id}")
|
|
240
|
+
if supersedes:
|
|
241
|
+
row["supersedes"] = _superseded_carried_ids(ledger, supersedes)
|
|
240
242
|
active.append(row)
|
|
241
243
|
write_json_atomic(ledger_path, ledger)
|
|
242
244
|
|
|
243
245
|
|
|
246
|
+
def _superseded_carried_ids(
|
|
247
|
+
ledger: Mapping[str, Any], supersedes: Sequence[str],
|
|
248
|
+
) -> list[str]:
|
|
249
|
+
carried = ledger.get("carriedDecisions")
|
|
250
|
+
carried_ids = {
|
|
251
|
+
entry["decision"].get("id")
|
|
252
|
+
for entry in (carried if isinstance(carried, list) else [])
|
|
253
|
+
if isinstance(entry, Mapping) and isinstance(entry.get("decision"), Mapping)
|
|
254
|
+
}
|
|
255
|
+
ids = list(dict.fromkeys(_required(value, "supersedes") for value in supersedes))
|
|
256
|
+
unknown = [value for value in ids if value not in carried_ids]
|
|
257
|
+
if unknown:
|
|
258
|
+
raise ApprovalDecisionError(
|
|
259
|
+
f"supersedes must name carried decisions of this ledger: {unknown}"
|
|
260
|
+
)
|
|
261
|
+
return ids
|
|
262
|
+
|
|
263
|
+
|
|
244
264
|
def resolve_decision(
|
|
245
265
|
ledger_path: Path, clarification_id: str, *, disposition: str,
|
|
246
266
|
user_text: str, user_response_ref: str, check_refs: Sequence[str],
|
|
@@ -412,10 +432,20 @@ def _add_open_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
412
432
|
)
|
|
413
433
|
for flag in repeated:
|
|
414
434
|
parser.add_argument(f"--option-{flag}", action="append", default=[])
|
|
435
|
+
parser.add_argument(
|
|
436
|
+
"--supersedes",
|
|
437
|
+
action="append",
|
|
438
|
+
default=[],
|
|
439
|
+
help=(
|
|
440
|
+
"a carried decision this question replaces; once this row is "
|
|
441
|
+
"resolved, report assembly publishes that carried row as obsolete. "
|
|
442
|
+
"Repeat for several ids."
|
|
443
|
+
),
|
|
444
|
+
)
|
|
415
445
|
|
|
416
446
|
|
|
417
447
|
_CLI_EPILOG = r"""Usage:
|
|
418
|
-
okstra approval-decision open --ledger <path> [decision fields]
|
|
448
|
+
okstra approval-decision open --ledger <path> [decision fields] [--supersedes <C-NNN> ...]
|
|
419
449
|
okstra approval-decision resolve --ledger <path> --clarification-id <C-NNN> [resolution fields]
|
|
420
450
|
okstra approval-decision carry --ledger <path> --from-responses <clarification-response.md> --clarification-id <C-NNN> [--clarification-id <C-NNN> ...]
|
|
421
451
|
okstra approval-decision carry --ledger <path> --source-ledger <path> --source-run-ref <ref> --clarification-id <C-NNN>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Ancestor-based runtime asset lookup independent of run and phase state."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def walk_to_asset_root(
|
|
9
|
+
relative: Sequence[str],
|
|
10
|
+
*,
|
|
11
|
+
start: Path,
|
|
12
|
+
is_present: Callable[[Path], bool] = Path.is_file,
|
|
13
|
+
) -> Path | None:
|
|
14
|
+
"""Return the nearest ancestor carrying the asset without home overrides."""
|
|
15
|
+
here = Path(start).resolve()
|
|
16
|
+
for parent in [here, *here.parents]:
|
|
17
|
+
if is_present(parent.joinpath(*relative)):
|
|
18
|
+
return parent
|
|
19
|
+
return None
|
|
@@ -327,6 +327,14 @@ def resolve_model_selection(
|
|
|
327
327
|
raw_model: str,
|
|
328
328
|
) -> ResolvedModel:
|
|
329
329
|
"""Resolve one explicit or provider-default catalog model without binding."""
|
|
330
|
+
if raw_model and "/" in raw_model:
|
|
331
|
+
# `agent-prompt resolve-operation` prints slots as model refs.
|
|
332
|
+
selected = pool.resolve(raw_model)
|
|
333
|
+
if selected.provider_id != provider.strip().lower():
|
|
334
|
+
raise AssignmentResolutionError(
|
|
335
|
+
f"model {raw_model!r} does not belong to provider {provider!r}"
|
|
336
|
+
)
|
|
337
|
+
return selected
|
|
330
338
|
if raw_model:
|
|
331
339
|
return pool.resolve_alias(provider, raw_model)
|
|
332
340
|
selected = pool.default_candidate(provider, role)
|
|
@@ -239,6 +239,13 @@ _BLOCKING: tuple[tuple[str, str], ...] = (
|
|
|
239
239
|
"같은 이유 — 검증 범위가 열거값 밖이거나 단일 stage 검증이 전체 "
|
|
240
240
|
"release-handoff 를 요구한다.",
|
|
241
241
|
),
|
|
242
|
+
(
|
|
243
|
+
"for release but `runs/implementation-planning/consumers.jsonl` carries no",
|
|
244
|
+
"통과 판정이 stage 에 `verified` 행을 남기지 않았다 — handoff.py 의 "
|
|
245
|
+
"compute_eligibility 가 그 행으로만 PR 대상을 고르므로 통과한 stage 가 "
|
|
246
|
+
"release-handoff 에서 영영 제외된다. report-finalize 의 record-verified "
|
|
247
|
+
"단계가 이 검증 직전에 행을 쓰므로 정상 run 은 항상 만족한다.",
|
|
248
|
+
),
|
|
242
249
|
)
|
|
243
250
|
|
|
244
251
|
# 2026-08-28 에 제거한 조각과 근거 (감사 보고서 §2):
|