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
|
@@ -29,6 +29,7 @@ from .domain.worker_exec import (
|
|
|
29
29
|
WorkerExecRequest,
|
|
30
30
|
)
|
|
31
31
|
from .domain.worker_presentation import JsonEvents, Presentation
|
|
32
|
+
from .process_group import MemoryWatch, kill_group, memory_cap_bytes
|
|
32
33
|
from .session_transcript import SessionTranscript
|
|
33
34
|
from .json_boundary import JsonBoundaryError, write_owned_object_atomic
|
|
34
35
|
|
|
@@ -42,6 +43,8 @@ _TERM_GRACE_SECONDS = 5
|
|
|
42
43
|
# this only has to cover a drain, never a producer.
|
|
43
44
|
_DRAIN_AFTER_EXIT_SECONDS = 2
|
|
44
45
|
_TIMEOUT_EXIT_CODE = 124
|
|
46
|
+
# sysexits 의 EX_OSERR. 워커 그룹이 메모리 상한을 넘어 러너가 죽인 run.
|
|
47
|
+
MEMORY_CAP_EXIT_CODE = 71
|
|
45
48
|
_READ_SIZE = 8192
|
|
46
49
|
_WRITE_SIZE = 8192
|
|
47
50
|
_NO_STATUS_EXTRA: Mapping[str, Any] = {}
|
|
@@ -66,6 +69,7 @@ def run_worker(
|
|
|
66
69
|
_validate_status_extra(status_extra)
|
|
67
70
|
_validate_request_write_contract(request, status_extra)
|
|
68
71
|
command = strategy.build_command(request)
|
|
72
|
+
memory_watch = MemoryWatch(memory_cap_bytes(request.project_root))
|
|
69
73
|
started_monotonic = time.monotonic()
|
|
70
74
|
status = _started_status(status_extra, log_path)
|
|
71
75
|
_write_status(status_path, status)
|
|
@@ -73,11 +77,12 @@ def run_worker(
|
|
|
73
77
|
|
|
74
78
|
try:
|
|
75
79
|
with guard:
|
|
76
|
-
exit_code, timed_out, idle_seconds, raw_model, usage = _launch(
|
|
80
|
+
exit_code, timed_out, idle_seconds, raw_model, usage, reaped = _launch(
|
|
77
81
|
command,
|
|
78
82
|
log_path,
|
|
79
83
|
presentation=presentation,
|
|
80
84
|
idle_timeout_seconds=request.idle_timeout_seconds,
|
|
85
|
+
memory_watch=memory_watch,
|
|
81
86
|
on_spawn=guard.watch,
|
|
82
87
|
)
|
|
83
88
|
at_exit = getattr(command.presentation, "served_model_at_exit", None)
|
|
@@ -107,6 +112,11 @@ def run_worker(
|
|
|
107
112
|
if failure:
|
|
108
113
|
status["failure"] = failure
|
|
109
114
|
exit_code = SERVED_MODEL_MISMATCH_EXIT_CODE
|
|
115
|
+
if reaped:
|
|
116
|
+
status["leftoverProcessesKilled"] = True
|
|
117
|
+
if memory_watch.exceeded_bytes:
|
|
118
|
+
exit_code = MEMORY_CAP_EXIT_CODE
|
|
119
|
+
status.update(failure=memory_watch.failure(), terminated_by="memory-cap")
|
|
110
120
|
status["exit_code"] = exit_code
|
|
111
121
|
if timed_out:
|
|
112
122
|
status.update(
|
|
@@ -176,8 +186,9 @@ def _launch(
|
|
|
176
186
|
*,
|
|
177
187
|
presentation: str,
|
|
178
188
|
idle_timeout_seconds: int,
|
|
189
|
+
memory_watch: MemoryWatch,
|
|
179
190
|
on_spawn: Callable[[subprocess.Popen[bytes]], None],
|
|
180
|
-
) -> tuple[int, bool, int, str | None, Mapping[str, Any] | None]:
|
|
191
|
+
) -> tuple[int, bool, int, str | None, Mapping[str, Any] | None, bool]:
|
|
181
192
|
live = presentation == LIVE
|
|
182
193
|
transcript = SessionTranscript(log_path, live=live)
|
|
183
194
|
observation = _ServedModelObservation()
|
|
@@ -211,14 +222,18 @@ def _launch(
|
|
|
211
222
|
strategy=strategy,
|
|
212
223
|
presentation=presentation,
|
|
213
224
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
225
|
+
memory_watch=memory_watch,
|
|
214
226
|
stdin_text=command.stdin_text,
|
|
215
227
|
)
|
|
228
|
+
# 워커가 띄운 빌드 워커·서버는 워커가 끝나도 그룹에 남아 계속 자란다.
|
|
229
|
+
reaped = kill_group(process.pid)
|
|
216
230
|
return (
|
|
217
231
|
exit_code,
|
|
218
232
|
timed_out,
|
|
219
233
|
idle_seconds,
|
|
220
234
|
observation.raw_model,
|
|
221
235
|
usage_observation.usage,
|
|
236
|
+
reaped,
|
|
222
237
|
)
|
|
223
238
|
finally:
|
|
224
239
|
transcript.close()
|
|
@@ -386,6 +401,7 @@ def _pump(
|
|
|
386
401
|
strategy: Presentation,
|
|
387
402
|
presentation: str,
|
|
388
403
|
idle_timeout_seconds: int,
|
|
404
|
+
memory_watch: MemoryWatch,
|
|
389
405
|
stdin_text: str | None = None,
|
|
390
406
|
) -> tuple[int, bool, int]:
|
|
391
407
|
selector = selectors.DefaultSelector()
|
|
@@ -418,6 +434,7 @@ def _pump(
|
|
|
418
434
|
):
|
|
419
435
|
timed_out = True
|
|
420
436
|
_terminate(process)
|
|
437
|
+
memory_watch.check(process.pid, time.monotonic())
|
|
421
438
|
drain_deadline = _drain_deadline(process, drain_deadline)
|
|
422
439
|
if drain_deadline is not None and time.monotonic() >= drain_deadline:
|
|
423
440
|
_stop_reading(selector)
|
|
@@ -457,7 +474,8 @@ def _drain_deadline(
|
|
|
457
474
|
holding the write end of the pipe open for as long as it lives. The shell
|
|
458
475
|
wrappers returned as soon as `wait <pid>` did, so blocking on a grandchild is
|
|
459
476
|
a regression rather than a policy, and the exit code the caller gets stays
|
|
460
|
-
the worker's own.
|
|
477
|
+
the worker's own. Whatever is still in the group afterwards is killed
|
|
478
|
+
(`_launch`).
|
|
461
479
|
|
|
462
480
|
The deadline is set once and never pushed back: output arriving after the
|
|
463
481
|
worker exited is the grandchild's, and letting it extend the wait would
|
|
@@ -7,14 +7,13 @@ task/run status, render-only flag) 만 받아서 WORKFLOW_* 필드 dict 를 돌
|
|
|
7
7
|
`okstra_ctl.next_phase` 하나가 소유하고, 값은 리포트가 저작한 라우팅에서
|
|
8
8
|
투영된다.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
PHASE_FORBIDDEN_ACTIONS 는 `prompts/profiles/forbidden-actions.json` SSOT 에서 로드한다.
|
|
10
|
+
단계별 출력·금지 문구는 선택한 단계의 boundary.json 에서 읽는다.
|
|
12
11
|
"""
|
|
13
12
|
from __future__ import annotations
|
|
14
13
|
|
|
15
|
-
import json
|
|
16
14
|
from pathlib import Path
|
|
17
15
|
from .json_boundary import load_owned_object
|
|
16
|
+
from .phases.catalog import phase_boundary, phase_record, task_types
|
|
18
17
|
|
|
19
18
|
PHASE_SEQUENCE = [
|
|
20
19
|
"requirements-discovery",
|
|
@@ -35,125 +34,6 @@ ERROR_ANALYSIS_ROUTING_DIRECTIONS = {
|
|
|
35
34
|
"implementation-option-selection": "begin-option-selection",
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
# Phase 별 allowed outputs. bash heredoc 원문 그대로 옮긴 값 — 들여쓰기와 백틱은
|
|
39
|
-
# prompt template 에 그대로 박혀 lead 가 읽는다. forbidden actions 는 이 dict 가
|
|
40
|
-
# 아니라 prompts/profiles/forbidden-actions.json (load_phase_forbidden) 이 SSOT.
|
|
41
|
-
PHASE_RULES: dict[str, dict[str, str]] = {
|
|
42
|
-
"technical-verification": {
|
|
43
|
-
"allowed": (
|
|
44
|
-
" - falsifiable experiment plans for the frozen unresolved facts\n"
|
|
45
|
-
" - dependency installation, source experiments, tests and builds only in this run's experiment copies\n"
|
|
46
|
-
" - command logs, observed signals and per-fact supported/refuted/inconclusive/not-run results\n"
|
|
47
|
-
" - return to implementation-option-selection with evidence, without adoption or plan approval"
|
|
48
|
-
),
|
|
49
|
-
},
|
|
50
|
-
"requirements-discovery": {
|
|
51
|
-
"allowed": (
|
|
52
|
-
" - work-category classification (bugfix / feature / refactor / ops / improvement)\n"
|
|
53
|
-
" - routing decision as the `routing` object `{nextTaskType, readyWhen, rationale}`: `nextTaskType` is `error-analysis` or `implementation-option-selection` and nothing else — this phase never hands off directly to planning or implementation. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `nextTaskType`\n"
|
|
54
|
-
" - missing-input list and clarification requests\n"
|
|
55
|
-
" - approval / confirmation checkpoints recorded for the next phase\n"
|
|
56
|
-
" - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)"
|
|
57
|
-
),
|
|
58
|
-
},
|
|
59
|
-
"improvement-discovery": {
|
|
60
|
-
"allowed": (
|
|
61
|
-
" - improvement candidate discovery within the lens whitelist defined in `scripts/okstra_ctl/improvement_lenses.py`\n"
|
|
62
|
-
" - top-N ranking (default 8, brief `candidate-cap` overrides within 1..12)\n"
|
|
63
|
-
" - per-candidate columns Lens / Scope / Severity / Effort / Consensus / Source workers / Recommended next-phase / Expected behavior after / Evidence (path:line)\n"
|
|
64
|
-
" - Phase 1.5 reflect-back grilling log at `runs/improvement-discovery/<seq>/state/phase-1.5-grilling.md`\n"
|
|
65
|
-
" - Phase 5.5 consensus classification (full / partial / contested / worker-unique / unverified)"
|
|
66
|
-
),
|
|
67
|
-
},
|
|
68
|
-
"project-analysis": {
|
|
69
|
-
"allowed": (
|
|
70
|
-
" - components, dependency directions, entry points, repositories, and external integrations\n"
|
|
71
|
-
" - shallow feature index and scan scope\n"
|
|
72
|
-
),
|
|
73
|
-
},
|
|
74
|
-
"feature-analysis": {
|
|
75
|
-
"allowed": (
|
|
76
|
-
" - confirmed target and normal, alternate, and failure flows\n"
|
|
77
|
-
" - rules, state transitions, external calls, and test coverage scope\n"
|
|
78
|
-
),
|
|
79
|
-
},
|
|
80
|
-
"change-impact-analysis": {
|
|
81
|
-
"allowed": (
|
|
82
|
-
" - preserved behavior, impacted items, and dependency propagation\n"
|
|
83
|
-
" - test and operational impact\n"
|
|
84
|
-
" - constraints and open decisions passed to implementation-planning\n"
|
|
85
|
-
),
|
|
86
|
-
},
|
|
87
|
-
"error-analysis": {
|
|
88
|
-
"allowed": (
|
|
89
|
-
" - symptom and trigger evidence\n"
|
|
90
|
-
" - root-cause hypotheses with supporting citations\n"
|
|
91
|
-
" - reproduction gaps and missing observability\n"
|
|
92
|
-
" - validation paths to confirm or refute hypotheses\n"
|
|
93
|
-
" - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)"
|
|
94
|
-
),
|
|
95
|
-
},
|
|
96
|
-
"implementation-option-selection": {
|
|
97
|
-
"allowed": (
|
|
98
|
-
" - candidate-comparison evidence for up to three options per worker\n"
|
|
99
|
-
" - preselected-validation of exactly ONE candidate: every analyser returns its own feasibility verdict on that one direction, `candidateAudit` stays empty, and no analyser generates an alternative. It is not a re-evaluation of the merged set — `okstra_ctl.implementation_options._validate_mode` rejects a preselected-validation report carrying more than one option or a non-empty audit\n"
|
|
100
|
-
" - a ranked display of at most three options with requirement mappings\n"
|
|
101
|
-
" - an audit record for every rejected candidate\n"
|
|
102
|
-
" - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)"
|
|
103
|
-
),
|
|
104
|
-
},
|
|
105
|
-
"implementation-planning": {
|
|
106
|
-
"allowed": (
|
|
107
|
-
" - pre-planning context exploration notes (files/interfaces inspected, recent commits scanned, ambiguities flagged)\n"
|
|
108
|
-
" - selected-direction planning: realize the one validated selected direction without reopening candidate comparison, with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n"
|
|
109
|
-
" - legacy candidate-comparison planning only: at least two implementation option candidates, each with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n"
|
|
110
|
-
" - legacy candidate-comparison planning only: a trade-off matrix across options (complexity, risk, reversibility, test cost, rollout cost) and recommended option with rationale tied to isolation / single-responsibility / YAGNI principles\n"
|
|
111
|
-
" - bite-sized stepwise execution order for the selected direction or legacy recommended option (each step ~2-5 min, exact file paths and commands, TDD ordering when applicable, no placeholders)\n"
|
|
112
|
-
" - dependency / migration risk assessment, validation checklist (pre / mid / post with exact commands), rollback strategy with revert path and trigger signal\n"
|
|
113
|
-
" - every unresolved ambiguity registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under `5.5.x` — the unified table is the single home)\n"
|
|
114
|
-
" - report record `frontmatter.approved: false` awaiting `--approve` or the in-session wizard\n"
|
|
115
|
-
" - self-review confirmation (spec coverage, placeholder scan, internal consistency, ambiguity, scope)\n"
|
|
116
|
-
" - one endStateCoverage row per brief end-state id, each mapped to the R-NNN row that carries it"
|
|
117
|
-
),
|
|
118
|
-
},
|
|
119
|
-
"implementation": {
|
|
120
|
-
"allowed": (
|
|
121
|
-
" - approved-plan reference and quoted user-approval evidence\n"
|
|
122
|
-
" - commit list with SHA, message, and the plan step each commit satisfies\n"
|
|
123
|
-
" - `git diff --stat <base>..HEAD` summary plus per-file one-line change summary\n"
|
|
124
|
-
" - `Out-of-plan edits` block listing every file touched outside the approved plan with rationale (empty block preferred)\n"
|
|
125
|
-
' - validation evidence: actual stdout/stderr and exit code for every pre / mid / post command from the plan (no paraphrased "tests pass")\n'
|
|
126
|
-
" - TDD evidence for TDD-applicable steps: failing-test output before implementation commit and passing-test output after, with framing SHAs\n"
|
|
127
|
-
" - per-verifier sections for every verifier in the resolved roster, with an independent verdict (PASS / CONCERNS / FAIL) and cited diff snippets; dissent is preserved by the Okstra lead\n"
|
|
128
|
-
" - rollback verification (advisory, never blocks — record the revert path for a human; `result` is ok / not-applicable / advisory — human-run)\n"
|
|
129
|
-
" - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `final-verification`, `error-analysis`, `implementation-planning`, `implementation`, and `rationale` is why that one and nothing else. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`"
|
|
130
|
-
),
|
|
131
|
-
},
|
|
132
|
-
"final-verification": {
|
|
133
|
-
"allowed": (
|
|
134
|
-
" - acceptance verdict with requirement coverage assessment\n"
|
|
135
|
-
" - residual risk and regression notes\n"
|
|
136
|
-
" - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, `done`, and `rationale` ties that choice to the verdict and the blocker list. Both `release-handoff` forms require a release-ready verdict — `accepted`, or `conditional-accept` with every condition declaring `blocksReleaseHandoff: false` (`okstra_ctl.release_gate.release_handoff_allowed`); either verification scope may route there, because release-handoff opens one PR per stage. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`"
|
|
137
|
-
),
|
|
138
|
-
},
|
|
139
|
-
"release-handoff": {
|
|
140
|
-
"allowed": (
|
|
141
|
-
" - entering this phase only when the cited final-verification report's verdict is release-ready — `Verdict Token` exactly `accepted`, or exactly `conditional-accept` with every Conditional Acceptance Condition row declaring `blocksReleaseHandoff: false` (`okstra_ctl.release_gate.release_handoff_allowed`)\n"
|
|
142
|
-
" - asking the user (via `AskUserQuestion` / interactive prompt) which delivery action to take: `local checkout`, `push + PR`, or `skip` (end the run)\n"
|
|
143
|
-
" - asking the user to pick a PR base branch from `staging` | `preprod` | `prod` | `main` | `dev` | a user-supplied branch name\n"
|
|
144
|
-
" - drafting PR title and PR body **inline as the Okstra lead** (no drafter worker and no delegated dispatch); the lead reviews its own draft with the user before any mutating git / gh command runs\n"
|
|
145
|
-
" - read-only git inspection: `git status`, `git diff`, `git log`, `git rev-parse`\n"
|
|
146
|
-
" - pushing the current feature branch to its origin remote via `git push -u origin <current-branch>` (the feature branch only — NEVER the base branch)\n"
|
|
147
|
-
" - creating a pull request via `gh pr create --base <chosen-base> --head <current-branch>`; if a PR with the same head already exists, surface its URL and skip creation\n"
|
|
148
|
-
" - the lead writes the final report directly (no `Report writer worker` dispatch); the report still conforms to the standard final-report template"
|
|
149
|
-
),
|
|
150
|
-
},
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
PHASE_RULES_UNKNOWN = {
|
|
154
|
-
"allowed": " - outputs defined by the active task type",
|
|
155
|
-
}
|
|
156
|
-
|
|
157
37
|
|
|
158
38
|
def render_forbidden(items: list[str]) -> str:
|
|
159
39
|
"""Render a forbidden-action list to the ` - item` bullet block injected
|
|
@@ -161,24 +41,30 @@ def render_forbidden(items: list[str]) -> str:
|
|
|
161
41
|
return "\n".join(f" - {item}" for item in items)
|
|
162
42
|
|
|
163
43
|
|
|
164
|
-
def
|
|
165
|
-
"""
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
`prompts/profiles/forbidden-actions.json`.
|
|
170
|
-
"""
|
|
171
|
-
path = Path(workspace_root) / "prompts" / "profiles" / "forbidden-actions.json"
|
|
172
|
-
data = load_owned_object(path, artifact="forbidden actions profile")
|
|
173
|
-
if "unknown" not in data:
|
|
174
|
-
# compute_workflow_state falls back to the "unknown" entry for any
|
|
175
|
-
# unmapped task_type; without it a missing/renamed key would surface as
|
|
176
|
-
# a KeyError deep in prompt assembly instead of here at the SSOT seam.
|
|
44
|
+
def load_phase_boundary(workspace_root: Path, task_type: str) -> dict[str, str]:
|
|
45
|
+
"""선택한 단계의 출력 경계를 읽고 구형 자산 루트의 금지 목록도 보존한다."""
|
|
46
|
+
fallback_path = Path(workspace_root) / "prompts" / "profiles" / "forbidden-actions.json"
|
|
47
|
+
fallback = load_owned_object(fallback_path, artifact="forbidden actions profile")
|
|
48
|
+
if "unknown" not in fallback:
|
|
177
49
|
raise ValueError(
|
|
178
|
-
f"forbidden-actions SSOT at {
|
|
50
|
+
f"forbidden-actions SSOT at {fallback_path} is missing the required "
|
|
179
51
|
"'unknown' fallback entry"
|
|
180
52
|
)
|
|
181
|
-
|
|
53
|
+
if task_type not in task_types():
|
|
54
|
+
return {
|
|
55
|
+
"allowed": " - outputs defined by the active task type",
|
|
56
|
+
"forbidden": render_forbidden(fallback.get(task_type, fallback["unknown"])),
|
|
57
|
+
}
|
|
58
|
+
path = phase_boundary(workspace_root, task_type)
|
|
59
|
+
if path is None:
|
|
60
|
+
# 패키지 없는 외부 프로필 루트는 기존 코드의 출력 정책과 자체 금지 목록을 쓴다.
|
|
61
|
+
path = Path(__file__).parent / "phases" / phase_record(task_type).package_name / "boundary.json"
|
|
62
|
+
data = load_owned_object(path, artifact="phase boundary")
|
|
63
|
+
items = fallback.get(task_type, fallback["unknown"])
|
|
64
|
+
else:
|
|
65
|
+
data = load_owned_object(path, artifact="phase boundary")
|
|
66
|
+
items = data["forbidden"]
|
|
67
|
+
return {"allowed": data["allowed"], "forbidden": render_forbidden(items)}
|
|
182
68
|
|
|
183
69
|
|
|
184
70
|
def phase_position(task_type: str) -> str:
|
|
@@ -203,7 +89,7 @@ def compute_workflow_state(
|
|
|
203
89
|
current_run_status: str,
|
|
204
90
|
current_task_status: str,
|
|
205
91
|
render_only: bool,
|
|
206
|
-
|
|
92
|
+
boundary: dict[str, str],
|
|
207
93
|
work_category: str = "",
|
|
208
94
|
) -> dict:
|
|
209
95
|
"""WORKFLOW_* + PHASE_* 값을 dict 로 돌려준다."""
|
|
@@ -234,7 +120,6 @@ def compute_workflow_state(
|
|
|
234
120
|
else:
|
|
235
121
|
checkpoint_label = "run-prepared"
|
|
236
122
|
|
|
237
|
-
rules = PHASE_RULES.get(task_type, PHASE_RULES_UNKNOWN)
|
|
238
123
|
last_completed = task_type if current_run_status == "completed" else ""
|
|
239
124
|
|
|
240
125
|
resolved_work_category = (work_category or "").strip() or "unknown"
|
|
@@ -256,8 +141,6 @@ def compute_workflow_state(
|
|
|
256
141
|
"WORKFLOW_LAST_COMPLETED_PHASE": last_completed,
|
|
257
142
|
"WORKFLOW_AWAITING_APPROVAL": "false",
|
|
258
143
|
"WORKFLOW_LAST_SAFE_CHECKPOINT_LABEL": checkpoint_label,
|
|
259
|
-
"PHASE_ALLOWED_OUTPUTS":
|
|
260
|
-
"PHASE_FORBIDDEN_ACTIONS":
|
|
261
|
-
task_type, forbidden_by_phase["unknown"]
|
|
262
|
-
),
|
|
144
|
+
"PHASE_ALLOWED_OUTPUTS": boundary["allowed"],
|
|
145
|
+
"PHASE_FORBIDDEN_ACTIONS": boundary["forbidden"],
|
|
263
146
|
}
|
|
@@ -26,6 +26,7 @@ from .domain.write_policy import ( # noqa: F401 — 재노출(값 코어는 도
|
|
|
26
26
|
write_policy_digest,
|
|
27
27
|
write_policy_from_payload,
|
|
28
28
|
)
|
|
29
|
+
from .conformance import conformance_result_file
|
|
29
30
|
from .final_report_paths import final_report_data_path
|
|
30
31
|
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
31
32
|
from .path_hints import hydrate_active_run_context
|
|
@@ -65,11 +66,15 @@ def build_write_policy(
|
|
|
65
66
|
),
|
|
66
67
|
"externalRoots": [str(path) for path in external_roots],
|
|
67
68
|
}
|
|
69
|
+
artifact_policy: dict[str, Any] = {
|
|
70
|
+
"allowedRoot": str(project_root),
|
|
71
|
+
"allowedPaths": list(artifact_paths),
|
|
72
|
+
}
|
|
73
|
+
preserved_paths = _relative_paths(invocation.get("preservedPaths", ()))
|
|
74
|
+
if preserved_paths:
|
|
75
|
+
artifact_policy["preservedPaths"] = list(preserved_paths)
|
|
68
76
|
policy = WritePolicy(
|
|
69
|
-
artifact_policy=
|
|
70
|
-
"allowedRoot": str(project_root),
|
|
71
|
-
"allowedPaths": list(artifact_paths),
|
|
72
|
-
},
|
|
77
|
+
artifact_policy=artifact_policy,
|
|
73
78
|
source_policy=source_policy,
|
|
74
79
|
git_policy=git_policy,
|
|
75
80
|
auxiliary_policy=auxiliary_policy,
|
|
@@ -94,9 +99,12 @@ def _role_qa_artifact_paths(role: str, task_root: Path | None) -> tuple[Path, ..
|
|
|
94
99
|
conformance script, its `tsconfig.json` and any real-IO qa spec under
|
|
95
100
|
`qa/scripts/`, plus the manifest entry naming them
|
|
96
101
|
(`_implementation-executor.md` §"Stage conformance script", §"Real-IO test
|
|
97
|
-
isolation")
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
isolation"), plus `qa/output/` for whatever those scripts write when the
|
|
103
|
+
executor runs them against its own work — a baseline capture, build logs
|
|
104
|
+
(§"Verifier gates are not yours to run" lets it run them). Everything else
|
|
105
|
+
under `qa/` — the self-mock sidecar and its diff, the conformance run's
|
|
106
|
+
`result-*.json` — is written while the verifier runs its own gates.
|
|
107
|
+
Keeping the executor's grant to those three entries is
|
|
100
108
|
what leaves the audit able to catch an executor that runs the verifier's
|
|
101
109
|
self-mock gate (`_implementation-executor.md` §"Verifier gates are not
|
|
102
110
|
yours to run"); widening it to `qa/` would silence that.
|
|
@@ -114,6 +122,7 @@ def _role_qa_artifact_paths(role: str, task_root: Path | None) -> tuple[Path, ..
|
|
|
114
122
|
if role == "implementer":
|
|
115
123
|
return (
|
|
116
124
|
task_qa_dir(task_root) / "scripts",
|
|
125
|
+
task_qa_dir(task_root) / "output",
|
|
117
126
|
task_conformance_manifest_file(task_root),
|
|
118
127
|
)
|
|
119
128
|
if role == "verifier":
|
|
@@ -121,6 +130,42 @@ def _role_qa_artifact_paths(role: str, task_root: Path | None) -> tuple[Path, ..
|
|
|
121
130
|
return ()
|
|
122
131
|
|
|
123
132
|
|
|
133
|
+
def _inherited_qa_evidence(
|
|
134
|
+
role: str, task_type: str, task_root: Path | None,
|
|
135
|
+
) -> tuple[Path, ...]:
|
|
136
|
+
"""Existing qa files a final-verification verifier must leave byte-identical.
|
|
137
|
+
|
|
138
|
+
Its grant is the whole `qa/` tree, so re-running a plan step that captures a
|
|
139
|
+
baseline overwrote the implementation's RED evidence and still passed the
|
|
140
|
+
audit (observed 2026-09-26, dev-11054 `qa/baseline/base.json`). New files and
|
|
141
|
+
the conformance results it re-runs (`qa/result-<stageKey>.json`, the only qa
|
|
142
|
+
write `final_verification/boundary.json` names) stay writable.
|
|
143
|
+
"""
|
|
144
|
+
if role != "verifier" or task_type != "final-verification" or task_root is None:
|
|
145
|
+
return ()
|
|
146
|
+
qa_dir = task_qa_dir(task_root)
|
|
147
|
+
if not qa_dir.is_dir():
|
|
148
|
+
return ()
|
|
149
|
+
rerun = {
|
|
150
|
+
conformance_result_file(qa_dir, key)
|
|
151
|
+
for key in _conformance_stage_keys(task_root)
|
|
152
|
+
}
|
|
153
|
+
return tuple(sorted(
|
|
154
|
+
path for path in qa_dir.rglob("*")
|
|
155
|
+
if path.is_file() and not path.is_symlink() and path not in rerun
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _conformance_stage_keys(task_root: Path) -> tuple[str, ...]:
|
|
160
|
+
path = task_conformance_manifest_file(task_root)
|
|
161
|
+
if not path.is_file():
|
|
162
|
+
return ()
|
|
163
|
+
entries = _read_json(path, "conformance manifest").get("entries")
|
|
164
|
+
return tuple(
|
|
165
|
+
entry["stageKey"] for entry in entries or ()
|
|
166
|
+
if isinstance(entry, dict) and isinstance(entry.get("stageKey"), str)
|
|
167
|
+
)
|
|
168
|
+
|
|
124
169
|
|
|
125
170
|
def _technical_experiment_paths(
|
|
126
171
|
artifact_paths: Sequence[Path], task_root: Path | None,
|
|
@@ -145,6 +190,7 @@ def _technical_experiment_paths(
|
|
|
145
190
|
def build_invocation_write_contract(
|
|
146
191
|
*,
|
|
147
192
|
role: str,
|
|
193
|
+
task_type: str,
|
|
148
194
|
project_root: Path,
|
|
149
195
|
worktree: Path | None,
|
|
150
196
|
artifact_paths: Sequence[Path],
|
|
@@ -180,6 +226,10 @@ def build_invocation_write_contract(
|
|
|
180
226
|
"plannedPaths": list(source_paths),
|
|
181
227
|
"plannedPathsDeclared": planned_paths_declared,
|
|
182
228
|
"protectedPaths": [".okstra", ".git"],
|
|
229
|
+
"preservedPaths": [
|
|
230
|
+
_relative_to_root(path, root, "preserved")
|
|
231
|
+
for path in _inherited_qa_evidence(role, task_type, task_root)
|
|
232
|
+
],
|
|
183
233
|
"generatedPaths": list(generated_paths),
|
|
184
234
|
"scratchRoots": [str(path) for path in scratch_roots],
|
|
185
235
|
"auxiliaryRoots": [str(path) for path in auxiliary_roots],
|
|
@@ -87,6 +87,20 @@ def okstra_root(project_root: Path) -> Path:
|
|
|
87
87
|
return Path(project_root) / OKSTRA_RELATIVE
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def ensure_okstra_gitignore(project_root: Path) -> Path:
|
|
91
|
+
"""`.okstra/.gitignore` 에 `*` 를 둔다. 이미 있으면 건드리지 않는다.
|
|
92
|
+
|
|
93
|
+
전역 gitignore 로만 `.okstra` 를 빼면 그것을 읽지 않는 도구가 산출물을 훑는다.
|
|
94
|
+
Tailwind v4(4.3.2) 스캐너는 전역 설정은 무시하고 중첩 `.gitignore` 는 따른다
|
|
95
|
+
(2026-09-27 실측) — 실험 사이트 복사본을 읽어 빌드가 수십 GB 로 커졌다.
|
|
96
|
+
"""
|
|
97
|
+
path = okstra_root(project_root) / ".gitignore"
|
|
98
|
+
if not path.exists():
|
|
99
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
path.write_text("*\n", encoding="utf-8")
|
|
101
|
+
return path
|
|
102
|
+
|
|
103
|
+
|
|
90
104
|
def project_json_path(project_root: Path) -> Path:
|
|
91
105
|
"""`<project_root>/.okstra/project.json` 절대 path."""
|
|
92
106
|
return Path(project_root) / PROJECT_JSON_RELATIVE
|
|
@@ -11,6 +11,7 @@ from typing import Optional
|
|
|
11
11
|
from .dirs import (
|
|
12
12
|
OKSTRA_DIR_NAME,
|
|
13
13
|
PROJECT_JSON_RELATIVE,
|
|
14
|
+
ensure_okstra_gitignore,
|
|
14
15
|
project_json_path,
|
|
15
16
|
)
|
|
16
17
|
|
|
@@ -154,7 +155,7 @@ def upsert_project_json(project_root: Path, project_id: str, *,
|
|
|
154
155
|
if not project_id:
|
|
155
156
|
raise ResolverError("project_id is required for upsert_project_json")
|
|
156
157
|
target = project_json_path(project_root)
|
|
157
|
-
|
|
158
|
+
ensure_okstra_gitignore(project_root)
|
|
158
159
|
when = now or _now_iso()
|
|
159
160
|
abs_root = str(Path(project_root).resolve())
|
|
160
161
|
if target.is_file():
|
|
@@ -137,7 +137,8 @@
|
|
|
137
137
|
"required": ["allowedRoot", "allowedPaths"],
|
|
138
138
|
"properties": {
|
|
139
139
|
"allowedRoot": {"type": "string", "pattern": "^/"},
|
|
140
|
-
"allowedPaths": {"type": "array", "items": {"type": "string", "pattern": "\\S"}, "minItems": 1, "uniqueItems": true}
|
|
140
|
+
"allowedPaths": {"type": "array", "items": {"type": "string", "pattern": "\\S"}, "minItems": 1, "uniqueItems": true},
|
|
141
|
+
"preservedPaths": {"type": "array", "items": {"type": "string", "pattern": "\\S"}, "uniqueItems": true}
|
|
141
142
|
},
|
|
142
143
|
"additionalProperties": false
|
|
143
144
|
},
|
|
@@ -345,7 +345,7 @@ If the user picked `Codebase scan`, gather the codebase-scan-specific inputs:
|
|
|
345
345
|
|
|
346
346
|
1. `AskUserQuestion` (free text):
|
|
347
347
|
`"Scan scope — comma-separated paths inside the project (e.g. src/foo/, src/bar/baz.py)"` → `scan_scope` (CSV).
|
|
348
|
-
2. `AskUserQuestion` (multi-select, options = the lens values from `~/.okstra/lib/python/okstra_ctl/
|
|
348
|
+
2. `AskUserQuestion` (multi-select, options = the lens values from `~/.okstra/lib/python/okstra_ctl/phases/improvement_discovery/lenses.py`'s `LENSES` — code source: `scripts/okstra_ctl/phases/improvement_discovery/lenses.py`):
|
|
349
349
|
`"Priority lenses (pick 1–4)"` → `priority_lenses` (1..4 values from the enum).
|
|
350
350
|
Current `LENSES` values (this list mirrors the module — if they diverge, the module wins; Step 6.6 `validate-brief` enforces the subset): `performance`, `security`, `readability`, `architecture`, `test-coverage`, `dx`, `observability`, `accessibility`.
|
|
351
351
|
3. `AskUserQuestion` (free text):
|
|
@@ -362,7 +362,7 @@ If the user picked `Codebase scan`, gather the codebase-scan-specific inputs:
|
|
|
362
362
|
Validation (before Step 4 sharpening):
|
|
363
363
|
|
|
364
364
|
- Confirm that each path in `scan_scope` actually exists inside `<PROJECT_ROOT>` via `ls` — if not, correct it with a single question.
|
|
365
|
-
- Confirm that `priority_lenses` is a subset (1–4) of `LENSES` in `~/.okstra/lib/python/okstra_ctl/
|
|
365
|
+
- Confirm that `priority_lenses` is a subset (1–4) of `LENSES` in `~/.okstra/lib/python/okstra_ctl/phases/improvement_discovery/lenses.py` — on violation, show the enum + re-select. Final enforcement is done by Step 6.6 `validate-brief` (`check_codebase_scope`).
|
|
366
366
|
- Confirm that `candidate_cap` is an integer in 1–12.
|
|
367
367
|
|
|
368
368
|
Once validation passes, jump to Step 2 (task key).
|
|
@@ -632,7 +632,7 @@ These 5 rules apply only to `scope: codebase` briefs. The budget expands from th
|
|
|
632
632
|
|
|
633
633
|
1. **scan-scope path existence check.** Confirm each path via `ls` / `Read`. If missing, correct it with a single question (offer the nearest path candidate as `Recommended:`).
|
|
634
634
|
2. **Ambiguous path narrowing.** Narrow abstract paths like `"backend"`, `"payment module"` down to one or more concrete directories. Guess codebase-first, then confirm with a single question.
|
|
635
|
-
3. **priority-lenses whitelist check.** Confirm it is a subset of `LENSES` in `~/.okstra/lib/python/okstra_ctl/
|
|
635
|
+
3. **priority-lenses whitelist check.** Confirm it is a subset of `LENSES` in `~/.okstra/lib/python/okstra_ctl/phases/improvement_discovery/lenses.py`. If out-of-enum, offer the nearest in-enum value as `Recommended:`.
|
|
636
636
|
4. **out-of-scope consistency.** Confirm each path in `out-of-scope` is a subset of `scan-scope`. If a path is unrelated, remove it with a single question or absorb it into scan-scope.
|
|
637
637
|
5. **One line of lens-priority rationale.** If the brief body lacks a line on *why* each priority lens takes precedence in this scope, offer the result of a first codebase pass as `Recommended:` and ask one question.
|
|
638
638
|
|