okstra 0.173.0 → 0.175.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 +18 -9
- package/bin/okstra +8 -4
- package/dist/cli-registry.d.mts +16 -0
- package/dist/cli-registry.mjs +560 -0
- package/dist/cli-registry.mjs.map +1 -0
- package/dist/commands/execute/agent-prompt.d.mts +1 -0
- package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
- package/dist/commands/execute/agent-prompt.mjs.map +1 -0
- package/dist/commands/execute/codex-dispatch.d.mts +3 -0
- package/dist/commands/execute/codex-dispatch.mjs +6 -0
- package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
- package/dist/commands/execute/codex-run.d.mts +3 -0
- package/{src → dist}/commands/execute/codex-run.mjs +30 -37
- package/dist/commands/execute/codex-run.mjs.map +1 -0
- package/dist/commands/execute/convergence.d.mts +1 -0
- package/{src → dist}/commands/execute/convergence.mjs +15 -16
- package/dist/commands/execute/convergence.mjs.map +1 -0
- package/dist/commands/execute/error-log.d.mts +1 -0
- package/{src → dist}/commands/execute/error-log.mjs +2 -3
- package/dist/commands/execute/error-log.mjs.map +1 -0
- package/dist/commands/execute/git-reconcile.d.mts +1 -0
- package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
- package/dist/commands/execute/git-reconcile.mjs.map +1 -0
- package/dist/commands/execute/handoff.d.mts +1 -0
- package/{src → dist}/commands/execute/handoff.mjs +10 -11
- package/dist/commands/execute/handoff.mjs.map +1 -0
- package/dist/commands/execute/incremental-carry.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
- package/dist/commands/execute/incremental-carry.mjs.map +1 -0
- package/dist/commands/execute/incremental-scope.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
- package/dist/commands/execute/incremental-scope.mjs.map +1 -0
- package/dist/commands/execute/integrate-stages.d.mts +1 -0
- package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
- package/dist/commands/execute/integrate-stages.mjs.map +1 -0
- package/dist/commands/execute/pane-title.d.mts +1 -0
- package/dist/commands/execute/pane-title.mjs +20 -0
- package/dist/commands/execute/pane-title.mjs.map +1 -0
- package/dist/commands/execute/plan-items.d.mts +1 -0
- package/dist/commands/execute/plan-items.mjs +9 -0
- package/dist/commands/execute/plan-items.mjs.map +1 -0
- package/dist/commands/execute/plan-validate.d.mts +1 -0
- package/dist/commands/execute/plan-validate.mjs +68 -0
- package/dist/commands/execute/plan-validate.mjs.map +1 -0
- package/dist/commands/execute/plan-verify.d.mts +1 -0
- package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
- package/dist/commands/execute/plan-verify.mjs.map +1 -0
- package/dist/commands/execute/render-bundle.d.mts +3 -0
- package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
- package/dist/commands/execute/render-bundle.mjs.map +1 -0
- package/dist/commands/execute/run.d.mts +24 -0
- package/dist/commands/execute/run.mjs +196 -0
- package/dist/commands/execute/run.mjs.map +1 -0
- package/dist/commands/execute/spawn-followups.d.mts +1 -0
- package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
- package/dist/commands/execute/spawn-followups.mjs.map +1 -0
- package/dist/commands/execute/team.d.mts +3 -0
- package/dist/commands/execute/team.mjs +56 -0
- package/dist/commands/execute/team.mjs.map +1 -0
- package/dist/commands/execute/token-usage.d.mts +1 -0
- package/{src → dist}/commands/execute/token-usage.mjs +2 -3
- package/dist/commands/execute/token-usage.mjs.map +1 -0
- package/dist/commands/execute/wizard.d.mts +8 -0
- package/dist/commands/execute/wizard.mjs +191 -0
- package/dist/commands/execute/wizard.mjs.map +1 -0
- package/dist/commands/execute/worker-audit-check.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
- package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
- package/dist/commands/execute/worker-dispatch.d.mts +7 -0
- package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
- package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
- package/dist/commands/execute/worker-state.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-state.mjs +15 -16
- package/dist/commands/execute/worker-state.mjs.map +1 -0
- package/dist/commands/execute/worktree-lookup.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
- package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
- package/dist/commands/execute/worktree-status.d.mts +1 -0
- package/dist/commands/execute/worktree-status.mjs +115 -0
- package/dist/commands/execute/worktree-status.mjs.map +1 -0
- package/dist/commands/inspect/code-review.d.mts +1 -0
- package/{src → dist}/commands/inspect/code-review.mjs +16 -19
- package/dist/commands/inspect/code-review.mjs.map +1 -0
- package/dist/commands/inspect/container.d.mts +1 -0
- package/{src → dist}/commands/inspect/container.mjs +11 -13
- package/dist/commands/inspect/container.mjs.map +1 -0
- package/dist/commands/inspect/context-cost.d.mts +1 -0
- package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
- package/dist/commands/inspect/context-cost.mjs.map +1 -0
- package/dist/commands/inspect/design-prep.d.mts +1 -0
- package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
- package/dist/commands/inspect/design-prep.mjs.map +1 -0
- package/dist/commands/inspect/error-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-report.mjs +11 -13
- package/dist/commands/inspect/error-report.mjs.map +1 -0
- package/dist/commands/inspect/error-zip.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
- package/dist/commands/inspect/error-zip.mjs.map +1 -0
- package/dist/commands/inspect/log-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/log-report.mjs +11 -13
- package/dist/commands/inspect/log-report.mjs.map +1 -0
- package/dist/commands/inspect/profile-show.d.mts +1 -0
- package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
- package/dist/commands/inspect/profile-show.mjs.map +1 -0
- package/dist/commands/inspect/recap.d.mts +1 -0
- package/{src → dist}/commands/inspect/recap.mjs +11 -13
- package/dist/commands/inspect/recap.mjs.map +1 -0
- package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
- package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
- package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
- package/dist/commands/inspect/rollup.d.mts +1 -0
- package/{src → dist}/commands/inspect/rollup.mjs +11 -13
- package/dist/commands/inspect/rollup.mjs.map +1 -0
- package/dist/commands/inspect/run-audit.d.mts +1 -0
- package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
- package/dist/commands/inspect/run-audit.mjs.map +1 -0
- package/dist/commands/inspect/set-work-status.d.mts +1 -0
- package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
- package/dist/commands/inspect/set-work-status.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.d.mts +1 -0
- package/dist/commands/inspect/stage-map.mjs +110 -0
- package/dist/commands/inspect/stage-map.mjs.map +1 -0
- package/dist/commands/inspect/task-list.d.mts +1 -0
- package/dist/commands/inspect/task-list.mjs +103 -0
- package/dist/commands/inspect/task-list.mjs.map +1 -0
- package/dist/commands/inspect/task-show.d.mts +1 -0
- package/dist/commands/inspect/task-show.mjs +108 -0
- package/dist/commands/inspect/task-show.mjs.map +1 -0
- package/dist/commands/inspect/time-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/time-report.mjs +11 -13
- package/dist/commands/inspect/time-report.mjs.map +1 -0
- package/dist/commands/inspect/usage-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
- package/dist/commands/inspect/usage-report.mjs.map +1 -0
- package/dist/commands/inspect/user-response.d.mts +1 -0
- package/{src → dist}/commands/inspect/user-response.mjs +10 -11
- package/dist/commands/inspect/user-response.mjs.map +1 -0
- package/dist/commands/inspect/worker-liveness.d.mts +1 -0
- package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
- package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
- package/dist/commands/lifecycle/check-project.d.mts +21 -0
- package/dist/commands/lifecycle/check-project.mjs +175 -0
- package/dist/commands/lifecycle/check-project.mjs.map +1 -0
- package/dist/commands/lifecycle/config.d.mts +1 -0
- package/dist/commands/lifecycle/config.mjs +369 -0
- package/dist/commands/lifecycle/config.mjs.map +1 -0
- package/dist/commands/lifecycle/doctor.d.mts +10 -0
- package/dist/commands/lifecycle/doctor.mjs +317 -0
- package/dist/commands/lifecycle/doctor.mjs.map +1 -0
- package/dist/commands/lifecycle/install.d.mts +17 -0
- package/dist/commands/lifecycle/install.mjs +1077 -0
- package/dist/commands/lifecycle/install.mjs.map +1 -0
- package/dist/commands/lifecycle/migrate.d.mts +1 -0
- package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
- package/dist/commands/lifecycle/migrate.mjs.map +1 -0
- package/dist/commands/lifecycle/model.d.mts +1 -0
- package/dist/commands/lifecycle/model.mjs +22 -0
- package/dist/commands/lifecycle/model.mjs.map +1 -0
- package/dist/commands/lifecycle/paths.d.mts +1 -0
- package/dist/commands/lifecycle/paths.mjs +102 -0
- package/dist/commands/lifecycle/paths.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.d.mts +1 -0
- package/dist/commands/lifecycle/preflight.mjs +124 -0
- package/dist/commands/lifecycle/preflight.mjs.map +1 -0
- package/dist/commands/lifecycle/setup.d.mts +1 -0
- package/dist/commands/lifecycle/setup.mjs +297 -0
- package/dist/commands/lifecycle/setup.mjs.map +1 -0
- package/dist/commands/lifecycle/uninstall.d.mts +4 -0
- package/dist/commands/lifecycle/uninstall.mjs +262 -0
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
- package/dist/commands/manager.d.mts +3 -0
- package/{src → dist}/commands/manager.mjs +25 -32
- package/dist/commands/manager.mjs.map +1 -0
- package/dist/commands/memory/memory.d.mts +1 -0
- package/dist/commands/memory/memory.mjs +569 -0
- package/dist/commands/memory/memory.mjs.map +1 -0
- package/dist/commands/pr/pr.d.mts +33 -0
- package/dist/commands/pr/pr.mjs +296 -0
- package/dist/commands/pr/pr.mjs.map +1 -0
- package/dist/commands/report/agent-activity.d.mts +1 -0
- package/{src → dist}/commands/report/agent-activity.mjs +11 -12
- package/dist/commands/report/agent-activity.mjs.map +1 -0
- package/dist/commands/report/finalize.d.mts +4 -0
- package/dist/commands/report/finalize.mjs +64 -0
- package/dist/commands/report/finalize.mjs.map +1 -0
- package/dist/commands/report/inject-report-index.d.mts +1 -0
- package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
- package/dist/commands/report/inject-report-index.mjs.map +1 -0
- package/dist/commands/report/render-final-report.d.mts +1 -0
- package/{src → dist}/commands/report/render-final-report.mjs +6 -7
- package/dist/commands/report/render-final-report.mjs.map +1 -0
- package/dist/commands/report/render-views.d.mts +1 -0
- package/{src → dist}/commands/report/render-views.mjs +6 -7
- package/dist/commands/report/render-views.mjs.map +1 -0
- package/dist/commands/report/translate.d.mts +1 -0
- package/{src → dist}/commands/report/translate.mjs +6 -7
- package/dist/commands/report/translate.mjs.map +1 -0
- package/dist/lib/helper-scripts.d.mts +3 -0
- package/{src → dist}/lib/helper-scripts.mjs +11 -14
- package/dist/lib/helper-scripts.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +25 -0
- package/dist/lib/host-registry-client.mjs +152 -0
- package/dist/lib/host-registry-client.mjs.map +1 -0
- package/dist/lib/install-assets.d.mts +8 -0
- package/{src → dist}/lib/install-assets.mjs +17 -20
- package/dist/lib/install-assets.mjs.map +1 -0
- package/dist/lib/okstra-dirs.d.mts +4 -0
- package/{src → dist}/lib/okstra-dirs.mjs +3 -6
- package/dist/lib/okstra-dirs.mjs.map +1 -0
- package/dist/lib/paths.d.mts +4 -0
- package/dist/lib/paths.mjs +78 -0
- package/dist/lib/paths.mjs.map +1 -0
- package/dist/lib/proc.d.mts +3 -0
- package/dist/lib/proc.mjs +31 -0
- package/dist/lib/proc.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +8 -0
- package/dist/lib/python-helper.mjs +123 -0
- package/dist/lib/python-helper.mjs.map +1 -0
- package/dist/lib/runtime-manifest.d.mts +7 -0
- package/dist/lib/runtime-manifest.mjs +73 -0
- package/dist/lib/runtime-manifest.mjs.map +1 -0
- package/dist/lib/skill-catalog.d.mts +4 -0
- package/dist/lib/skill-catalog.mjs +50 -0
- package/dist/lib/skill-catalog.mjs.map +1 -0
- package/dist/lib/types.d.mts +129 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/lib/version.d.mts +2 -0
- package/dist/lib/version.mjs +27 -0
- package/dist/lib/version.mjs.map +1 -0
- package/docs/architecture/storage-model.md +17 -3
- package/docs/architecture.md +12 -22
- package/docs/cli.md +29 -8
- package/docs/container.md +1 -1
- package/docs/contributor-change-matrix.md +3 -3
- package/docs/project-structure-overview.md +29 -20
- package/docs/task-process/README.md +3 -1
- package/docs/task-process/implementation-planning.md +2 -2
- package/docs/task-process/implementation.md +1 -1
- package/package.json +11 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +3 -2
- package/runtime/agents/workers/report-writer-worker.md +3 -1
- package/runtime/agents/workers/translator-worker.md +3 -1
- package/runtime/bin/lib/okstra/cli.sh +12 -0
- package/runtime/bin/lib/okstra/globals.sh +4 -1
- package/runtime/bin/lib/okstra/usage.sh +4 -4
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +245 -16
- package/runtime/bin/okstra-trace-cleanup.sh +58 -129
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/lead/adapters/cmux.md +2 -0
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +4 -2
- package/runtime/prompts/lead/plan-body-verification.md +13 -6
- package/runtime/prompts/lead/report-writer.md +6 -6
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +2 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/change-impact-analysis.md +16 -0
- package/runtime/prompts/profiles/error-analysis.md +21 -1
- package/runtime/prompts/profiles/feature-analysis.md +16 -0
- package/runtime/prompts/profiles/final-verification.md +16 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +16 -0
- package/runtime/prompts/profiles/implementation-planning.md +32 -9
- package/runtime/prompts/profiles/implementation.md +17 -1
- package/runtime/prompts/profiles/improvement-discovery.md +16 -0
- package/runtime/prompts/profiles/project-analysis.md +16 -0
- package/runtime/prompts/profiles/release-handoff.md +5 -1
- package/runtime/prompts/profiles/requirements-discovery.md +20 -0
- package/runtime/prompts/wizard/prompts.ko.json +18 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +1 -6
- package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +4 -4
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +100 -25
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
- package/runtime/python/okstra_ctl/agent_activity.py +5 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
- package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
- package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
- package/runtime/python/okstra_ctl/conformance.py +68 -0
- package/runtime/python/okstra_ctl/convergence.py +409 -3
- package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
- package/runtime/python/okstra_ctl/convergence_store.py +170 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +1181 -102
- package/runtime/python/okstra_ctl/dispatch_state.py +835 -22
- package/runtime/python/okstra_ctl/doctor.py +16 -2
- package/runtime/python/okstra_ctl/domain/host.py +32 -1
- package/runtime/python/okstra_ctl/domain/provider.py +212 -6
- package/runtime/python/okstra_ctl/domain/role.py +125 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +13 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +39 -1
- package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
- package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
- package/runtime/python/okstra_ctl/error_log_write.py +9 -0
- package/runtime/python/okstra_ctl/error_report.py +21 -1
- package/runtime/python/okstra_ctl/execution_identity.py +529 -0
- package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +5 -4
- package/runtime/python/okstra_ctl/index.py +6 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
- package/runtime/python/okstra_ctl/legacy_model_selection.py +490 -0
- package/runtime/python/okstra_ctl/model_cli.py +278 -0
- package/runtime/python/okstra_ctl/model_defaults.py +46 -0
- package/runtime/python/okstra_ctl/model_discovery.py +26 -7
- package/runtime/python/okstra_ctl/model_pool.py +501 -0
- package/runtime/python/okstra_ctl/models.py +47 -27
- package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
- package/runtime/python/okstra_ctl/pane_reclaim.py +13 -22
- package/runtime/python/okstra_ctl/pane_title.py +151 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
- package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +78 -10
- package/runtime/python/okstra_ctl/render_final_report.py +34 -19
- package/runtime/python/okstra_ctl/report_contract.py +39 -1
- package/runtime/python/okstra_ctl/report_finalize.py +84 -9
- package/runtime/python/okstra_ctl/report_html/render.py +1 -0
- package/runtime/python/okstra_ctl/role_requirements.py +217 -0
- package/runtime/python/okstra_ctl/run.py +1250 -260
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- package/runtime/python/okstra_ctl/schema_excerpt.py +1 -1
- package/runtime/python/okstra_ctl/scope_provenance.py +1 -1
- package/runtime/python/okstra_ctl/session.py +69 -12
- package/runtime/python/okstra_ctl/team.py +51 -25
- package/runtime/python/okstra_ctl/time_report.py +38 -4
- package/runtime/python/okstra_ctl/tmux.py +19 -149
- package/runtime/python/okstra_ctl/usage_identity.py +86 -0
- package/runtime/python/okstra_ctl/usage_report.py +15 -3
- package/runtime/python/okstra_ctl/wizard.py +1253 -62
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- package/runtime/python/okstra_ctl/worker_request.py +9 -1
- package/runtime/python/okstra_ctl/worker_runner.py +130 -5
- package/runtime/python/okstra_ctl/workers.py +7 -5
- package/runtime/python/okstra_ctl/worktree.py +69 -3
- package/runtime/python/okstra_ctl/write_policy.py +571 -0
- package/runtime/python/okstra_token_usage/cli.py +1 -1
- package/runtime/python/okstra_token_usage/collect.py +66 -6
- package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
- package/runtime/schemas/final-report-v2.0.schema.json +25 -0
- package/runtime/skills/okstra-run/SKILL.md +9 -9
- package/runtime/skills/okstra-setup/references/project-config.md +11 -0
- package/runtime/templates/report-writer-prompt-preamble.md +2 -0
- package/runtime/templates/reports/final-report-v2.template.md +7 -1
- package/runtime/templates/reports/html/base.template.html +21 -0
- package/runtime/templates/reports/html/i18n/en.json +4 -0
- package/runtime/templates/reports/html/i18n/ko.json +4 -0
- package/runtime/templates/reports/settings.template.json +0 -24
- package/runtime/templates/worker-prompt-preamble.md +6 -2
- package/runtime/validators/lib/fixtures.sh +98 -33
- package/runtime/validators/validate-implementation-plan-stages.py +63 -3
- package/runtime/validators/validate-run.py +443 -508
- package/runtime/validators/validate_session_conformance.py +26 -8
- package/runtime/bin/okstra-subagent-reclaim.sh +0 -26
- package/runtime/schemas/final-report-v1.0.schema.json +0 -6366
- package/runtime/templates/reports/final-report.template.md +0 -1258
- package/src/cli-registry.mjs +0 -532
- package/src/commands/execute/codex-dispatch.mjs +0 -10
- package/src/commands/execute/plan-items.mjs +0 -9
- package/src/commands/execute/plan-validate.mjs +0 -68
- package/src/commands/execute/run.mjs +0 -208
- package/src/commands/execute/team.mjs +0 -63
- package/src/commands/execute/wizard.mjs +0 -167
- package/src/commands/inspect/stage-map.mjs +0 -98
- package/src/commands/inspect/task-list.mjs +0 -97
- package/src/commands/inspect/task-show.mjs +0 -100
- package/src/commands/lifecycle/check-project.mjs +0 -187
- package/src/commands/lifecycle/config.mjs +0 -385
- package/src/commands/lifecycle/doctor.mjs +0 -308
- package/src/commands/lifecycle/install.mjs +0 -1172
- package/src/commands/lifecycle/paths.mjs +0 -104
- package/src/commands/lifecycle/preflight.mjs +0 -125
- package/src/commands/lifecycle/setup.mjs +0 -333
- package/src/commands/lifecycle/uninstall.mjs +0 -279
- package/src/commands/memory/memory.mjs +0 -524
- package/src/commands/pr/pr.mjs +0 -261
- package/src/commands/report/finalize.mjs +0 -64
- package/src/lib/host-registry-client.mjs +0 -176
- package/src/lib/paths.mjs +0 -86
- package/src/lib/proc.mjs +0 -31
- package/src/lib/python-helper.mjs +0 -142
- package/src/lib/runtime-manifest.mjs +0 -70
- package/src/lib/skill-catalog.mjs +0 -53
- package/src/lib/version.mjs +0 -20
- /package/{src → dist}/commands/pr/default.md +0 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
"""Batch-scoped source and Git mutation audit for worker invocations."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
from collections.abc import Mapping, Sequence
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .write_policy import WritePolicy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MutationAuditError(ValueError):
|
|
17
|
+
"""Raised before dispatch when policies cannot share one audit batch."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def assert_compatible_batch(policies: Sequence[WritePolicy]) -> None:
|
|
21
|
+
"""Reject a dispatch batch before any attempt is recorded."""
|
|
22
|
+
_validate_batch(tuple(policies))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class MutationSnapshot:
|
|
27
|
+
root: Path
|
|
28
|
+
artifact_root: Path
|
|
29
|
+
policy_digests: tuple[str, ...]
|
|
30
|
+
file_digests: Mapping[str, str]
|
|
31
|
+
artifact_digests: Mapping[str, str]
|
|
32
|
+
scratch_digests: Mapping[str, str]
|
|
33
|
+
git_projection: Mapping[str, Any]
|
|
34
|
+
digest: str
|
|
35
|
+
orchestrator_paths: tuple[str, ...] = ()
|
|
36
|
+
|
|
37
|
+
def to_payload(self) -> dict[str, Any]:
|
|
38
|
+
return {
|
|
39
|
+
"root": str(self.root),
|
|
40
|
+
"artifactRoot": str(self.artifact_root),
|
|
41
|
+
"policyDigests": list(self.policy_digests),
|
|
42
|
+
"fileDigests": dict(self.file_digests),
|
|
43
|
+
"artifactDigests": dict(self.artifact_digests),
|
|
44
|
+
"scratchDigests": dict(self.scratch_digests),
|
|
45
|
+
"gitProjection": dict(self.git_projection),
|
|
46
|
+
"digest": self.digest,
|
|
47
|
+
"orchestratorPaths": list(self.orchestrator_paths),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_payload(cls, payload: Mapping[str, Any]) -> "MutationSnapshot":
|
|
52
|
+
snapshot = cls(
|
|
53
|
+
root=Path(str(payload["root"])),
|
|
54
|
+
artifact_root=Path(str(payload["artifactRoot"])),
|
|
55
|
+
policy_digests=tuple(payload["policyDigests"]),
|
|
56
|
+
file_digests=dict(payload["fileDigests"]),
|
|
57
|
+
artifact_digests=dict(payload["artifactDigests"]),
|
|
58
|
+
scratch_digests=dict(payload["scratchDigests"]),
|
|
59
|
+
git_projection=dict(payload["gitProjection"]),
|
|
60
|
+
digest=str(payload["digest"]),
|
|
61
|
+
orchestrator_paths=tuple(payload.get("orchestratorPaths", ())),
|
|
62
|
+
)
|
|
63
|
+
expected = _snapshot_digest(
|
|
64
|
+
snapshot.root,
|
|
65
|
+
snapshot.artifact_root,
|
|
66
|
+
snapshot.policy_digests,
|
|
67
|
+
snapshot.file_digests,
|
|
68
|
+
snapshot.artifact_digests,
|
|
69
|
+
snapshot.scratch_digests,
|
|
70
|
+
snapshot.git_projection,
|
|
71
|
+
snapshot.orchestrator_paths,
|
|
72
|
+
)
|
|
73
|
+
if snapshot.digest != expected:
|
|
74
|
+
raise MutationAuditError("mutation snapshot digest does not match payload")
|
|
75
|
+
return snapshot
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class MutationAuditResult:
|
|
80
|
+
status: str
|
|
81
|
+
changed_paths: tuple[str, ...]
|
|
82
|
+
source_changed: bool
|
|
83
|
+
git_changed: bool
|
|
84
|
+
attribution: str
|
|
85
|
+
retry_allowed: bool
|
|
86
|
+
violations: tuple[str, ...]
|
|
87
|
+
before_digest: str
|
|
88
|
+
after_digest: str
|
|
89
|
+
git_projection: Mapping[str, Any]
|
|
90
|
+
|
|
91
|
+
def change_summary(self) -> dict[str, Any]:
|
|
92
|
+
return {
|
|
93
|
+
"mutationStatus": self.status,
|
|
94
|
+
"changedPaths": list(self.changed_paths),
|
|
95
|
+
"sourceChanged": self.source_changed,
|
|
96
|
+
"gitChanged": self.git_changed,
|
|
97
|
+
"attribution": self.attribution,
|
|
98
|
+
"retryAllowed": self.retry_allowed,
|
|
99
|
+
"violations": list(self.violations),
|
|
100
|
+
"beforeDigest": self.before_digest,
|
|
101
|
+
"afterDigest": self.after_digest,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ExecutionMutationAudit:
|
|
106
|
+
"""Capture and compare one invocation batch without global state."""
|
|
107
|
+
|
|
108
|
+
def snapshot(
|
|
109
|
+
self,
|
|
110
|
+
policies: Sequence[WritePolicy],
|
|
111
|
+
*,
|
|
112
|
+
orchestrator_paths: Sequence[Path] = (),
|
|
113
|
+
) -> MutationSnapshot:
|
|
114
|
+
rows = tuple(policies)
|
|
115
|
+
root, artifact_root = _validate_batch(rows)
|
|
116
|
+
generated = _generated_paths(rows)
|
|
117
|
+
file_digests = _content_snapshot(root, generated)
|
|
118
|
+
artifact_digests = (
|
|
119
|
+
file_digests
|
|
120
|
+
if artifact_root == root
|
|
121
|
+
else _content_snapshot(artifact_root, generated)
|
|
122
|
+
)
|
|
123
|
+
scratch_digests = _scratch_snapshot(rows)
|
|
124
|
+
git_projection = _git_projection(root)
|
|
125
|
+
orchestrator = tuple(
|
|
126
|
+
sorted(str(path.resolve()) for path in orchestrator_paths)
|
|
127
|
+
)
|
|
128
|
+
policy_digests = tuple(policy.digest for policy in rows)
|
|
129
|
+
digest = _snapshot_digest(
|
|
130
|
+
root,
|
|
131
|
+
artifact_root,
|
|
132
|
+
policy_digests,
|
|
133
|
+
file_digests,
|
|
134
|
+
artifact_digests,
|
|
135
|
+
scratch_digests,
|
|
136
|
+
git_projection,
|
|
137
|
+
orchestrator,
|
|
138
|
+
)
|
|
139
|
+
return MutationSnapshot(
|
|
140
|
+
root=root,
|
|
141
|
+
artifact_root=artifact_root,
|
|
142
|
+
policy_digests=policy_digests,
|
|
143
|
+
file_digests=file_digests,
|
|
144
|
+
artifact_digests=artifact_digests,
|
|
145
|
+
scratch_digests=scratch_digests,
|
|
146
|
+
git_projection=git_projection,
|
|
147
|
+
digest=digest,
|
|
148
|
+
orchestrator_paths=orchestrator,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def compare(
|
|
152
|
+
self,
|
|
153
|
+
before: MutationSnapshot,
|
|
154
|
+
policies: Sequence[WritePolicy],
|
|
155
|
+
*,
|
|
156
|
+
out_of_plan_edits: Sequence[str] = (),
|
|
157
|
+
attempt_succeeded: bool = True,
|
|
158
|
+
result_present: bool = True,
|
|
159
|
+
) -> MutationAuditResult:
|
|
160
|
+
rows = tuple(policies)
|
|
161
|
+
after = self.snapshot(
|
|
162
|
+
rows,
|
|
163
|
+
orchestrator_paths=tuple(Path(path) for path in before.orchestrator_paths),
|
|
164
|
+
)
|
|
165
|
+
_validate_snapshot_authority(before, after, rows)
|
|
166
|
+
changed = _changed_keys(before.file_digests, after.file_digests)
|
|
167
|
+
artifact_changed = _changed_keys(
|
|
168
|
+
before.artifact_digests, after.artifact_digests
|
|
169
|
+
)
|
|
170
|
+
scratch_changed = _changed_keys(
|
|
171
|
+
before.scratch_digests, after.scratch_digests
|
|
172
|
+
)
|
|
173
|
+
source_changes = _source_changes(changed, rows, before)
|
|
174
|
+
git_changed = before.git_projection != after.git_projection
|
|
175
|
+
violations = _policy_violations(
|
|
176
|
+
before,
|
|
177
|
+
after,
|
|
178
|
+
rows,
|
|
179
|
+
source_changes,
|
|
180
|
+
out_of_plan_edits,
|
|
181
|
+
)
|
|
182
|
+
violations.extend(
|
|
183
|
+
_artifact_policy_failures(before, rows, artifact_changed)
|
|
184
|
+
)
|
|
185
|
+
status = _terminal_status(
|
|
186
|
+
rows,
|
|
187
|
+
source_changed=bool(source_changes),
|
|
188
|
+
git_changed=git_changed,
|
|
189
|
+
attempt_succeeded=attempt_succeeded,
|
|
190
|
+
result_present=result_present,
|
|
191
|
+
violations=violations,
|
|
192
|
+
)
|
|
193
|
+
retry_allowed = _retry_allowed(
|
|
194
|
+
status, bool(source_changes), git_changed, bool(scratch_changed)
|
|
195
|
+
)
|
|
196
|
+
return MutationAuditResult(
|
|
197
|
+
status=status,
|
|
198
|
+
changed_paths=tuple(sorted(source_changes)),
|
|
199
|
+
source_changed=bool(source_changes),
|
|
200
|
+
git_changed=git_changed,
|
|
201
|
+
attribution="batch-unattributed",
|
|
202
|
+
retry_allowed=retry_allowed,
|
|
203
|
+
violations=tuple(violations),
|
|
204
|
+
before_digest=before.digest,
|
|
205
|
+
after_digest=after.digest,
|
|
206
|
+
git_projection=after.git_projection,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _validate_batch(policies: tuple[WritePolicy, ...]) -> tuple[Path, Path]:
|
|
211
|
+
if not policies:
|
|
212
|
+
raise MutationAuditError("mutation audit requires at least one policy")
|
|
213
|
+
roots = {Path(str(row.source_policy["allowedRoot"])).resolve() for row in policies}
|
|
214
|
+
artifact_roots = {
|
|
215
|
+
Path(str(row.artifact_policy["allowedRoot"])).resolve()
|
|
216
|
+
for row in policies
|
|
217
|
+
}
|
|
218
|
+
if len(roots) != 1:
|
|
219
|
+
raise MutationAuditError("one mutation batch must use one assigned worktree")
|
|
220
|
+
if len(artifact_roots) != 1:
|
|
221
|
+
raise MutationAuditError("one mutation batch must use one artifact root")
|
|
222
|
+
mutation = any(row.source_mode == "project-mutation" for row in policies)
|
|
223
|
+
non_exact_readonly = any(
|
|
224
|
+
row.source_mode == "source-readonly"
|
|
225
|
+
and _maximum_precision(row) != "exact-path"
|
|
226
|
+
for row in policies
|
|
227
|
+
)
|
|
228
|
+
if mutation and non_exact_readonly:
|
|
229
|
+
raise MutationAuditError(
|
|
230
|
+
"project mutation and non-exact readonly calls must run in separate batches"
|
|
231
|
+
)
|
|
232
|
+
return next(iter(roots)), next(iter(artifact_roots))
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _validate_snapshot_authority(
|
|
236
|
+
before: MutationSnapshot,
|
|
237
|
+
after: MutationSnapshot,
|
|
238
|
+
policies: tuple[WritePolicy, ...],
|
|
239
|
+
) -> None:
|
|
240
|
+
expected = tuple(policy.digest for policy in policies)
|
|
241
|
+
if (
|
|
242
|
+
before.root != after.root
|
|
243
|
+
or before.artifact_root != after.artifact_root
|
|
244
|
+
or before.policy_digests != expected
|
|
245
|
+
):
|
|
246
|
+
raise MutationAuditError("mutation snapshot does not match batch policies")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _maximum_precision(policy: WritePolicy) -> str:
|
|
250
|
+
return policy.maximum_boundary_precision
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _content_snapshot(root: Path, generated: frozenset[str]) -> dict[str, str]:
|
|
254
|
+
rows: dict[str, str] = {}
|
|
255
|
+
for current, directories, filenames in os.walk(root, followlinks=False):
|
|
256
|
+
current_path = Path(current)
|
|
257
|
+
traversable: list[str] = []
|
|
258
|
+
for name in sorted(directories):
|
|
259
|
+
path = current_path / name
|
|
260
|
+
relative_path = path.relative_to(root)
|
|
261
|
+
if name == ".git" or _excluded(relative_path, generated):
|
|
262
|
+
continue
|
|
263
|
+
if path.is_symlink():
|
|
264
|
+
rows[relative_path.as_posix()] = _path_digest(path)
|
|
265
|
+
continue
|
|
266
|
+
traversable.append(name)
|
|
267
|
+
directories[:] = traversable
|
|
268
|
+
for name in sorted(filenames):
|
|
269
|
+
path = current_path / name
|
|
270
|
+
relative = path.relative_to(root).as_posix()
|
|
271
|
+
if relative == ".git" or _excluded(Path(relative), generated):
|
|
272
|
+
continue
|
|
273
|
+
rows[relative] = _path_digest(path)
|
|
274
|
+
return rows
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _scratch_snapshot(policies: Sequence[WritePolicy]) -> dict[str, str]:
|
|
278
|
+
roots = {
|
|
279
|
+
Path(value)
|
|
280
|
+
for policy in policies
|
|
281
|
+
for value in policy.auxiliary_policy.get("scratchRoots", ())
|
|
282
|
+
}
|
|
283
|
+
rows: dict[str, str] = {}
|
|
284
|
+
for root in sorted(roots):
|
|
285
|
+
if not root.exists():
|
|
286
|
+
continue
|
|
287
|
+
if root.is_file() or root.is_symlink():
|
|
288
|
+
rows[str(root)] = _path_digest(root)
|
|
289
|
+
continue
|
|
290
|
+
for path in sorted(item for item in root.rglob("*") if not item.is_dir()):
|
|
291
|
+
rows[str(path)] = _path_digest(path)
|
|
292
|
+
return rows
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _path_digest(path: Path) -> str:
|
|
296
|
+
if path.is_symlink():
|
|
297
|
+
value = b"symlink:" + os.readlink(path).encode("utf-8")
|
|
298
|
+
else:
|
|
299
|
+
value = path.read_bytes()
|
|
300
|
+
return "sha256:" + hashlib.sha256(value).hexdigest()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _excluded(path: Path, generated: frozenset[str]) -> bool:
|
|
304
|
+
relative = path.as_posix()
|
|
305
|
+
return any(relative == item or relative.startswith(item + "/") for item in generated)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _generated_paths(policies: Sequence[WritePolicy]) -> frozenset[str]:
|
|
309
|
+
return frozenset(
|
|
310
|
+
path
|
|
311
|
+
for policy in policies
|
|
312
|
+
for path in policy.auxiliary_policy.get("generatedPaths", ())
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _git_projection(root: Path) -> dict[str, Any]:
|
|
317
|
+
probe = subprocess.run(
|
|
318
|
+
["git", "-C", str(root), "rev-parse", "--is-inside-work-tree"],
|
|
319
|
+
capture_output=True,
|
|
320
|
+
text=True,
|
|
321
|
+
check=False,
|
|
322
|
+
)
|
|
323
|
+
if probe.returncode != 0:
|
|
324
|
+
return {"available": False}
|
|
325
|
+
git_dir = Path(_git_output(root, "rev-parse", "--path-format=absolute", "--git-dir"))
|
|
326
|
+
common_dir = Path(
|
|
327
|
+
_git_output(root, "rev-parse", "--path-format=absolute", "--git-common-dir")
|
|
328
|
+
)
|
|
329
|
+
branch_ref = _git_output(root, "symbolic-ref", "-q", "HEAD")
|
|
330
|
+
head = _git_output(root, "rev-parse", "HEAD")
|
|
331
|
+
index_path = Path(_git_output(root, "rev-parse", "--path-format=absolute", "--git-path", "index"))
|
|
332
|
+
return {
|
|
333
|
+
"available": True,
|
|
334
|
+
"head": head,
|
|
335
|
+
"allowedBranchRef": branch_ref,
|
|
336
|
+
"branchCommit": _git_output(root, "rev-parse", branch_ref),
|
|
337
|
+
"gitDir": str(git_dir.resolve()),
|
|
338
|
+
"gitCommonDir": str(common_dir.resolve()),
|
|
339
|
+
"indexDigest": _optional_file_digest(index_path),
|
|
340
|
+
"stagedPaths": sorted(_git_changed_paths(root, "--cached")),
|
|
341
|
+
"worktreeRegistration": _worktree_registration(root),
|
|
342
|
+
"reflogDigest": _reflog_digest(root),
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _worktree_registration(root: Path) -> str:
|
|
347
|
+
output = _git_output(root, "worktree", "list", "--porcelain")
|
|
348
|
+
blocks = output.split("\n\n")
|
|
349
|
+
prefix = f"worktree {root.resolve()}\n"
|
|
350
|
+
block = next((item for item in blocks if (item + "\n").startswith(prefix)), "")
|
|
351
|
+
stable_lines = [
|
|
352
|
+
line for line in block.splitlines()
|
|
353
|
+
if not line.startswith("HEAD ")
|
|
354
|
+
]
|
|
355
|
+
return "\n".join(stable_lines)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _optional_file_digest(path: Path) -> str | None:
|
|
359
|
+
return _path_digest(path) if path.is_file() else None
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _git_output(root: Path, *args: str) -> str:
|
|
363
|
+
result = subprocess.run(
|
|
364
|
+
["git", "-C", str(root), *args],
|
|
365
|
+
capture_output=True,
|
|
366
|
+
text=True,
|
|
367
|
+
check=False,
|
|
368
|
+
)
|
|
369
|
+
if result.returncode != 0:
|
|
370
|
+
raise MutationAuditError(result.stderr.strip() or "Git projection failed")
|
|
371
|
+
return result.stdout.strip()
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _snapshot_digest(
|
|
375
|
+
root: Path,
|
|
376
|
+
artifact_root: Path,
|
|
377
|
+
policy_digests: Sequence[str],
|
|
378
|
+
files: Mapping[str, str],
|
|
379
|
+
artifacts: Mapping[str, str],
|
|
380
|
+
scratch: Mapping[str, str],
|
|
381
|
+
git_projection: Mapping[str, Any],
|
|
382
|
+
orchestrator_paths: Sequence[str],
|
|
383
|
+
) -> str:
|
|
384
|
+
encoded = json.dumps(
|
|
385
|
+
{
|
|
386
|
+
"root": str(root),
|
|
387
|
+
"artifactRoot": str(artifact_root),
|
|
388
|
+
"policyDigests": list(policy_digests),
|
|
389
|
+
"files": files,
|
|
390
|
+
"artifacts": artifacts,
|
|
391
|
+
"scratch": scratch,
|
|
392
|
+
"gitProjection": git_projection,
|
|
393
|
+
"orchestratorPaths": list(orchestrator_paths),
|
|
394
|
+
},
|
|
395
|
+
sort_keys=True,
|
|
396
|
+
separators=(",", ":"),
|
|
397
|
+
).encode("utf-8")
|
|
398
|
+
return "sha256:" + hashlib.sha256(encoded).hexdigest()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _changed_keys(before: Mapping[str, str], after: Mapping[str, str]) -> set[str]:
|
|
402
|
+
return {
|
|
403
|
+
key for key in set(before) | set(after) if before.get(key) != after.get(key)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _source_changes(
|
|
408
|
+
changed: set[str],
|
|
409
|
+
policies: Sequence[WritePolicy],
|
|
410
|
+
snapshot: MutationSnapshot,
|
|
411
|
+
) -> set[str]:
|
|
412
|
+
artifacts = _allowed_artifact_paths(policies)
|
|
413
|
+
generated = _generated_paths(policies)
|
|
414
|
+
orchestrator = {
|
|
415
|
+
Path(value).relative_to(snapshot.root).as_posix()
|
|
416
|
+
for value in snapshot.orchestrator_paths
|
|
417
|
+
if _is_relative_to(Path(value), snapshot.root)
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
path for path in changed
|
|
421
|
+
if not (
|
|
422
|
+
snapshot.artifact_root == snapshot.root
|
|
423
|
+
and any(_is_within(path, item) for item in artifacts)
|
|
424
|
+
)
|
|
425
|
+
and path not in orchestrator
|
|
426
|
+
and not _excluded(Path(path), generated)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _artifact_policy_failures(
|
|
431
|
+
snapshot: MutationSnapshot,
|
|
432
|
+
policies: Sequence[WritePolicy],
|
|
433
|
+
changed: set[str],
|
|
434
|
+
) -> list[str]:
|
|
435
|
+
allowed = _allowed_artifact_paths(policies)
|
|
436
|
+
orchestrator = {
|
|
437
|
+
Path(value).relative_to(snapshot.artifact_root).as_posix()
|
|
438
|
+
for value in snapshot.orchestrator_paths
|
|
439
|
+
if _is_relative_to(Path(value), snapshot.artifact_root)
|
|
440
|
+
}
|
|
441
|
+
unauthorized = {
|
|
442
|
+
path for path in changed
|
|
443
|
+
if not any(_is_within(path, item) for item in allowed)
|
|
444
|
+
and path not in orchestrator
|
|
445
|
+
}
|
|
446
|
+
if snapshot.artifact_root == snapshot.root:
|
|
447
|
+
return []
|
|
448
|
+
return ["artifact-root change exceeds batch policy union"] if unauthorized else []
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _allowed_artifact_paths(policies: Sequence[WritePolicy]) -> set[str]:
|
|
452
|
+
return {
|
|
453
|
+
path
|
|
454
|
+
for policy in policies
|
|
455
|
+
for path in policy.artifact_policy.get("allowedPaths", ())
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _is_relative_to(path: Path, root: Path) -> bool:
|
|
460
|
+
try:
|
|
461
|
+
path.relative_to(root)
|
|
462
|
+
except ValueError:
|
|
463
|
+
return False
|
|
464
|
+
return True
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _policy_violations(
|
|
468
|
+
before: MutationSnapshot,
|
|
469
|
+
after: MutationSnapshot,
|
|
470
|
+
policies: tuple[WritePolicy, ...],
|
|
471
|
+
source_changes: set[str],
|
|
472
|
+
out_of_plan_edits: Sequence[str],
|
|
473
|
+
) -> list[str]:
|
|
474
|
+
if all(policy.source_mode == "source-readonly" for policy in policies):
|
|
475
|
+
failures = ["readonly source changed"] if source_changes else []
|
|
476
|
+
if before.git_projection != after.git_projection:
|
|
477
|
+
failures.append("gitPolicy disabled but Git projection changed")
|
|
478
|
+
return failures
|
|
479
|
+
policy = next(row for row in policies if row.source_mode == "project-mutation")
|
|
480
|
+
failures = _source_policy_failures(policy, source_changes, out_of_plan_edits)
|
|
481
|
+
failures.extend(_git_policy_failures(before, after, policy, source_changes, out_of_plan_edits))
|
|
482
|
+
return failures
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _source_policy_failures(
|
|
486
|
+
policy: WritePolicy,
|
|
487
|
+
changed: set[str],
|
|
488
|
+
out_of_plan_edits: Sequence[str],
|
|
489
|
+
) -> list[str]:
|
|
490
|
+
planned = set(policy.source_policy.get("plannedPaths", ()))
|
|
491
|
+
declared = set(out_of_plan_edits)
|
|
492
|
+
protected = set(policy.source_policy.get("protectedPaths", ()))
|
|
493
|
+
failures: list[str] = []
|
|
494
|
+
if not changed <= planned | declared:
|
|
495
|
+
failures.append("source changes exceed planned and declared out-of-plan paths")
|
|
496
|
+
if not declared <= changed:
|
|
497
|
+
failures.append("declared out-of-plan path did not change")
|
|
498
|
+
if any(_is_within(path, item) for path in changed for item in protected):
|
|
499
|
+
failures.append("protected path changed")
|
|
500
|
+
return failures
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _git_policy_failures(
|
|
504
|
+
before: MutationSnapshot,
|
|
505
|
+
after: MutationSnapshot,
|
|
506
|
+
policy: WritePolicy,
|
|
507
|
+
changed: set[str],
|
|
508
|
+
out_of_plan_edits: Sequence[str],
|
|
509
|
+
) -> list[str]:
|
|
510
|
+
git = policy.git_policy
|
|
511
|
+
root = before.root
|
|
512
|
+
failures: list[str] = []
|
|
513
|
+
if before.git_projection.get("head") != git.get("expectedBaseCommit"):
|
|
514
|
+
failures.append("audit baseline does not match expectedBaseCommit")
|
|
515
|
+
if after.git_projection.get("allowedBranchRef") != git.get("allowedBranchRef"):
|
|
516
|
+
failures.append("assigned branch changed")
|
|
517
|
+
if after.git_projection.get("gitDir") != git.get("worktreeGitDir"):
|
|
518
|
+
failures.append("assigned worktree Git directory changed")
|
|
519
|
+
if after.git_projection.get("gitCommonDir") != git.get("gitCommonDir"):
|
|
520
|
+
failures.append("assigned Git common directory changed")
|
|
521
|
+
if before.git_projection.get("worktreeRegistration") != after.git_projection.get("worktreeRegistration"):
|
|
522
|
+
failures.append("worktree registration changed")
|
|
523
|
+
if after.git_projection.get("stagedPaths"):
|
|
524
|
+
failures.append("final Git index contains staged changes")
|
|
525
|
+
if not _is_ancestor(root, str(git.get("expectedBaseCommit")), str(after.git_projection.get("head"))):
|
|
526
|
+
failures.append("final HEAD is not a fast-forward descendant")
|
|
527
|
+
allowed = set(policy.source_policy.get("plannedPaths", ())) | set(out_of_plan_edits)
|
|
528
|
+
if any(
|
|
529
|
+
not paths <= allowed
|
|
530
|
+
for paths in _commit_paths_by_commit(
|
|
531
|
+
root,
|
|
532
|
+
str(git.get("expectedBaseCommit")),
|
|
533
|
+
str(after.git_projection.get("head")),
|
|
534
|
+
)
|
|
535
|
+
):
|
|
536
|
+
failures.append("commit chain changed paths outside source policy")
|
|
537
|
+
if not changed <= allowed:
|
|
538
|
+
failures.append("working tree changed paths outside source policy")
|
|
539
|
+
return failures
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _is_ancestor(root: Path, base: str, head: str) -> bool:
|
|
543
|
+
result = subprocess.run(
|
|
544
|
+
["git", "-C", str(root), "merge-base", "--is-ancestor", base, head],
|
|
545
|
+
capture_output=True,
|
|
546
|
+
check=False,
|
|
547
|
+
)
|
|
548
|
+
return result.returncode == 0
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _commit_paths_by_commit(root: Path, base: str, head: str) -> tuple[set[str], ...]:
|
|
552
|
+
if base == head:
|
|
553
|
+
return ()
|
|
554
|
+
commits = _git_output(root, "rev-list", "--reverse", f"{base}..{head}").splitlines()
|
|
555
|
+
return tuple(
|
|
556
|
+
{
|
|
557
|
+
line
|
|
558
|
+
for line in _git_output(
|
|
559
|
+
root, "diff-tree", "--no-commit-id", "--name-only", "-r", commit
|
|
560
|
+
).splitlines()
|
|
561
|
+
if line
|
|
562
|
+
}
|
|
563
|
+
for commit in commits
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _reflog_digest(root: Path) -> str:
|
|
568
|
+
payload = _git_output(root, "reflog", "show", "--no-abbrev", "--format=%H %gs", "HEAD")
|
|
569
|
+
return "sha256:" + hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _git_changed_paths(root: Path, *args: str) -> set[str]:
|
|
573
|
+
result = subprocess.run(
|
|
574
|
+
["git", "-C", str(root), "diff", *args, "--name-only"],
|
|
575
|
+
capture_output=True,
|
|
576
|
+
text=True,
|
|
577
|
+
check=False,
|
|
578
|
+
)
|
|
579
|
+
if result.returncode != 0:
|
|
580
|
+
raise MutationAuditError(result.stderr.strip() or "Git diff failed")
|
|
581
|
+
return {line for line in result.stdout.splitlines() if line}
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _is_within(path: str, parent: str) -> bool:
|
|
585
|
+
return path == parent or path.startswith(parent + "/")
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _terminal_status(
|
|
589
|
+
policies: Sequence[WritePolicy],
|
|
590
|
+
*,
|
|
591
|
+
source_changed: bool,
|
|
592
|
+
git_changed: bool,
|
|
593
|
+
attempt_succeeded: bool,
|
|
594
|
+
result_present: bool,
|
|
595
|
+
violations: Sequence[str],
|
|
596
|
+
) -> str:
|
|
597
|
+
project_mutation = any(row.source_mode == "project-mutation" for row in policies)
|
|
598
|
+
if project_mutation and (source_changed or git_changed) and (
|
|
599
|
+
not attempt_succeeded or not result_present
|
|
600
|
+
):
|
|
601
|
+
return "mutation-present-unresolved"
|
|
602
|
+
if violations:
|
|
603
|
+
return "contract-failed-unattributed"
|
|
604
|
+
if not attempt_succeeded or not result_present:
|
|
605
|
+
return "failed-no-mutation"
|
|
606
|
+
return "ok"
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _retry_allowed(
|
|
610
|
+
status: str,
|
|
611
|
+
source_changed: bool,
|
|
612
|
+
git_changed: bool,
|
|
613
|
+
scratch_changed: bool,
|
|
614
|
+
) -> bool:
|
|
615
|
+
if status in {"mutation-present-unresolved", "contract-failed-unattributed"}:
|
|
616
|
+
return False
|
|
617
|
+
if source_changed or git_changed:
|
|
618
|
+
return False
|
|
619
|
+
return status == "failed-no-mutation" and not scratch_changed
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Mini JSON Schema validator for the final-report data.json.
|
|
2
2
|
|
|
3
3
|
This is a deliberately narrow JSON Schema implementation that supports
|
|
4
|
-
exactly the keywords used by ``schemas/final-report-
|
|
4
|
+
exactly the keywords used by ``schemas/final-report-v2.0.schema.json``:
|
|
5
5
|
|
|
6
6
|
type, required, properties, additionalProperties, enum, const, pattern,
|
|
7
7
|
minLength, minItems, maxItems, uniqueItems, items, minimum, maximum, $ref ($defs),
|
|
@@ -23,6 +23,8 @@ import re
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import Any
|
|
25
25
|
|
|
26
|
+
from okstra_ctl.report_contract import CURRENT_REPORT_SCHEMA_VERSION
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
class SchemaError(ValueError):
|
|
28
30
|
"""Raised when a schema itself is malformed (e.g. a $ref points
|
|
@@ -260,7 +262,6 @@ def validate(data: Any, schema: dict) -> list[str]:
|
|
|
260
262
|
|
|
261
263
|
|
|
262
264
|
SCHEMA_FILENAMES = {
|
|
263
|
-
"1.0": "final-report-v1.0.schema.json",
|
|
264
265
|
"2.0": "final-report-v2.0.schema.json",
|
|
265
266
|
}
|
|
266
267
|
|
|
@@ -295,9 +296,9 @@ def load_schema_for_data(data: dict, start: Path | None = None) -> dict:
|
|
|
295
296
|
|
|
296
297
|
def load_schema(schema_path: Path | None = None) -> dict:
|
|
297
298
|
"""Load the final-report schema. If ``schema_path`` is None, locate
|
|
298
|
-
``schemas/final-report-
|
|
299
|
+
``schemas/final-report-v2.0.schema.json`` relative to this file's
|
|
299
300
|
repo root.
|
|
300
301
|
"""
|
|
301
302
|
if schema_path is not None:
|
|
302
303
|
return json.loads(Path(schema_path).read_text(encoding="utf-8"))
|
|
303
|
-
return load_schema_version(
|
|
304
|
+
return load_schema_version(CURRENT_REPORT_SCHEMA_VERSION)
|
|
@@ -39,7 +39,9 @@ def record_start(home: Path, *, project_id: str, project_root: str,
|
|
|
39
39
|
okstra_version: str = "",
|
|
40
40
|
brief_sha256: str = "",
|
|
41
41
|
initial_status: str = "running",
|
|
42
|
-
final_status_rel: str = ""
|
|
42
|
+
final_status_rel: str = "",
|
|
43
|
+
execution_manifest_path: str = "",
|
|
44
|
+
role_execution_refs: list | None = None) -> str:
|
|
43
45
|
"""run 시작 시점의 인덱스/invocation/meta 를 1 트랜잭션으로 기록.
|
|
44
46
|
반환값: 생성된 runId.
|
|
45
47
|
"""
|
|
@@ -54,7 +56,9 @@ def record_start(home: Path, *, project_id: str, project_root: str,
|
|
|
54
56
|
finished_at=when if is_terminal else None,
|
|
55
57
|
workers=workers, lead_model=lead_model, validation="not-run",
|
|
56
58
|
run_dir_rel=run_dir_rel, final_report_rel=final_report_rel,
|
|
57
|
-
final_status_rel=final_status_rel
|
|
59
|
+
final_status_rel=final_status_rel,
|
|
60
|
+
execution_manifest_path=execution_manifest_path,
|
|
61
|
+
role_execution_refs=role_execution_refs)
|
|
58
62
|
# okstra-ctl 가 미리 'reserving' 예약 row 를 작성했을 수 있다(P1-2). 같은
|
|
59
63
|
# runId 의 예약 row 가 있으면 update; 없으면 append.
|
|
60
64
|
existing_rows = read_jsonl(home / "active.jsonl")
|