okstra 0.205.1 → 0.206.1
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 +2 -2
- package/dist/commands/lifecycle/install.mjs +2 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture.md +16 -16
- package/docs/cli.md +4 -4
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +45 -21
- package/package.json +2 -3
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +3 -3
- package/runtime/prompts/lead/okstra-lead-contract.md +14 -55
- package/runtime/prompts/lead/phase-routing.md +82 -0
- package/runtime/prompts/lead/report-writer.md +2 -2
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- 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/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/contract_graph.py +75 -10
- package/runtime/python/okstra_ctl/dispatch_state.py +22 -0
- package/runtime/python/okstra_ctl/doctor.py +15 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +27 -1
- 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 +9 -485
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
- package/runtime/python/okstra_ctl/next_phase.py +2 -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 +22 -6
- package/runtime/python/okstra_ctl/phases/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/catalog.py +260 -0
- 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/{docs/task-process/error-analysis.md → runtime/python/okstra_ctl/phases/error_analysis/spec.md} +22 -7
- 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/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/entry.py +166 -0
- package/runtime/{prompts/profiles/final-verification.md → python/okstra_ctl/phases/final_verification/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/final_verification.py → phases/final_verification/report.py} +12 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/{docs/task-process/final-verification.md → runtime/python/okstra_ctl/phases/final_verification/spec.md} +42 -25
- package/runtime/python/okstra_ctl/phases/final_verification/target.py +296 -0
- package/runtime/python/okstra_ctl/phases/final_verification/validation.py +190 -0
- package/runtime/python/okstra_ctl/phases/final_verification/wizard.py +38 -0
- 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 +4 -4
- 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/{docs/task-process/implementation.md → runtime/python/okstra_ctl/phases/implementation/spec.md} +20 -8
- 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} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/{docs/task-process/implementation-option-selection.md → runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md} +20 -7
- 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/{docs/task-process/implementation-planning.md → runtime/python/okstra_ctl/phases/implementation_planning/spec.md} +30 -6
- 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/{docs/task-process/release-handoff.md → runtime/python/okstra_ctl/phases/release_handoff/spec.md} +22 -9
- 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/{docs/task-process/requirements-discovery.md → runtime/python/okstra_ctl/phases/requirements_discovery/spec.md} +25 -6
- 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} +1 -1
- 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/profile_show.py +9 -3
- package/runtime/python/okstra_ctl/render.py +9 -2
- package/runtime/python/okstra_ctl/render_final_report.py +3 -2
- package/runtime/python/okstra_ctl/report_assembly.py +14 -93
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_html/router.py +9 -33
- 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_template_loader.py +35 -0
- package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +18 -2
- package/runtime/python/okstra_ctl/run.py +113 -503
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/stage_targets.py +9 -286
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- package/runtime/python/okstra_ctl/user_response.py +199 -4
- package/runtime/python/okstra_ctl/verification_target.py +1 -1
- 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/state.py +6 -2
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +27 -278
- 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_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +23 -4
- 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 +287 -4091
- package/runtime/validators/validate_analysis_report.py +14 -126
- package/docs/task-process/README.md +0 -82
- package/docs/task-process/common-flow.md +0 -173
- 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/final-verification.json → python/okstra_ctl/phases/final_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.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
|
@@ -93,7 +93,7 @@ This section adds report-specific checks to [okstra-lead-contract](./okstra-lead
|
|
|
93
93
|
2. The ledger lives at `runs/<task-type>/state/report-writer-corrections-<task-type>-<seq>-a<N>.json` (schema `schemas/report-writer-corrections-v1.0.schema.json`) and holds one entry per defect: `replace` with the exact replacement value (add `current` when you want it checked), `remove` for an item or optional field, `rewrite` with a `rule` when the writer has to re-author prose. Paths use the validator's grammar (`implementationOptionSelection.rankedOptions[1].coverageSummary.coveragePercent`), so a report-assembly refusal can be copied into the ledger verbatim. Never write an indirect instruction such as `use the schema value`, `use the valid status`, or `fix the enum`: a `replacement` is the literal, and a `rule` names the required outcome. You do not copy allowed enum literals by hand — okstra attaches each `rewrite`'s schema constraint from the frozen schema.
|
|
94
94
|
3. Run `okstra agent-prompt check-corrections --project-root <root> --run-manifest <path> --corrections <ledger>` until it reports no defect. It applies the ledger to a scratch copy of the base narrative and validates the complete proposed narrative against the writer-owned value schema and the task's semantic validator, listing every defect at once. Validating only the edited field is insufficient because one replacement can select a different schema branch, which is why the check covers the whole narrative.
|
|
95
95
|
4. When the check reports `mechanical: true` and has corrections, run `okstra agent-prompt apply-corrections` with the same arguments: okstra writes the corrected narrative to `reportNarrativePath` and records a `lead-correction-applied` activity row naming the ledger and its correction ids. This includes validated `replace`, `remove`, `add`, `move`, and derived step counts. No writer dispatch, `record-dispatch`, or `link-result` follows; the roster row's result already exists.
|
|
96
|
-
5. Otherwise materialize the writer prompt with the same `--corrections <ledger>` under a new invocation id and prompt path (retire the first attempt's link with `reject-result` as
|
|
96
|
+
5. Otherwise materialize the writer prompt with the same `--corrections <ledger>` under a new invocation id and prompt path (retire the first attempt's link with `reject-result` as `plan-body-verification` (the absolute path in **Okstra Runtime Resources**) describes). okstra renders the correction-only field values, evidence, schema constraints, replacement-file contract, application command, and output paths. Put context in the ledger; the initial instruction body is not sent to the correction writer.
|
|
97
97
|
|
|
98
98
|
A report-writer materialization without `--corrections` whose narrative already exists and parses is refused before any prompt is written — free-form corrections cannot be checked before the writer runs, and four of six re-runs in the 2026-09-03 measurement were lead instructions that contradicted the authoring contract. Only a narrative whose structure does not parse (line grammar, an unknown top-level field) is re-authored, not corrected: that dispatch needs no ledger, and its body quotes the parser's message. Because re-authoring overwrites the live file in place, okstra copies the existing narrative to `worker-results/<narrative-name>.pre-<invocation-id>.md` at materialization and renders a `## Previous Attempt` section naming that copy (**Enforced:** `_preserve_reauthored_narrative` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`); the 2026-09-09 dev-10642 run lost a 579-line attempt to a failed in-place re-indent command with no copy to fall back on. A narrative that breaks the line grammar is not a produced artifact: the dispatcher settles that attempt as `required worker artifact is unusable: narrative does not parse: …` and retries it inside the same batch, so you see the parser's message at collection, not at Phase 7 assembly (**Enforced:** `okstra_ctl.dispatch_state.unusable_result_defect`, read by `missing_completion_paths` and the `team await` record path). The synthesis packet's Authoring Contract carries the line grammar itself (`report_narrative.NARRATIVE_GRAMMAR_INSTRUCTIONS`), so a writer that reads only the packet still sees it. Value defects — an id outside its pattern, a value outside its enum, a missing required field — leave the structure readable and are exactly what the ledger fixes; the a3 attempt of the 2026-09-03 run carried twenty `SC-` ids that assembly refused and was still a corrective base.
|
|
99
99
|
|
|
@@ -106,7 +106,7 @@ A report-writer materialization without `--corrections` whose narrative already
|
|
|
106
106
|
3. Run initial plan-body verification as round 1.
|
|
107
107
|
4. Apply at most one automatic planner self-fix to the narrative. Skip this step when `gating` is `false`.
|
|
108
108
|
5. Run targeted re-verification as round 2 when needed. Skip this step when `gating` is `false`.
|
|
109
|
-
6. Persist the completed `planBodyVerification` value in convergence state. Run `okstra plan-items next-dispatch`: after the single automatic self-fix, settle eligible judgements with `resolve-dissent`; ask the user immediately for decisions outside lead authority. Preserve dissent and do not restart the automatic loop. The exact procedure and enforced authority checks are in `
|
|
109
|
+
6. Persist the completed `planBodyVerification` value in convergence state. Run `okstra plan-items next-dispatch`: after the single automatic self-fix, settle eligible judgements with `resolve-dissent`; ask the user immediately for decisions outside lead authority. Preserve dissent and do not restart the automatic loop. The exact procedure and enforced authority checks are in `scripts/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md` step 8.
|
|
110
110
|
7. Complete the design-surface detector snapshot: `okstra design-snapshot --narrative <reportNarrativePath> --output <designPreparationPath>`, taking both paths from the run manifest. Nothing else writes that snapshot, and step 8 fails without it — `report_inputs._PLANNING_INPUT_FIELDS` lists `designPreparationPath` as a required planning input.
|
|
111
111
|
8. Run Phase 7 report assembly.
|
|
112
112
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- When verifying worker team composition and operational rules
|
|
6
6
|
- When applying model assignment rules
|
|
7
7
|
|
|
8
|
-
**Not applicable to `release-handoff`** — that profile is lead-only and intentionally has no `Required workers:` block (see `
|
|
8
|
+
**Not applicable to `release-handoff`** — that profile is lead-only and intentionally has no `Required workers:` block (see `scripts/okstra_ctl/phases/release_handoff/profile.md`). The worker-dispatch contract in this document does not engage during `release-handoff` runs.
|
|
9
9
|
|
|
10
10
|
## Team Structure
|
|
11
11
|
|
|
@@ -16,7 +16,7 @@ prompt through `prepare_agent_invocation()` before `worker-dispatch`.
|
|
|
16
16
|
Load the applicable coding conventions for every language the diff will touch, then state in ONE line which conventions apply (e.g. `Applying TS + hexagonal overlay; domain at src/domains/*/domain/`). Lint/test green is necessary but NOT sufficient — self-mocked tests, interaction-only assertions, and untruthful names all pass a green pipeline; this gate is what keeps them out of the diff.
|
|
17
17
|
|
|
18
18
|
- **Resource selection — read the routed pack, never inline it here.** Use this worker prompt's `**Coding preflight pack:**` anchor header as the absolute path to the installed routed pack. Detect each touched file's language and framework from its extension or project manifest (`package.json`, `Cargo.toml`, `pyproject.toml`, `pom.xml`, `build.gradle*`, `prisma/schema.prisma`), then read that pack's resources via the Read tool by absolute path. Always read `overview.md` (the router) + `clean-code.md`, then select per the router's three ordered stages — Stage 1 language → `languages/<lang>.md`, Stage 2 framework → `frameworks/<fw>.md` (e.g. `frameworks/node-server.md` for server-side Node), Stage 3 architecture → `architectures/<arch>.md` (e.g. `architectures/hexagonal.md` for ports-and-adapters / NestJS-hex). Each stage is a list of rules; include EVERY matching resource (a change set can touch multiple languages/frameworks/architectures) — do not stop at the first match. These files are runtime resources, not Skill-tool skills, so always read them by path.
|
|
19
|
-
- **Project policy projection:** before selecting resources, run `okstra model-io project-context --project-root <PROJECT_ROOT> --task-ref <
|
|
19
|
+
- **Project policy projection:** before selecting resources, run `okstra model-io project-context --project-root <PROJECT_ROOT> --task-ref <TASK_KEY>` (the full `Task key` your dispatch prompt names). Consume its `Architecture style`, `Project Review Rule Packs`, and `Project QA Commands` sections; do not open Okstra-owned JSON storage.
|
|
20
20
|
- **Declared architecture style — an authoritative Stage 3 input, and it binds.** A projected `hexagonal` selects `architectures/hexagonal.md` even when none of Stage 3's layout signals matched, so the declaration — not the directory shape — decides. A projected `layered` has no pack resource; its invariant applies from this line: dependencies run one direction only — an upper layer may import a lower one, never the reverse — and a variation point is extracted onto a layer boundary. A declared style makes this overlay binding rather than advisory, and which rule binds follows the style: under `hexagonal` the overlay's otherwise-advisory concrete-adapter item is blocking, so a service dependency you add or modify goes through a port instead of a concrete implementation and that placement violation is fixed before the write rather than recorded as a note; under `layered` what binds is the direction invariant just stated — your own judgement over the import list of every file the diff touches, plus extracting a variation point onto a layer boundary — while the concrete-adapter item stays advisory, since `layered` has no ports to route it through. An absent or `none` projected style leaves Stage 3 detection-driven and its overlay advisory. The verifier re-grades the same diff under the same declaration (`_implementation-verifier.md` → Static design & test-quality review), so a placement violation missed here returns as a verdict `FAIL`.
|
|
21
21
|
- **Project review rule packs:** a pack applies when either source names it — the task brief's `Source Material` / `Reporter Confirmations` cites its exact `SKILL.md` path, or the project-context projection lists it as a standing standard. The two sources are a union. Read only those files and the `references/*.md` files they directly name; a declared path that will not open is recorded as `project-review-rules: declared <path> unreadable`, never silently dropped. Do not search parent directories or host skill catalogs. Apply those rules during implementation as a prevention pass, not a PR-comment generation workflow: do not dispatch reviewer subagents from the executor. For Fonts Ninja-style PR review packs, the executor must avoid newly introduced duplicate helper stacks, tautological tests that merely re-call the delegated helper, self-mocking, domain rules in adapters/ports, domain objects outside `domain/`, dead APIs, weak public names, and functions that fail the plain-English read.
|
|
22
22
|
- **Language-agnostic principles that ALWAYS bind (the TDD loop MUST satisfy them):** (1) no self-mocking of the SUT — stub/spy only injected collaborators, never the subject's own methods; (2) behavioral assertions on outcomes (return value, state, persisted rows, events, boundary calls) — never `toHaveBeenCalled*` on an internal helper as the only/primary assertion; (3) truthful names — a `get*` / `find*` that writes/inserts, or a name encoding the caller's use-case (`*ForInit`) or hiding a domain rule (`findValid*`), is a defect; (4) single-purpose functions ≤50 effective lines, plain-English readability. Self-mocking (1) — Enforced by `validators/detect_self_mock.py` (static), which the implementation **verifier** runs; it is never delegated to the executor (`_implementation-verifier.md` §"Self-mock detection"). Naming the enforcement here says who will check your diff, not that you should run the check: the executor's half is satisfying principle (1) in the code it writes, and it MUST NOT invoke the detector or write `<task_root>/qa/self-mock-*.json`. **Enforced:** that sidecar is not among the paths an executor attempt's `writePolicy.artifactPolicy.allowedPaths` carries, so writing it closes an otherwise-passing stage as `error` with `artifact-root change exceeds batch policy union` (`scripts/okstra_ctl/execution_mutation_audit.py`). The sidecar's absence BLOCKS at `validate-run.py` on the verifier's report.
|
|
@@ -91,7 +91,7 @@ profile document.
|
|
|
91
91
|
- if a schema-v1 table or an analysis-worker result table requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: (a) <answer> — <rationale>; Alternatives: (b) <option> (c) <option>`. The recommended answer is always the first option and MUST carry the `(a)` label; alternatives continue the same letter sequence from `(b)` (a lone alternative is `(b) <option>`, never restart at `(a)`), so the full option set reads `(a) (b) (c) …` in order and renders each as its own selectable option. Do **not** append a pick-one answer-space summary such as `(pick 1 of A / B)` or `(pick N of …)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
|
|
92
92
|
- For schema v2, data.json is canonical and the HTML exports answers to a user-response sidecar; the source report is never edited. `--resume-clarification` carries those answers into the next run. The lower-level `--clarification-response <path>` remains available for scripted runs.
|
|
93
93
|
- When a response is carried in, reconcile every prior `clarificationItems[]` row against new evidence and update its status to `resolved` or `obsolete` before issuing the next verdict. Schema-v1 compatibility Markdown may additionally render its conditional Section 0; the schema-v2 full reading copy records decisions under `## Clarification and User Decisions`.
|
|
94
|
-
- **Supersession (BLOCKING).** Reconciling the `C-*` row is only half of incorporating an answer. An answer does not merely *add* a decision — it *invalidates* whatever the previous run wrote under the opposite assumption. Before issuing the next decision, walk the prior deliverable prose for every statement the answer makes false and **delete or rewrite it**, then record the retirement. Adding the new decision while leaving the contradicting sentence in place puts two opposite instructions for the same symbol in one document; the implementer must then guess which is live, and the next verification round correctly blocks on it. In `implementation-planning` this record is `implementationPlanning.supersessionLedger[]` — one entry per answered clarification, either `disposition: superseded` (with the retired statement, its replacement, and the sections revised) or `disposition: no-dependent-statement` (with a rationale). A plan built from a selected direction inherits the answers the option-selection record carried before it has any statement to retire, so those carried rows need no entry; the rows this plan itself raised and settled still do. **Enforced:** `
|
|
94
|
+
- **Supersession (BLOCKING).** Reconciling the `C-*` row is only half of incorporating an answer. An answer does not merely *add* a decision — it *invalidates* whatever the previous run wrote under the opposite assumption. Before issuing the next decision, walk the prior deliverable prose for every statement the answer makes false and **delete or rewrite it**, then record the retirement. Adding the new decision while leaving the contradicting sentence in place puts two opposite instructions for the same symbol in one document; the implementer must then guess which is live, and the next verification round correctly blocks on it. In `implementation-planning` this record is `implementationPlanning.supersessionLedger[]` — one entry per answered clarification, either `disposition: superseded` (with the retired statement, its replacement, and the sections revised) or `disposition: no-dependent-statement` (with a rationale). A plan built from a selected direction inherits the answers the option-selection record carried before it has any statement to retire, so those carried rows need no entry; the rows this plan itself raised and settled still do. **Enforced:** `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_supersession_ledger` requires an entry per answered clarification, exempting the ledger's `carriedDecisions[]` ids on a selected-direction plan; whether the claim is *true* is what the §5.5.9 adversarial round tests.
|
|
95
95
|
- Verdict Card data consistency (shared; schema-v1 Markdown keeps the legacy visible card):
|
|
96
96
|
- The Card carries no verdict token — the token lives once, in `finalVerdict.verdictToken`, and every gate reads it there. `verdictCard.direction` byte-matches `finalVerdict.direction`; next-step routing agrees with `recommendedNextSteps[0]`. The full reading copy and human summary are derived from the data fields without repeating both visible sections. **Enforced in part:** the v3.0 schema's `verdictCard` is `additionalProperties: false` with no verdict-token property, so the token cannot be duplicated onto the Card, and `scripts/okstra_ctl/report_narrative.py` `writer_owned_schema` applies the finished report's `$defs.Direction` enum to the narrative, rejecting an off-enum `direction` while the writer can still be re-run. The byte-match between the two `direction` fields is not compared by anything — assembly overwrites `nextStep` on both when the plan-body gate passes (`scripts/okstra_ctl/report_assembly.py:590-604`) but leaves `direction` as the writer wrote it.
|
|
97
97
|
- Cross-worker traceability (shared — applies to every analysis worker output and to the lead's `## 6.` / `## 2.` tables in the final-report):
|
|
@@ -14,4 +14,4 @@ mode:" when the include directive (placed at column 0) is resolved in-place.
|
|
|
14
14
|
Do NOT write the literal include directive token in this file's body — the
|
|
15
15
|
resolver matches it anywhere and would recurse on this file itself.
|
|
16
16
|
-->
|
|
17
|
-
- **Coverage critic (opt-in, one slot)**: critic `min` is 0 and `recommended`/`max` are 1 — the wizard asks whether to add the slot with 1 recommended, and the user picks the model when they add it (`--role-model critic=<provider>/<model>` or the wizard role-model step). `--critic off` and a 0-count selection are both accepted; the run then dispatches no critic pass, and an analyser 1-1 tie stays `needs-reverify` with no slot to settle it — `okstra plan-items next-dispatch` answers `user-decision` for those items and the lead opens an approval decision instead of another round. A reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface **both** findings nobody covered and work the findings propose that no requirement asked for (`category: "unrequested-scope"`); its candidates are judged only after a 1-round adversarial reverify that follows convergence. The two halves are disposed of differently — a contested coverage gap is dropped as a hallucination, while a contested over-scope candidate is recorded as a `## 5. Missing Information and Risks` row instead of vanishing. In `implementation-planning`, the same critic slot also settles plan-body 1-1 splits (`critic-worker` on `--tie-vote` items only). See `prompts/lead/convergence.md` "Coverage critic pass" and `
|
|
17
|
+
- **Coverage critic (opt-in, one slot)**: critic `min` is 0 and `recommended`/`max` are 1 — the wizard asks whether to add the slot with 1 recommended, and the user picks the model when they add it (`--role-model critic=<provider>/<model>` or the wizard role-model step). `--critic off` and a 0-count selection are both accepted; the run then dispatches no critic pass, and an analyser 1-1 tie stays `needs-reverify` with no slot to settle it — `okstra plan-items next-dispatch` answers `user-decision` for those items and the lead opens an approval decision instead of another round. A reused-worker critic pass is dispatched concurrently with the first convergence reverify round to surface **both** findings nobody covered and work the findings propose that no requirement asked for (`category: "unrequested-scope"`); its candidates are judged only after a 1-round adversarial reverify that follows convergence. The two halves are disposed of differently — a contested coverage gap is dropped as a hallucination, while a contested over-scope candidate is recorded as a `## 5. Missing Information and Risks` row instead of vanishing. In `implementation-planning`, the same critic slot also settles plan-body 1-1 splits (`critic-worker` on `--tie-vote` items only). See `prompts/lead/convergence.md` "Coverage critic pass" and `scripts/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md` even-split rule.
|
|
@@ -1,100 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"requirements-discovery": [
|
|
3
|
-
"source code edits of any kind",
|
|
4
|
-
"implementation planning or detailed design beyond what is required to choose the next phase",
|
|
5
|
-
"executing builds, migrations, deployments, or any state-mutating command",
|
|
6
|
-
"starting `error-analysis`, `implementation-planning`, or `implementation` inside this run (each must be a separate run, and `implementation` additionally requires an approved `implementation-planning` deliverable)"
|
|
7
|
-
],
|
|
8
|
-
"improvement-discovery": [
|
|
9
|
-
"source code edits of any kind",
|
|
10
|
-
"implementation planning, root-cause analysis, builds, migrations, or deployments",
|
|
11
|
-
"starting `implementation-planning`, `implementation`, `error-analysis`, or any other lifecycle phase inside this run",
|
|
12
|
-
"generating candidates outside the lens whitelist (Lens enum violation rejects the report)",
|
|
13
|
-
"exceeding the candidate cap (absolute cap 12)",
|
|
14
|
-
"free external data fetch beyond the brief's Source Material or Phase 1.5 resolved scope",
|
|
15
|
-
"interpreting user phrases like `다음 단계 진행해` as authorisation to enter another phase"
|
|
16
|
-
],
|
|
17
|
-
"project-analysis": [
|
|
18
|
-
"source or configuration edits",
|
|
19
|
-
"tests, builds, migrations, or deployments",
|
|
20
|
-
"starting any other lifecycle phase inside this run"
|
|
21
|
-
],
|
|
22
|
-
"feature-analysis": [
|
|
23
|
-
"source or configuration edits",
|
|
24
|
-
"tests, builds, migrations, or deployments",
|
|
25
|
-
"starting any other lifecycle phase inside this run"
|
|
26
|
-
],
|
|
27
|
-
"change-impact-analysis": [
|
|
28
|
-
"source or configuration edits",
|
|
29
|
-
"tests, builds, migrations, or deployments",
|
|
30
|
-
"starting any other lifecycle phase inside this run",
|
|
31
|
-
"implementation alternatives",
|
|
32
|
-
"file change specifications",
|
|
33
|
-
"stepwise execution plans"
|
|
34
|
-
],
|
|
35
|
-
"error-analysis": [
|
|
36
|
-
"source code edits, refactors, or fix attempts",
|
|
37
|
-
"implementation design or planning artifacts",
|
|
38
|
-
"executing builds, migrations, deployments, or any state-mutating command",
|
|
39
|
-
"starting `implementation-planning` or `implementation` inside this run (each must be a separate run, and `implementation` additionally requires an approved `implementation-planning` deliverable)"
|
|
40
|
-
],
|
|
41
|
-
"implementation-option-selection": [
|
|
42
|
-
"source or configuration edits, refactors, or fix attempts",
|
|
43
|
-
"tests, builds, migrations, deployments, or any state-mutating command",
|
|
44
|
-
"detailed file lists, stage maps, execution commands, or plan approval",
|
|
45
|
-
"starting `implementation-planning` or `implementation` inside this run",
|
|
46
|
-
"displaying more than three merged candidates or omitting rejected-candidate audit records"
|
|
47
|
-
],
|
|
48
|
-
"implementation-planning": [
|
|
49
|
-
"source code edits of any kind (Edit/Write on project source files is forbidden)",
|
|
50
|
-
"file writes outside the run`s artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`), including task-root QA scripts, manifest, and tsconfig (planning declares conformance commands and required dependencies; implementation writes these files); in particular, do not write to `docs/superpowers/specs/` or `docs/superpowers/plans/`",
|
|
51
|
-
"executing builds, migrations, deployments, or any state-mutating command",
|
|
52
|
-
"starting `implementation` inside this run (must be a separate run authorised by an approved deliverable from this phase), even if the user says \"다음 단계 진행해\"",
|
|
53
|
-
"dispatching parallel sub-agents beyond the required worker roster (okstra owns worker fan-out)",
|
|
54
|
-
"leaving placeholders such as TBD / TODO / \"handle edge cases\" / \"similar to Option N\" in the report",
|
|
55
|
-
"delegating the self-review pass — the Okstra lead must run it"
|
|
56
|
-
],
|
|
57
|
-
"implementation": [
|
|
58
|
-
"any Edit/Write or state-mutating Bash before the pre-implementation gate passes (gate requires --approved-plan pointing to a final-report.md whose frontmatter has `approved: true`)",
|
|
59
|
-
"`git push` of any kind (including `--dry-run` against a real remote that produces side-effects), `npm publish` / `cargo publish` / `pip publish`, `gh release`, `docker push`",
|
|
60
|
-
"real database migrations, schema changes against shared environments, or writes to non-local datastores",
|
|
61
|
-
"production credentials, deploy commands, infra mutation (`terraform apply`, `kubectl apply` against non-local cluster, etc.)",
|
|
62
|
-
"external API write calls (POST/PUT/PATCH/DELETE) to third-party services other than localhost test fixtures",
|
|
63
|
-
"source edits or Bash mutations performed by any verifier role (`Antigravity verifier`, `Codex verifier`, `Claude verifier` are read-only — recommend, do not apply)",
|
|
64
|
-
"dispatching parallel sub-agents beyond the required worker roster",
|
|
65
|
-
"silent scope expansion: every file edited outside the approved plan list MUST appear in the `Out-of-plan edits` block with rationale",
|
|
66
|
-
"leaving placeholders such as TBD / TODO / \"implement later\" / \"handle edge cases\" in newly-added lines of this run (check via `git diff <base>..HEAD | grep -E '^\\+[^+].*\\b(TBD|TODO|FIXME|XXX|implement later|handle edge cases|similar to|placeholder)\\b'`; pre-existing strings in untouched regions are out of scope)",
|
|
67
|
-
"lead substituting its own verdict when every verifier present in the resolved roster returned a non-result terminal status (`timeout`/`error`/`not-run`); in that case the run MUST end as `blocked` with routing recommendation back to `error-analysis`, never with a lead-only verdict",
|
|
68
|
-
"declaring overall task acceptance — that is `final-verification` ownership; this phase reports only \"ready for final-verification\" or \"needs new planning loop\"",
|
|
69
|
-
"delegating the self-review pass — the Okstra lead must run it"
|
|
70
|
-
],
|
|
71
|
-
"final-verification": [
|
|
72
|
-
"source code edits, follow-up bug fixes, or scope expansion",
|
|
73
|
-
"state-mutating commands against the project or shared environments; permitted mutations are limited to the run's own `.okstra` artifacts (reports, state, `<task_root>/qa/result-*.json` sidecars, `okstra handoff record-verified` on acceptance) and Tier3 conformance scripts that mutate only their qaEnv replica datastore — everything else is read-only execution of pre-existing test or validation commands",
|
|
74
|
-
"starting any follow-up phase inside this run; record findings and end the run"
|
|
75
|
-
],
|
|
76
|
-
"release-handoff": [
|
|
77
|
-
"entering this phase when the cited final-verification `Verdict Token` is `conditional-accept` or `blocked`, or when no final-verification report is cited",
|
|
78
|
-
"local commit commands of any kind (`git add`, `git commit`, `git restore --staged`, `git stash`), and any direct `git merge` / `git rebase` / `git rebase --onto` / `git cherry-pick` / `git commit --amend` run by the lead. The single exception is the merge commits `okstra handoff pr-plan` itself creates on a `merge-base` branch — the lead never merges by hand. Rewriting a stage branch breaks the PR stack that sits on it.",
|
|
79
|
-
"any git push variant that rewrites remote history, regardless of intent or whether the user said \"force it\": `git push --force`, `git push --force-with-lease`, `git push -f`, `git push +<refspec>`, or any other history-rewriting invocation",
|
|
80
|
-
"pushing directly to a release base branch — i.e. `git push origin <branch>` where `<branch>` is `main`, `master`, `prod`, `preprod`, `staging`, `dev`, or the branch the user chose as the release base in this run. The only permitted push targets are the branches `okstra handoff pr-plan` listed for this run (each stage `head_branch`, and any `merge-base` branch).",
|
|
81
|
-
"bypassing repo safeguards: `--no-verify` / `-n` on `git push`, bypassing GPG signing, disabling safeguards via equivalent flags, or any hook bypass.",
|
|
82
|
-
"release-publishing commands: `gh release create`, `gh release edit`, `npm publish`, `cargo publish`, `pip publish`, `twine upload`, `docker push`, `terraform apply`, `kubectl apply` against any non-local cluster.",
|
|
83
|
-
"source-code edits, refactors, or any modification to files outside the run's own artifact directories (`reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/`). The diff being shipped MUST be exactly what the prior `implementation` run produced; release-handoff packages it, it does not re-author it.",
|
|
84
|
-
"executing any mutating command the user did NOT select. Examples: opening a PR when the user picked `local checkout`; pushing when the user picked `skip`; switching the release base branch silently after the user already chose one; opening a PR for a stage outside `HANDOFF_STAGES`.",
|
|
85
|
-
"squash-merging, or instructing anyone to squash-merge, a stage PR — and `gh pr merge` in any form. A squash replaces the commits the next stage's PR base points at, so the stack breaks; the PR body states merge-commit-or-rebase and the lead never merges.",
|
|
86
|
-
"retrying a failed git / gh command with weaker safety flags. If `git push` fails with non-fast-forward, the lead MUST stop, explain the failure to the user, and ask for instructions — it MUST NOT add `--force`.",
|
|
87
|
-
"worker dispatch of any kind, or any other parallel sub-agent fan-out. This phase runs entirely under the Okstra lead.",
|
|
88
|
-
"silently treating an unrecognised user reply as one of the menu options. If the user's answer does not match a presented choice, re-ask the question verbatim."
|
|
89
|
-
],
|
|
90
2
|
"unknown": [
|
|
91
3
|
"any action that belongs to a different lifecycle phase",
|
|
92
4
|
"source code edits or state-mutating commands unless this task type explicitly authorises them"
|
|
93
|
-
],
|
|
94
|
-
"technical-verification": [
|
|
95
|
-
"source edits or installs in the project checkout, task worktree, or another worker's experiment copy",
|
|
96
|
-
"production credentials, remote writes, deployments, migrations, publishing, commits or merging experiments into product branches",
|
|
97
|
-
"marking candidates feasible, selecting a direction, approving a plan, or declaring task acceptance",
|
|
98
|
-
"starting another lifecycle phase inside this run; return the evidence to implementation-option-selection"
|
|
99
5
|
]
|
|
100
6
|
}
|
|
@@ -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 (
|
|
@@ -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
|
|
@@ -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
|
|
@@ -430,3 +430,15 @@ def backfill_done_from_carry(plan_run_root: Path) -> int:
|
|
|
430
430
|
settled_stages.add(stage)
|
|
431
431
|
recovered += 1
|
|
432
432
|
return recovered
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def require_done_task_key(rows: List[Dict[str, Any]], stage: int) -> str:
|
|
436
|
+
from .handoff_error import HandoffError
|
|
437
|
+
|
|
438
|
+
done = latest_done_by_stage(rows)
|
|
439
|
+
row = done.get(stage)
|
|
440
|
+
if not row:
|
|
441
|
+
raise HandoffError(
|
|
442
|
+
f"stage {stage} has no done row in consumers.jsonl — "
|
|
443
|
+
"finish the implementation stage first")
|
|
444
|
+
return row.get("impl_task_key", "")
|
|
@@ -10,6 +10,14 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
from .final_report_schema import validate as validate_schema
|
|
12
12
|
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
13
|
+
from .phases.catalog import (
|
|
14
|
+
PHASES,
|
|
15
|
+
UnknownTaskType,
|
|
16
|
+
is_migrated,
|
|
17
|
+
package_root,
|
|
18
|
+
profile_contract,
|
|
19
|
+
profile_markdown,
|
|
20
|
+
)
|
|
13
21
|
|
|
14
22
|
|
|
15
23
|
_SCHEMA_FILES = {
|
|
@@ -199,16 +207,12 @@ def _load_contracts(root: ContractGraphRoot) -> _LoadedContracts:
|
|
|
199
207
|
common = _load_json_contract(root.common, "agents/common.json")
|
|
200
208
|
roles = _load_directory(root.roles, "agents/roles")
|
|
201
209
|
duties = _load_directory(root.duties, "prompts/duties")
|
|
202
|
-
profiles =
|
|
203
|
-
root.profiles,
|
|
204
|
-
"prompts/profiles",
|
|
205
|
-
excluded_names=frozenset({"forbidden-actions.json"}),
|
|
206
|
-
)
|
|
210
|
+
profiles = _load_profile_contracts(root)
|
|
207
211
|
operations = _load_directory(root.operations, "agents/operations")
|
|
208
212
|
except JsonBoundaryError as exc:
|
|
209
213
|
relative = str(exc.artifact)
|
|
210
214
|
raise ContractGraphError([f"{relative}: <root>: {exc.reason}"]) from exc
|
|
211
|
-
markdown = _load_profile_markdown(root
|
|
215
|
+
markdown = _load_profile_markdown(root, profiles)
|
|
212
216
|
return _LoadedContracts(common, roles, duties, profiles, operations, markdown)
|
|
213
217
|
|
|
214
218
|
|
|
@@ -234,25 +238,86 @@ def _load_json_contract(path: Path, relative_path: str) -> ContractFile:
|
|
|
234
238
|
return ContractFile(relative_path, schema_version, _digest(path), payload)
|
|
235
239
|
|
|
236
240
|
|
|
241
|
+
def _load_profile_contracts(root: ContractGraphRoot) -> tuple[ContractFile, ...]:
|
|
242
|
+
legacy = _load_directory(
|
|
243
|
+
root.profiles,
|
|
244
|
+
"prompts/profiles",
|
|
245
|
+
excluded_names=frozenset({"forbidden-actions.json"}),
|
|
246
|
+
)
|
|
247
|
+
migrated = _migrated_profile_contracts(_asset_root(root.profiles))
|
|
248
|
+
return _merge_profile_contracts(legacy, migrated)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _asset_root(profiles_dir: Path) -> Path:
|
|
252
|
+
# `ContractGraphRoot.from_base` 가 profiles 를 <base>/prompts/profiles 로 만든다.
|
|
253
|
+
# 손으로 만든 루트도 같은 배치를 따라야 단계 패키지를 찾는다.
|
|
254
|
+
return profiles_dir.parent.parent
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _migrated_profile_contracts(asset_root: Path) -> tuple[ContractFile, ...]:
|
|
258
|
+
if package_root(asset_root) is None:
|
|
259
|
+
return ()
|
|
260
|
+
rows: list[ContractFile] = []
|
|
261
|
+
for record in PHASES:
|
|
262
|
+
if not record.migrated:
|
|
263
|
+
continue
|
|
264
|
+
path = profile_contract(asset_root, record.task_type)
|
|
265
|
+
logical = f"prompts/profiles/{record.task_type}.json"
|
|
266
|
+
if not path.is_file():
|
|
267
|
+
raise ContractGraphError([
|
|
268
|
+
f"{logical}: migrated profile contract missing: {path}"
|
|
269
|
+
])
|
|
270
|
+
rows.append(_load_json_contract(path, logical))
|
|
271
|
+
return tuple(rows)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _merge_profile_contracts(
|
|
275
|
+
legacy: tuple[ContractFile, ...],
|
|
276
|
+
migrated: tuple[ContractFile, ...],
|
|
277
|
+
) -> tuple[ContractFile, ...]:
|
|
278
|
+
# 단계 패키지의 계약이 정본이다. 같은 id 의 prompts/profiles 사본은 업데이트가
|
|
279
|
+
# 남긴 옛 파일이다: `okstra install` 은 Python 트리를 먼저 복사하고 prompts 를
|
|
280
|
+
# 나중에 복사·정리하므로, 그 사이나 중간 실패 뒤에는 두 곳에 다 있다. 저장소에
|
|
281
|
+
# 옛 사본이 없다는 것은 test_phase_catalog 가 확인한다.
|
|
282
|
+
migrated_ids = {Path(row.relative_path).stem for row in migrated}
|
|
283
|
+
current = tuple(
|
|
284
|
+
row for row in legacy if Path(row.relative_path).stem not in migrated_ids
|
|
285
|
+
)
|
|
286
|
+
return tuple(sorted((*current, *migrated), key=lambda row: row.relative_path))
|
|
287
|
+
|
|
288
|
+
|
|
237
289
|
def _load_profile_markdown(
|
|
238
|
-
|
|
290
|
+
root: ContractGraphRoot, profiles: Sequence[ContractFile]
|
|
239
291
|
) -> dict[str, ContractFile]:
|
|
292
|
+
asset_root = _asset_root(root.profiles)
|
|
240
293
|
rows: dict[str, ContractFile] = {}
|
|
294
|
+
missing: list[str] = []
|
|
241
295
|
for profile in profiles:
|
|
242
296
|
profile_id = Path(profile.relative_path).stem
|
|
243
|
-
|
|
297
|
+
logical = f"prompts/profiles/{profile_id}.md"
|
|
298
|
+
path = _profile_markdown_path(asset_root, root.profiles, profile_id)
|
|
244
299
|
if not path.is_file():
|
|
300
|
+
if is_migrated(profile_id):
|
|
301
|
+
missing.append(f"{logical}: missing migrated profile markdown: {path}")
|
|
245
302
|
continue
|
|
246
|
-
relative_path = f"prompts/profiles/{path.name}"
|
|
247
303
|
rows[profile_id] = ContractFile(
|
|
248
|
-
|
|
304
|
+
logical,
|
|
249
305
|
profile.schema_version,
|
|
250
306
|
_digest(path),
|
|
251
307
|
{"text": path.read_text(encoding="utf-8")},
|
|
252
308
|
)
|
|
309
|
+
if missing:
|
|
310
|
+
raise ContractGraphError(missing)
|
|
253
311
|
return rows
|
|
254
312
|
|
|
255
313
|
|
|
314
|
+
def _profile_markdown_path(asset_root: Path, profiles_dir: Path, profile_id: str) -> Path:
|
|
315
|
+
try:
|
|
316
|
+
return profile_markdown(asset_root, profile_id)
|
|
317
|
+
except UnknownTaskType:
|
|
318
|
+
return profiles_dir / f"{profile_id}.md"
|
|
319
|
+
|
|
320
|
+
|
|
256
321
|
def _validate_documents(
|
|
257
322
|
loaded: _LoadedContracts, schemas: Mapping[str, Mapping[str, Any]]
|
|
258
323
|
) -> None:
|
|
@@ -59,6 +59,7 @@ from .worker_prompt_body import REPORT_WRITER_WORKER_ID
|
|
|
59
59
|
from .worker_prompt_contract import (
|
|
60
60
|
PromptRecord,
|
|
61
61
|
validate_initial_prompt_records,
|
|
62
|
+
validate_plan_verify_dispatch_identity,
|
|
62
63
|
validate_prompt_model_header,
|
|
63
64
|
validate_reverify_prompt,
|
|
64
65
|
)
|
|
@@ -1905,6 +1906,26 @@ def validate_initial_prompts(
|
|
|
1905
1906
|
raise DispatchError(f"{task_type} prompt contract: " + "; ".join(errors))
|
|
1906
1907
|
|
|
1907
1908
|
|
|
1909
|
+
def _validate_plan_verify_identities(jobs: Sequence[WorkerJob]) -> None:
|
|
1910
|
+
errors: list[str] = []
|
|
1911
|
+
for job in jobs:
|
|
1912
|
+
try:
|
|
1913
|
+
text = job.prompt_path.read_text(encoding="utf-8")
|
|
1914
|
+
except OSError as exc:
|
|
1915
|
+
errors.append(f"{job.worker_id}: cannot read prompt {job.prompt_path}: {exc}")
|
|
1916
|
+
continue
|
|
1917
|
+
errors.extend(
|
|
1918
|
+
f"{job.worker_id}: {error}"
|
|
1919
|
+
for error in validate_plan_verify_dispatch_identity(
|
|
1920
|
+
text,
|
|
1921
|
+
dispatch_kind=job.dispatch_kind,
|
|
1922
|
+
result_name=job.worker_result_path.name,
|
|
1923
|
+
)
|
|
1924
|
+
)
|
|
1925
|
+
if errors:
|
|
1926
|
+
raise DispatchError("plan-body dispatch contract: " + "; ".join(errors))
|
|
1927
|
+
|
|
1928
|
+
|
|
1908
1929
|
def validate_dispatch_prompts(
|
|
1909
1930
|
manifest: Mapping[str, Any],
|
|
1910
1931
|
active_context: Mapping[str, Any],
|
|
@@ -1912,6 +1933,7 @@ def validate_dispatch_prompts(
|
|
|
1912
1933
|
) -> None:
|
|
1913
1934
|
if isinstance(manifest.get("agentContract"), Mapping):
|
|
1914
1935
|
_validate_agent_invocations(manifest, jobs)
|
|
1936
|
+
_validate_plan_verify_identities(jobs)
|
|
1915
1937
|
initial_jobs = [
|
|
1916
1938
|
job for job in jobs if not is_verification_dispatch_kind(job.dispatch_kind)
|
|
1917
1939
|
]
|
|
@@ -11,8 +11,10 @@ from typing import Iterable
|
|
|
11
11
|
from okstra_project import ResolverError, project_json_path, resolve_project_root
|
|
12
12
|
from okstra_project.resolver import resolve_review_rule_packs
|
|
13
13
|
|
|
14
|
-
from . import
|
|
14
|
+
from . import worktree_registry
|
|
15
|
+
from .phases.improvement_discovery import lenses as improvement_lenses
|
|
15
16
|
from .models import provider_wrappers
|
|
17
|
+
from .phases.catalog import PhaseAssetError, UnknownTaskType, profile_markdown
|
|
16
18
|
from .workers import resolve_profile_workers
|
|
17
19
|
from .worktree import is_git_work_tree, main_worktree_path
|
|
18
20
|
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
@@ -207,7 +209,10 @@ def _review_rule_pack_check(project_root: Path) -> DoctorCheck | None:
|
|
|
207
209
|
|
|
208
210
|
|
|
209
211
|
def _profile_check(workspace: Path, phase: str) -> DoctorCheck:
|
|
210
|
-
|
|
212
|
+
try:
|
|
213
|
+
profile = _profile_path(workspace, phase)
|
|
214
|
+
except PhaseAssetError as exc:
|
|
215
|
+
return _fail("profile", str(exc))
|
|
211
216
|
if profile.is_file():
|
|
212
217
|
return _ok("profile", str(profile))
|
|
213
218
|
return _fail("profile", f"not found: {profile}")
|
|
@@ -311,7 +316,10 @@ def _worker_dispatch_checks(
|
|
|
311
316
|
phase: str,
|
|
312
317
|
host_runtime: str,
|
|
313
318
|
) -> list[DoctorCheck]:
|
|
314
|
-
|
|
319
|
+
try:
|
|
320
|
+
workers = resolve_profile_workers(_profile_path(workspace, phase))
|
|
321
|
+
except PhaseAssetError as exc:
|
|
322
|
+
return [_fail("worker dispatch", str(exc))]
|
|
315
323
|
if not workers:
|
|
316
324
|
return [_ok("worker dispatch", "no workers required")]
|
|
317
325
|
if not host_runtime:
|
|
@@ -456,7 +464,10 @@ def _agent_path(home: Path, worker: str) -> Path:
|
|
|
456
464
|
|
|
457
465
|
|
|
458
466
|
def _profile_path(workspace: Path, phase: str) -> Path:
|
|
459
|
-
|
|
467
|
+
try:
|
|
468
|
+
return profile_markdown(workspace, phase)
|
|
469
|
+
except UnknownTaskType:
|
|
470
|
+
return workspace / "prompts" / "profiles" / f"{phase}.md"
|
|
460
471
|
|
|
461
472
|
|
|
462
473
|
def _run(
|
|
@@ -281,7 +281,8 @@ class ExecutionMutationAudit:
|
|
|
281
281
|
+ tuple(f"unattributed change outside assigned worktree and artifact paths: {path}"
|
|
282
282
|
for path in sorted(external))
|
|
283
283
|
+ _plan_change_warnings(rows, source_changes | untracked_changes,
|
|
284
|
-
artifact_changed, out_of_plan_edits, unobserved)
|
|
284
|
+
artifact_changed, out_of_plan_edits, unobserved)
|
|
285
|
+
+ _linked_dependency_warnings(after.root),
|
|
285
286
|
changed_artifact_paths=tuple(sorted(artifact_changed - external)),
|
|
286
287
|
declared_out_of_plan_paths=tuple(out_of_plan_edits),
|
|
287
288
|
unobserved_artifact_paths=tuple(sorted(unobserved)),
|
|
@@ -370,6 +371,31 @@ def _audit_warnings(untracked_artifact_changes: set[str]) -> tuple[str, ...]:
|
|
|
370
371
|
)
|
|
371
372
|
|
|
372
373
|
|
|
374
|
+
def _linked_dependency_warnings(root: Path) -> tuple[str, ...]:
|
|
375
|
+
"""worktree 밖을 가리키는 `node_modules` 링크를 알린다.
|
|
376
|
+
|
|
377
|
+
감사는 `node_modules` 를 보지 않으므로, 그 링크를 통한 패키지 설치가 메인
|
|
378
|
+
체크아웃의 의존성 트리를 덮어써도 흔적이 남지 않는다(2026-09-25 실측).
|
|
379
|
+
"""
|
|
380
|
+
linked: list[str] = []
|
|
381
|
+
for current, directories, _ in os.walk(root, followlinks=False):
|
|
382
|
+
kept: list[str] = []
|
|
383
|
+
for name in directories:
|
|
384
|
+
path = Path(current) / name
|
|
385
|
+
if name == ".git":
|
|
386
|
+
continue
|
|
387
|
+
if name not in _INSTALLED_DEPENDENCY_DIRS:
|
|
388
|
+
kept.append(name)
|
|
389
|
+
elif path.is_symlink() and not _is_relative_to(path.resolve(), root.resolve()):
|
|
390
|
+
linked.append(f"{path.relative_to(root).as_posix()} -> {path.resolve()}")
|
|
391
|
+
directories[:] = kept
|
|
392
|
+
return tuple(
|
|
393
|
+
f"dependency tree links outside the assigned worktree: {row}; "
|
|
394
|
+
"a package install through it rewrites that target and this audit does not observe it"
|
|
395
|
+
for row in sorted(linked)
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
373
399
|
def _branch_switch_warnings(
|
|
374
400
|
before: MutationSnapshot, after: MutationSnapshot, switched: set[str],
|
|
375
401
|
) -> tuple[str, ...]:
|