okstra 0.174.0 → 0.176.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/{src → dist}/commands/execute/team.mjs +29 -36
- 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/{src → dist}/commands/execute/worktree-status.mjs +52 -46
- 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/{src → dist}/commands/report/finalize.mjs +25 -32
- 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 +6 -2
- package/docs/architecture.md +29 -20
- package/docs/cli.md +40 -16
- package/docs/contributor-change-matrix.md +2 -2
- package/docs/for-ai/skills/okstra-inspect.md +2 -3
- package/docs/for-ai/skills/okstra-rollup.md +1 -0
- package/docs/project-structure-overview.md +72 -62
- package/docs/task-process/README.md +12 -8
- package/docs/task-process/common-flow.md +13 -16
- package/docs/task-process/error-analysis.md +9 -10
- package/docs/task-process/final-verification.md +7 -7
- package/docs/task-process/implementation-planning.md +9 -9
- package/docs/task-process/implementation.md +6 -6
- package/docs/task-process/release-handoff.md +8 -7
- package/docs/task-process/requirements-discovery.md +8 -8
- package/package.json +11 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +2 -1
- 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 +3 -0
- package/runtime/bin/lib/okstra/interactive.sh +12 -6
- package/runtime/bin/lib/okstra/usage.sh +7 -6
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +217 -5
- package/runtime/bin/okstra-spawn-followups.py +4 -2
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/context-loader.md +1 -2
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/plan-body-verification.md +10 -3
- package/runtime/prompts/lead/report-writer.md +15 -1
- package/runtime/prompts/lead/team-contract.md +17 -13
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +0 -3
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
- package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
- package/runtime/prompts/profiles/error-analysis.md +27 -1
- package/runtime/prompts/profiles/feature-analysis.md +20 -0
- package/runtime/prompts/profiles/final-verification.md +20 -1
- package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
- package/runtime/prompts/profiles/implementation-planning.md +26 -0
- package/runtime/prompts/profiles/implementation.md +19 -0
- package/runtime/prompts/profiles/improvement-discovery.md +21 -1
- package/runtime/prompts/profiles/project-analysis.md +20 -0
- package/runtime/prompts/profiles/release-handoff.md +4 -0
- package/runtime/prompts/profiles/requirements-discovery.md +27 -1
- package/runtime/prompts/wizard/prompts.ko.json +35 -0
- 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/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 +95 -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/adapters/runtime/__init__.py +1 -0
- package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
- 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/open_worker.py +29 -0
- 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/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 +1204 -155
- package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
- package/runtime/python/okstra_ctl/doctor.py +14 -0
- package/runtime/python/okstra_ctl/domain/host.py +34 -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 +18 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
- package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
- 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/implementation_outcome.py +21 -5
- 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 +578 -0
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- 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/next_phase.py +236 -0
- 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/ports/worker_runtime.py +26 -0
- package/runtime/python/okstra_ctl/recap.py +4 -1
- package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +124 -57
- package/runtime/python/okstra_ctl/render_final_report.py +19 -0
- package/runtime/python/okstra_ctl/report_contract.py +39 -0
- package/runtime/python/okstra_ctl/report_finalize.py +16 -0
- package/runtime/python/okstra_ctl/report_html/render.py +1 -0
- package/runtime/python/okstra_ctl/role_requirements.py +210 -0
- package/runtime/python/okstra_ctl/rollup.py +4 -1
- package/runtime/python/okstra_ctl/run.py +1210 -258
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
- package/runtime/python/okstra_ctl/team.py +14 -18
- package/runtime/python/okstra_ctl/time_report.py +38 -4
- 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 +1449 -66
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
- package/runtime/python/okstra_ctl/worker_request.py +7 -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/workflow.py +18 -32
- package/runtime/python/okstra_ctl/worktree.py +3 -3
- package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
- package/runtime/python/okstra_ctl/write_policy.py +571 -0
- package/runtime/python/okstra_project/state.py +54 -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 +68 -5
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +15 -13
- package/runtime/skills/okstra-rollup/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +14 -14
- package/runtime/templates/implementation-worker-preamble.md +3 -18
- package/runtime/templates/project-docs/task-index.template.md +0 -1
- 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/html/macros/forms.html +5 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
- package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
- package/runtime/templates/worker-prompt-preamble.md +6 -35
- package/runtime/validators/lib/fixtures.sh +49 -16
- package/runtime/validators/validate-run.py +486 -134
- package/runtime/validators/validate_session_conformance.py +25 -7
- package/src/cli-registry.mjs +0 -539
- 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/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 -1170
- 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/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
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
14
|
import re
|
|
15
|
-
from collections.abc import Mapping
|
|
15
|
+
from collections.abc import Mapping
|
|
16
16
|
from dataclasses import dataclass
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
@@ -45,25 +45,26 @@ _EVIDENCE_COMMAND_RE = re.compile(
|
|
|
45
45
|
r"^- Evidence command:[ \t]*(?P<payload>.*)$", re.MULTILINE
|
|
46
46
|
)
|
|
47
47
|
_EVIDENCE_COMMAND_FIELDS = ("command", "cwd", "exitCode", "outputSummary")
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
r"(?i)(authorization\s*:\s*\S+|"
|
|
55
|
-
r"\b(?:[A-Za-z_][A-Za-z0-9_-]*)?(?:auth|credential|pass(?:wd|word)?|pwd|"
|
|
56
|
-
r"token|secret|api[_-]?key|access[_-]?key)[A-Za-z0-9_-]*\s*=|"
|
|
57
|
-
r"--[\w-]*(?:auth|credential|pass(?:wd|word)?|pwd|token|secret|"
|
|
58
|
-
r"api[-_]?key|access[-_]?key)[\w-]*(?:[ =])\S+|"
|
|
59
|
-
r"\w+://[^\s:/@]+:[^\s@/]+@)"
|
|
60
|
-
)
|
|
61
|
-
_KNOWN_SECRET_RE = re.compile(
|
|
62
|
-
r"(?:AKIA|ASIA)[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9_]{8,}|"
|
|
63
|
-
r"github_pat_[A-Za-z0-9_]{8,}|xox[abprs]-[A-Za-z0-9-]{8,}|"
|
|
64
|
-
r"\bsk-[A-Za-z0-9_-]{8,}\b|"
|
|
65
|
-
r"\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b"
|
|
48
|
+
_EVIDENCE_COMMAND_IDENTITY_FIELDS = (
|
|
49
|
+
"participantRef",
|
|
50
|
+
"roleExecutionRef",
|
|
51
|
+
"invocationRef",
|
|
52
|
+
"attempt",
|
|
53
|
+
"executionLabel",
|
|
66
54
|
)
|
|
55
|
+
_EVIDENCE_COMMAND_STRING_FIELDS = ("command", "cwd", "outputSummary")
|
|
56
|
+
# There is deliberately no secret detector here.
|
|
57
|
+
#
|
|
58
|
+
# One lived on this path and only ever cost runs. It matched the *name* half of
|
|
59
|
+
# an assignment, so `AUTH_MODE=off` and `TOKEN_TTL=60` were "sensitive material"
|
|
60
|
+
# and the row was refused; a worker that cannot record the command it ran has no
|
|
61
|
+
# way to satisfy the evidence ledger that demands it, and the run ends
|
|
62
|
+
# `contract-violated` over a switch. Judging the value half instead only moves
|
|
63
|
+
# the guesswork. What belongs in a row is the command's shape — the keys — and a
|
|
64
|
+
# value that must not be written down is the worker's to leave out or pass as a
|
|
65
|
+
# `$VAR` reference. A run does not need it: at execution time the value comes
|
|
66
|
+
# from the environment or from the person running it, and if it is absent the
|
|
67
|
+
# command fails then, where the failure is legible.
|
|
67
68
|
_FILE_LINE_CITATION_RE = re.compile(
|
|
68
69
|
r"`(?P<path>(?!https?://)[^`\n]+?):(?P<line>\d+(?:-\d+)?)`"
|
|
69
70
|
)
|
|
@@ -116,34 +117,18 @@ class EvidenceCommand:
|
|
|
116
117
|
cwd: str
|
|
117
118
|
exit_code: int
|
|
118
119
|
output_summary: str
|
|
120
|
+
identity: dict[str, object] | None = None
|
|
119
121
|
|
|
120
122
|
def to_record(self) -> dict[str, object]:
|
|
121
|
-
|
|
123
|
+
record: dict[str, object] = {
|
|
122
124
|
"command": self.command,
|
|
123
125
|
"cwd": self.cwd,
|
|
124
126
|
"exitCode": self.exit_code,
|
|
125
127
|
"outputSummary": self.output_summary,
|
|
126
128
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if isinstance(value, Mapping):
|
|
131
|
-
return any(
|
|
132
|
-
(
|
|
133
|
-
_SENSITIVE_KEY_RE.search(str(key)) is not None
|
|
134
|
-
and nested not in (None, "", False)
|
|
135
|
-
)
|
|
136
|
-
or _contains_sensitive_material(nested)
|
|
137
|
-
for key, nested in value.items()
|
|
138
|
-
)
|
|
139
|
-
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
|
140
|
-
return any(_contains_sensitive_material(item) for item in value)
|
|
141
|
-
if not isinstance(value, str):
|
|
142
|
-
return False
|
|
143
|
-
return (
|
|
144
|
-
_SENSITIVE_TEXT_RE.search(value) is not None
|
|
145
|
-
or _KNOWN_SECRET_RE.search(value) is not None
|
|
146
|
-
)
|
|
129
|
+
if self.identity:
|
|
130
|
+
record.update(self.identity)
|
|
131
|
+
return record
|
|
147
132
|
|
|
148
133
|
|
|
149
134
|
def _evidence_command_contract_failures(
|
|
@@ -151,7 +136,9 @@ def _evidence_command_contract_failures(
|
|
|
151
136
|
) -> list[str]:
|
|
152
137
|
failures: list[str] = []
|
|
153
138
|
missing = [key for key in _EVIDENCE_COMMAND_FIELDS if key not in payload]
|
|
154
|
-
unexpected = sorted(
|
|
139
|
+
unexpected = sorted(
|
|
140
|
+
set(payload) - set(_EVIDENCE_COMMAND_FIELDS) - set(_EVIDENCE_COMMAND_IDENTITY_FIELDS)
|
|
141
|
+
)
|
|
155
142
|
if missing:
|
|
156
143
|
failures.append(
|
|
157
144
|
f"Evidence command row {row_number} is missing {', '.join(missing)}"
|
|
@@ -173,21 +160,15 @@ def _evidence_command_contract_failures(
|
|
|
173
160
|
return failures
|
|
174
161
|
|
|
175
162
|
|
|
176
|
-
def _evidence_command_from_payload(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if sensitive:
|
|
180
|
-
return EvidenceCommand(
|
|
181
|
-
command="[redacted: potential secret]",
|
|
182
|
-
cwd="[redacted: potential secret]",
|
|
183
|
-
exit_code=payload["exitCode"],
|
|
184
|
-
output_summary="omitted because potential secret material was detected",
|
|
185
|
-
)
|
|
163
|
+
def _evidence_command_from_payload(payload: Mapping[str, object]) -> EvidenceCommand:
|
|
164
|
+
from okstra_ctl.execution_identity import stored_identity
|
|
165
|
+
|
|
186
166
|
return EvidenceCommand(
|
|
187
167
|
command=payload["command"],
|
|
188
168
|
cwd=payload["cwd"],
|
|
189
169
|
exit_code=payload["exitCode"],
|
|
190
170
|
output_summary=payload["outputSummary"],
|
|
171
|
+
identity=stored_identity(payload) or None,
|
|
191
172
|
)
|
|
192
173
|
|
|
193
174
|
|
|
@@ -209,16 +190,11 @@ def parse_evidence_commands(
|
|
|
209
190
|
f"Evidence command row {row_number} must be a JSON object"
|
|
210
191
|
)
|
|
211
192
|
continue
|
|
212
|
-
sensitive = _contains_sensitive_material(payload)
|
|
213
|
-
if sensitive:
|
|
214
|
-
failures.append(
|
|
215
|
-
f"Evidence command row {row_number} contains sensitive material"
|
|
216
|
-
)
|
|
217
193
|
contract_failures = _evidence_command_contract_failures(payload, row_number)
|
|
218
194
|
failures.extend(contract_failures)
|
|
219
195
|
if contract_failures:
|
|
220
196
|
continue
|
|
221
|
-
commands.append(_evidence_command_from_payload(payload
|
|
197
|
+
commands.append(_evidence_command_from_payload(payload))
|
|
222
198
|
return tuple(commands), tuple(failures)
|
|
223
199
|
|
|
224
200
|
|
|
@@ -375,7 +351,26 @@ def check_worker_results_audit(
|
|
|
375
351
|
*,
|
|
376
352
|
worker: str | None = None,
|
|
377
353
|
) -> list[str]:
|
|
378
|
-
"""Every audit-sidecar contract
|
|
354
|
+
"""Every audit-sidecar contract finding among this run's worker results.
|
|
355
|
+
|
|
356
|
+
Blocking findings first, then the advisory citation-ledger ones. Mid-run
|
|
357
|
+
callers want both in one list: the worker session is still alive and can fix
|
|
358
|
+
either. Phase 7 separates them — see `worker_results_audit_findings`.
|
|
359
|
+
"""
|
|
360
|
+
blocking, advisories = worker_results_audit_findings(
|
|
361
|
+
run_dir, task_type, seq, worker=worker
|
|
362
|
+
)
|
|
363
|
+
return blocking + advisories
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def worker_results_audit_findings(
|
|
367
|
+
run_dir: Path,
|
|
368
|
+
task_type: str,
|
|
369
|
+
seq: str | None,
|
|
370
|
+
*,
|
|
371
|
+
worker: str | None = None,
|
|
372
|
+
) -> tuple[list[str], list[str]]:
|
|
373
|
+
"""The same checks, split into `(blocking, advisory)`.
|
|
379
374
|
|
|
380
375
|
*run_dir* is `runs/<task-type>/`; `worker-results/` and `prompts/` hang off
|
|
381
376
|
it. *seq* scopes the check to one run — `worker-results/` accumulates every
|
|
@@ -383,16 +378,24 @@ def check_worker_results_audit(
|
|
|
383
378
|
did not produce. Pass ``None`` only when the seq is genuinely unknown, which
|
|
384
379
|
falls back to not filtering rather than silently checking nothing.
|
|
385
380
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
381
|
+
Blocking: a result file that still carries the `## 0. Reading Confirmation`
|
|
382
|
+
heading (that block moved to the sidecar), a missing sidecar, and a
|
|
383
|
+
malformed Evidence command row. Each of those means the audit trail itself
|
|
384
|
+
is absent or unreadable.
|
|
385
|
+
|
|
386
|
+
Advisory: a `path:line` citation with no matching Evidence read row. The
|
|
387
|
+
ledger exists and is readable; what the check found is a citation whose
|
|
388
|
+
spelling does not match a row. That is worth reporting and not worth
|
|
389
|
+
voiding a finished run over — one unmatched citation among hundreds used to
|
|
390
|
+
take the whole phase to `contract-violated`, which stops the run without
|
|
391
|
+
telling anyone whether the finding itself was wrong.
|
|
390
392
|
"""
|
|
391
393
|
failures: list[str] = []
|
|
394
|
+
advisories: list[str] = []
|
|
392
395
|
if not (run_dir / "worker-results").is_dir():
|
|
393
396
|
# No worker-results directory means no analysis workers ran (e.g.
|
|
394
397
|
# `release-handoff`, which is single-lead). Nothing to enforce.
|
|
395
|
-
return failures
|
|
398
|
+
return failures, advisories
|
|
396
399
|
|
|
397
400
|
for path, worker_role, result_seq in worker_result_files(run_dir, task_type, seq, worker):
|
|
398
401
|
rel = path.name
|
|
@@ -426,7 +429,7 @@ def check_worker_results_audit(
|
|
|
426
429
|
|
|
427
430
|
_, command_failures = read_evidence_commands(audit_path)
|
|
428
431
|
failures.extend(command_failures)
|
|
429
|
-
|
|
432
|
+
advisories.extend(_evidence_read_ledger_failures(
|
|
430
433
|
run_dir=run_dir,
|
|
431
434
|
worker_role=worker_role,
|
|
432
435
|
task_type=task_type,
|
|
@@ -435,4 +438,4 @@ def check_worker_results_audit(
|
|
|
435
438
|
result_content=content,
|
|
436
439
|
audit_path=audit_path,
|
|
437
440
|
))
|
|
438
|
-
return failures
|
|
441
|
+
return failures, advisories
|
|
@@ -6,11 +6,27 @@ from typing import Any, Mapping, Sequence
|
|
|
6
6
|
from .worker_prompt_policy import PromptPlan
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
_ANALYSIS_WORKER_LABELS = {
|
|
10
10
|
"claude": "Claude worker",
|
|
11
11
|
"codex": "Codex worker",
|
|
12
12
|
"antigravity": "Antigravity worker",
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def analysis_worker_label(worker_id: str) -> str:
|
|
17
|
+
"""The role label this worker's prompt body is titled with.
|
|
18
|
+
|
|
19
|
+
The map above only supplies display capitalization for the three providers
|
|
20
|
+
that predate it; every other worker id (`grok`, `kimi`, a user-installed
|
|
21
|
+
adapter) takes the id itself. Both branches are identity deltas the equality
|
|
22
|
+
group must normalize away, so `worker_prompt_contract` erases exactly what
|
|
23
|
+
this function returns rather than restating the map — an enumeration that
|
|
24
|
+
covered only the map's keys let `# grok worker Dispatch` through and failed
|
|
25
|
+
every roster carrying grok or kimi before publication.
|
|
26
|
+
"""
|
|
27
|
+
return _ANALYSIS_WORKER_LABELS.get(worker_id, f"{worker_id} worker")
|
|
28
|
+
|
|
29
|
+
|
|
14
30
|
def analysis_prompt_body(
|
|
15
31
|
manifest: Mapping[str, Any],
|
|
16
32
|
active_context: Mapping[str, Any],
|
|
@@ -19,7 +35,7 @@ def analysis_prompt_body(
|
|
|
19
35
|
plan: PromptPlan,
|
|
20
36
|
) -> list[str]:
|
|
21
37
|
"""Render the provider-neutral body for an initial analysis audience."""
|
|
22
|
-
label =
|
|
38
|
+
label = analysis_worker_label(worker_id)
|
|
23
39
|
pane_role = {
|
|
24
40
|
"implementation-executor": "executor",
|
|
25
41
|
"implementation-verifier": "verifier",
|
|
@@ -5,8 +5,9 @@ import json
|
|
|
5
5
|
import re
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Mapping, Sequence
|
|
8
|
+
from typing import Any, Iterable, Mapping, Sequence
|
|
9
9
|
|
|
10
|
+
from .worker_prompt_body import analysis_worker_label
|
|
10
11
|
from .worker_prompt_policy import (
|
|
11
12
|
ERRORS_PATH_HEADERS,
|
|
12
13
|
IMPLEMENTATION_HEADERS,
|
|
@@ -88,10 +89,7 @@ _WORKER_SPECIFIC_PREFIXES = (
|
|
|
88
89
|
"**Host runtime:**",
|
|
89
90
|
"**Host model value:**",
|
|
90
91
|
)
|
|
91
|
-
|
|
92
|
-
r"\b(?:Claude|Codex|Antigravity) worker\b",
|
|
93
|
-
re.IGNORECASE,
|
|
94
|
-
)
|
|
92
|
+
_WORKER_LABEL_SUBSTITUTE = "<analysis-worker>"
|
|
95
93
|
|
|
96
94
|
|
|
97
95
|
@dataclass(frozen=True)
|
|
@@ -156,8 +154,38 @@ def validate_final_verification_initial_prompt(text: str) -> list[str]:
|
|
|
156
154
|
return errors
|
|
157
155
|
|
|
158
156
|
|
|
159
|
-
def
|
|
160
|
-
"""
|
|
157
|
+
def _worker_label_pattern(worker_ids: Iterable[str]) -> re.Pattern[str] | None:
|
|
158
|
+
"""Match the role label the body renderer titled each compared worker with.
|
|
159
|
+
|
|
160
|
+
Built from `analysis_worker_label`, the same function that writes the label,
|
|
161
|
+
so a provider outside its display map (`grok`, `kimi`) is covered as it
|
|
162
|
+
comes. Restating the map here is what forked the roster: the enumeration
|
|
163
|
+
named only Claude / Codex / Antigravity, `# grok worker Dispatch` survived
|
|
164
|
+
normalization, and every run rostering grok or kimi failed the equality group
|
|
165
|
+
before publication with no prompt defect to fix.
|
|
166
|
+
"""
|
|
167
|
+
labels = sorted(
|
|
168
|
+
{
|
|
169
|
+
analysis_worker_label(worker_id.strip())
|
|
170
|
+
for worker_id in worker_ids
|
|
171
|
+
if worker_id.strip()
|
|
172
|
+
},
|
|
173
|
+
key=lambda label: (-len(label), label),
|
|
174
|
+
)
|
|
175
|
+
if not labels:
|
|
176
|
+
return None
|
|
177
|
+
alternation = "|".join(re.escape(label) for label in labels)
|
|
178
|
+
return re.compile(rf"\b(?:{alternation})\b", re.IGNORECASE)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def normalise_analysis_prompt(text: str, *, worker_ids: Iterable[str]) -> str:
|
|
182
|
+
"""Remove only permitted worker identity, model, role, and path deltas.
|
|
183
|
+
|
|
184
|
+
``worker_ids`` is every worker in the comparison group, not just this
|
|
185
|
+
prompt's own: a body that names a sibling worker must normalize to the same
|
|
186
|
+
bytes in all of them, or the mention itself reads as divergence.
|
|
187
|
+
"""
|
|
188
|
+
label = _worker_label_pattern(worker_ids)
|
|
161
189
|
normalized: list[str] = []
|
|
162
190
|
for line in text.replace("\r\n", "\n").replace("\r", "\n").splitlines():
|
|
163
191
|
stripped = line.strip()
|
|
@@ -167,7 +195,10 @@ def normalise_analysis_prompt(text: str) -> str:
|
|
|
167
195
|
)
|
|
168
196
|
if prefix:
|
|
169
197
|
continue
|
|
170
|
-
|
|
198
|
+
line = line.rstrip()
|
|
199
|
+
normalized.append(
|
|
200
|
+
line if label is None else label.sub(_WORKER_LABEL_SUBSTITUTE, line)
|
|
201
|
+
)
|
|
171
202
|
return "\n".join(normalized).strip() + "\n"
|
|
172
203
|
|
|
173
204
|
|
|
@@ -176,7 +207,7 @@ def validate_analysis_prompt_set(prompts: Mapping[str, str]) -> list[str]:
|
|
|
176
207
|
if len(prompts) < 2:
|
|
177
208
|
return []
|
|
178
209
|
normalized = {
|
|
179
|
-
worker_id: normalise_analysis_prompt(text)
|
|
210
|
+
worker_id: normalise_analysis_prompt(text, worker_ids=prompts.keys())
|
|
180
211
|
for worker_id, text in sorted(prompts.items())
|
|
181
212
|
}
|
|
182
213
|
baseline_worker = next(iter(normalized))
|
|
@@ -54,6 +54,17 @@ EVIDENCE_CITATION_HEADER = (
|
|
|
54
54
|
"ledger row and fails exactly like a file you never opened, however many "
|
|
55
55
|
"times you cited the full path earlier."
|
|
56
56
|
)
|
|
57
|
+
# 파일 인용과 같이 첫 결론 명령 앞에 형식을 둔다. 분석 프리앰블에만 있으면
|
|
58
|
+
# 구현 워커는 명령 행을 쓰지 못한다.
|
|
59
|
+
EVIDENCE_COMMANDS_HEADER = (
|
|
60
|
+
"**Evidence commands:** Append one `- Evidence command: "
|
|
61
|
+
"{\"command\":\"<exact command>\",\"cwd\":\"<project-root>\","
|
|
62
|
+
"\"exitCode\":0,\"outputSummary\":\"<one-line result>\"}` row to the audit "
|
|
63
|
+
"sidecar for each command that produced or verified a conclusion. Do not "
|
|
64
|
+
"record exploratory `rg`, `ls`, or file-opening commands. Write the command "
|
|
65
|
+
"as you ran it. A value that must not be written down is yours to omit or "
|
|
66
|
+
"pass as a `$VAR` reference."
|
|
67
|
+
)
|
|
57
68
|
|
|
58
69
|
# `agy`'s write tool validates the target against the Gemini artifact store
|
|
59
70
|
# whenever the model attaches ArtifactMetadata, and rejects every path outside
|
|
@@ -120,7 +131,11 @@ def worker_prompt_headers(
|
|
|
120
131
|
f"**Coding preflight pack:** {_coding_preflight_pack_path(active_context)}"
|
|
121
132
|
)
|
|
122
133
|
if dispatch_kind == "initial" and plan.audience != "report-writer":
|
|
123
|
-
headers += [
|
|
134
|
+
headers += [
|
|
135
|
+
EVIDENCE_LEDGER_HEADER,
|
|
136
|
+
EVIDENCE_CITATION_HEADER,
|
|
137
|
+
EVIDENCE_COMMANDS_HEADER,
|
|
138
|
+
]
|
|
124
139
|
headers.extend([
|
|
125
140
|
f"**Errors log path:** {errors_log_path}",
|
|
126
141
|
f"**Errors sidecar path:** {errors_sidecar_path}",
|
|
@@ -14,6 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
|
|
15
15
|
from .domain.worker_exec import ExecutionPolicy, WorkerExecRequest
|
|
16
16
|
from .domain.worker_role import role_spec
|
|
17
|
+
from .write_policy import WriteEnforcement, WritePolicy
|
|
17
18
|
|
|
18
19
|
# Carried over from the codex wrapper, where it was one provider's rule: cargo's
|
|
19
20
|
# package-cache flock and its registry live outside every workspace, so a
|
|
@@ -36,6 +37,8 @@ def build_request(
|
|
|
36
37
|
role: str,
|
|
37
38
|
idle_timeout_seconds: int,
|
|
38
39
|
session_id: str = "",
|
|
40
|
+
write_policy: WritePolicy | None = None,
|
|
41
|
+
write_enforcement: WriteEnforcement | None = None,
|
|
39
42
|
) -> WorkerExecRequest:
|
|
40
43
|
"""One dispatch, with every value the strategies will not re-derive.
|
|
41
44
|
|
|
@@ -53,7 +56,10 @@ def build_request(
|
|
|
53
56
|
# Nobody is at the keyboard to answer an approval prompt, so the gate is
|
|
54
57
|
# open and the boundary comes from the write scope instead.
|
|
55
58
|
policy=ExecutionPolicy(
|
|
56
|
-
auto_approve=True,
|
|
59
|
+
auto_approve=True,
|
|
60
|
+
write_scope=write_scope(root, worktree, role),
|
|
61
|
+
write_policy=write_policy,
|
|
62
|
+
write_enforcement=write_enforcement,
|
|
57
63
|
),
|
|
58
64
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
59
65
|
session_id=session_id,
|
|
@@ -21,7 +21,9 @@ from functools import partial
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any, Callable, Mapping
|
|
23
23
|
|
|
24
|
+
from .domain.provider import ServedModelAttestation, ServedModelNormalizer
|
|
24
25
|
from .domain.worker_exec import (
|
|
26
|
+
SERVED_MODEL_MISMATCH_EXIT_CODE,
|
|
25
27
|
STREAM_JSON,
|
|
26
28
|
ExecCommand,
|
|
27
29
|
ExecutionStrategy,
|
|
@@ -67,7 +69,11 @@ def run_worker(
|
|
|
67
69
|
log_path: Path,
|
|
68
70
|
status_path: Path,
|
|
69
71
|
status_extra: Mapping[str, Any] = _NO_STATUS_EXTRA,
|
|
72
|
+
served_model_normalizer: ServedModelNormalizer | None = None,
|
|
73
|
+
served_model_verifier: Callable[[ServedModelAttestation], str] | None = None,
|
|
70
74
|
) -> int:
|
|
75
|
+
_validate_status_extra(status_extra)
|
|
76
|
+
_validate_request_write_contract(request, status_extra)
|
|
71
77
|
command = strategy.build_command(request)
|
|
72
78
|
started_monotonic = time.monotonic()
|
|
73
79
|
status = _started_status(status_extra, log_path)
|
|
@@ -76,7 +82,7 @@ def run_worker(
|
|
|
76
82
|
|
|
77
83
|
try:
|
|
78
84
|
with guard:
|
|
79
|
-
exit_code, timed_out, idle_seconds = _launch(
|
|
85
|
+
exit_code, timed_out, idle_seconds, raw_model = _launch(
|
|
80
86
|
command,
|
|
81
87
|
log_path,
|
|
82
88
|
presentation=presentation,
|
|
@@ -94,6 +100,14 @@ def run_worker(
|
|
|
94
100
|
raise
|
|
95
101
|
|
|
96
102
|
status = _closed(status, started_monotonic)
|
|
103
|
+
if served_model_normalizer is not None:
|
|
104
|
+
attestation = served_model_normalizer(raw_model)
|
|
105
|
+
status["servedModelAttestation"] = _attestation_payload(attestation)
|
|
106
|
+
if served_model_verifier is not None:
|
|
107
|
+
failure = served_model_verifier(attestation)
|
|
108
|
+
if failure:
|
|
109
|
+
status["failure"] = failure
|
|
110
|
+
exit_code = SERVED_MODEL_MISMATCH_EXIT_CODE
|
|
97
111
|
status["exit_code"] = exit_code
|
|
98
112
|
if timed_out:
|
|
99
113
|
status.update(
|
|
@@ -106,6 +120,57 @@ def run_worker(
|
|
|
106
120
|
return exit_code
|
|
107
121
|
|
|
108
122
|
|
|
123
|
+
def _validate_status_extra(status_extra: Mapping[str, Any]) -> None:
|
|
124
|
+
schema = status_extra.get("schemaVersion")
|
|
125
|
+
identity_version = status_extra.get("executionIdentityVersion")
|
|
126
|
+
identity_keys = {
|
|
127
|
+
"participantRef",
|
|
128
|
+
"roleExecutionRef",
|
|
129
|
+
"executionLabel",
|
|
130
|
+
"dutyId",
|
|
131
|
+
"invocationRef",
|
|
132
|
+
}
|
|
133
|
+
if schema is None and identity_version is None:
|
|
134
|
+
if set(status_extra) & (
|
|
135
|
+
identity_keys | {"attempt", "servedModelAttestation"}
|
|
136
|
+
):
|
|
137
|
+
raise ValueError("worker status mixes v1 and v2 execution identity")
|
|
138
|
+
return
|
|
139
|
+
if schema != "2.0" or identity_version != 2:
|
|
140
|
+
raise ValueError("worker status mixes v1 and v2 execution identity")
|
|
141
|
+
if any(
|
|
142
|
+
not isinstance(status_extra.get(key), str) or not status_extra[key]
|
|
143
|
+
for key in identity_keys
|
|
144
|
+
):
|
|
145
|
+
raise ValueError("worker status v2 execution identity is incomplete")
|
|
146
|
+
attempt = status_extra.get("attempt")
|
|
147
|
+
if not isinstance(attempt, int) or isinstance(attempt, bool) or attempt < 1:
|
|
148
|
+
raise ValueError("worker status v2 attempt must be positive")
|
|
149
|
+
attestation = status_extra.get("servedModelAttestation")
|
|
150
|
+
if not isinstance(attestation, Mapping) or set(attestation) != {
|
|
151
|
+
"observedModel",
|
|
152
|
+
"normalizedModelRef",
|
|
153
|
+
"level",
|
|
154
|
+
"source",
|
|
155
|
+
}:
|
|
156
|
+
raise ValueError("worker status served model attestation is invalid")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _validate_request_write_contract(
|
|
160
|
+
request: WorkerExecRequest, status_extra: Mapping[str, Any]
|
|
161
|
+
) -> None:
|
|
162
|
+
if status_extra.get("executionIdentityVersion") != 2:
|
|
163
|
+
return
|
|
164
|
+
policy = request.policy.write_policy
|
|
165
|
+
enforcement = request.policy.write_enforcement
|
|
166
|
+
if policy is None or enforcement is None:
|
|
167
|
+
raise ValueError("v2 worker request has no write contract")
|
|
168
|
+
if status_extra.get("writePolicyDigest") != policy.digest:
|
|
169
|
+
raise ValueError("v2 worker request write policy digest changed")
|
|
170
|
+
if status_extra.get("writeEnforcement") != enforcement.to_payload():
|
|
171
|
+
raise ValueError("v2 worker request write enforcement changed")
|
|
172
|
+
|
|
173
|
+
|
|
109
174
|
def _launch(
|
|
110
175
|
command: ExecCommand,
|
|
111
176
|
log_path: Path,
|
|
@@ -113,7 +178,7 @@ def _launch(
|
|
|
113
178
|
presentation: str,
|
|
114
179
|
idle_timeout_seconds: int,
|
|
115
180
|
on_spawn: Callable[[subprocess.Popen[bytes]], None],
|
|
116
|
-
) -> tuple[int, bool, int]:
|
|
181
|
+
) -> tuple[int, bool, int, str | None]:
|
|
117
182
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
118
183
|
with log_path.open("w", encoding="utf-8") as log_file:
|
|
119
184
|
# The strategy decided where this provider runs — some CLIs work in the
|
|
@@ -128,7 +193,8 @@ def _launch(
|
|
|
128
193
|
env=_child_env(),
|
|
129
194
|
)
|
|
130
195
|
on_spawn(process)
|
|
131
|
-
|
|
196
|
+
observation = _ServedModelObservation()
|
|
197
|
+
exit_code, timed_out, idle_seconds = _pump(
|
|
132
198
|
process,
|
|
133
199
|
log_file,
|
|
134
200
|
stream_format=command.stream_format,
|
|
@@ -136,7 +202,19 @@ def _launch(
|
|
|
136
202
|
presentation=presentation,
|
|
137
203
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
138
204
|
stdin_text=command.stdin_text,
|
|
205
|
+
observe_served_model=command.observe_served_model,
|
|
206
|
+
model_observation=observation,
|
|
139
207
|
)
|
|
208
|
+
return exit_code, timed_out, idle_seconds, observation.raw_model
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class _ServedModelObservation:
|
|
212
|
+
def __init__(self) -> None:
|
|
213
|
+
self.raw_model: str | None = None
|
|
214
|
+
|
|
215
|
+
def record(self, raw_model: str | None) -> None:
|
|
216
|
+
if self.raw_model is None and isinstance(raw_model, str) and raw_model.strip():
|
|
217
|
+
self.raw_model = raw_model
|
|
140
218
|
|
|
141
219
|
|
|
142
220
|
class _AbnormalExit:
|
|
@@ -253,6 +331,8 @@ def _pump(
|
|
|
253
331
|
presentation: str,
|
|
254
332
|
idle_timeout_seconds: int,
|
|
255
333
|
stdin_text: str | None = None,
|
|
334
|
+
observe_served_model: Callable[[Mapping[str, Any]], str | None],
|
|
335
|
+
model_observation: _ServedModelObservation,
|
|
256
336
|
) -> tuple[int, bool, int]:
|
|
257
337
|
selector = selectors.DefaultSelector()
|
|
258
338
|
readers, finalize_log = _register_output(
|
|
@@ -261,6 +341,8 @@ def _pump(
|
|
|
261
341
|
log_file,
|
|
262
342
|
stream_format=stream_format,
|
|
263
343
|
normalise=normalise,
|
|
344
|
+
observe_served_model=observe_served_model,
|
|
345
|
+
model_observation=model_observation,
|
|
264
346
|
presentation=presentation,
|
|
265
347
|
)
|
|
266
348
|
outgoing = _register_prompt(selector, process, stdin_text)
|
|
@@ -357,6 +439,8 @@ def _register_output(
|
|
|
357
439
|
*,
|
|
358
440
|
stream_format: str,
|
|
359
441
|
normalise: Normalise,
|
|
442
|
+
observe_served_model: Callable[[Mapping[str, Any]], str | None],
|
|
443
|
+
model_observation: _ServedModelObservation,
|
|
360
444
|
presentation: str,
|
|
361
445
|
) -> tuple[dict[int, _LineReader], Callable[[], None]]:
|
|
362
446
|
"""Register every stream this child speaks, each with its own destination.
|
|
@@ -367,7 +451,17 @@ def _register_output(
|
|
|
367
451
|
assert process.stdout is not None
|
|
368
452
|
if stream_format == STREAM_JSON:
|
|
369
453
|
sinks = [
|
|
370
|
-
(
|
|
454
|
+
(
|
|
455
|
+
process.stdout,
|
|
456
|
+
partial(
|
|
457
|
+
_emit_event,
|
|
458
|
+
log_file,
|
|
459
|
+
presentation,
|
|
460
|
+
normalise,
|
|
461
|
+
observe_served_model,
|
|
462
|
+
model_observation,
|
|
463
|
+
),
|
|
464
|
+
)
|
|
371
465
|
]
|
|
372
466
|
finalize_log: Callable[[], None] = _nothing_to_finalize
|
|
373
467
|
else:
|
|
@@ -519,7 +613,12 @@ class _ProgressSink:
|
|
|
519
613
|
|
|
520
614
|
|
|
521
615
|
def _emit_event(
|
|
522
|
-
log_file,
|
|
616
|
+
log_file,
|
|
617
|
+
presentation: str,
|
|
618
|
+
normalise: Normalise,
|
|
619
|
+
observe_served_model: Callable[[Mapping[str, Any]], str | None],
|
|
620
|
+
model_observation: _ServedModelObservation,
|
|
621
|
+
line: str,
|
|
523
622
|
) -> str | None:
|
|
524
623
|
"""Write one line of a JSON stream out; return closing text if this is it.
|
|
525
624
|
|
|
@@ -542,6 +641,7 @@ def _emit_event(
|
|
|
542
641
|
return None
|
|
543
642
|
if not isinstance(event, dict):
|
|
544
643
|
return None
|
|
644
|
+
model_observation.record(observe_served_model(event))
|
|
545
645
|
|
|
546
646
|
closing: str | None = None
|
|
547
647
|
for entry in normalise(event):
|
|
@@ -560,6 +660,15 @@ def _emit_event(
|
|
|
560
660
|
return closing
|
|
561
661
|
|
|
562
662
|
|
|
663
|
+
def _attestation_payload(attestation: ServedModelAttestation) -> dict[str, Any]:
|
|
664
|
+
return {
|
|
665
|
+
"observedModel": attestation.observed_model,
|
|
666
|
+
"normalizedModelRef": attestation.normalized_model_ref,
|
|
667
|
+
"level": attestation.level,
|
|
668
|
+
"source": attestation.source,
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
|
|
563
672
|
def _write_log(log_file, lines) -> None:
|
|
564
673
|
for line in lines:
|
|
565
674
|
log_file.write(line + "\n")
|
|
@@ -605,6 +714,22 @@ def _child_env() -> dict[str, str]:
|
|
|
605
714
|
return env
|
|
606
715
|
|
|
607
716
|
|
|
717
|
+
def write_host_event(
|
|
718
|
+
path: Path,
|
|
719
|
+
*,
|
|
720
|
+
invocation_ref: str,
|
|
721
|
+
attempt: int,
|
|
722
|
+
event_type: str,
|
|
723
|
+
payload: Mapping[str, Any],
|
|
724
|
+
) -> Mapping[str, Any]:
|
|
725
|
+
"""Append one orchestrator-owned host event from the runner boundary."""
|
|
726
|
+
from .attempt_evidence import HostEventStreamWriter
|
|
727
|
+
|
|
728
|
+
return HostEventStreamWriter(
|
|
729
|
+
path, invocation_ref=invocation_ref, attempt=attempt
|
|
730
|
+
).append(event_type, payload)
|
|
731
|
+
|
|
732
|
+
|
|
608
733
|
def _write_status(path: Path, status: Mapping[str, Any]) -> None:
|
|
609
734
|
"""Best-effort status write; a sidecar failure must not break the run.
|
|
610
735
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""V1 provider-worker roster compatibility parsing.
|
|
2
2
|
|
|
3
3
|
Profile 파일에서 권장 worker 목록을 뽑고(`resolve_profile_workers`), 사용자
|
|
4
4
|
오버라이드와 합쳐 정규화한다(`normalize_workers`). bash workers.sh 의
|
|
@@ -28,8 +28,10 @@ class WorkersError(Exception):
|
|
|
28
28
|
"""invalid worker selection — surface to user."""
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def
|
|
32
|
-
"""
|
|
31
|
+
def _resolve_v1_workers_under(profile_path: Path, headers: set[str]) -> list[str]:
|
|
32
|
+
"""Read legacy provider-worker bullets without interpreting role YAML.
|
|
33
|
+
|
|
34
|
+
Collect ` - <id> …` sub-bullets under any of `headers`
|
|
33
35
|
until the next top-level bullet. Returns first token (before any
|
|
34
36
|
` — ` / ` -- ` / whitespace) of each captured line.
|
|
35
37
|
"""
|
|
@@ -61,7 +63,7 @@ def resolve_profile_workers(profile_path: Path) -> list[str]:
|
|
|
61
63
|
`- Workers:` / `- Reviewers:` / `- Analysers:`) 섹션 아래 sub-bullet 들을
|
|
62
64
|
worker id 리스트로 돌려준다. profile 파일이 없거나 섹션이 없으면 빈 리스트.
|
|
63
65
|
"""
|
|
64
|
-
return
|
|
66
|
+
return _resolve_v1_workers_under(profile_path, PROFILE_BULLET_HEADERS)
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
def resolve_optional_workers(profile_path: Path) -> list[str]:
|
|
@@ -69,7 +71,7 @@ def resolve_optional_workers(profile_path: Path) -> list[str]:
|
|
|
69
71
|
들에서 worker id 만 추출한다. (` - antigravity — when added …` → `antigravity`)
|
|
70
72
|
Required 와 중복되는 항목은 제거. ALLOWED_WORKERS 밖 토큰도 제거.
|
|
71
73
|
"""
|
|
72
|
-
raw =
|
|
74
|
+
raw = _resolve_v1_workers_under(profile_path, PROFILE_BULLET_HEADERS_OPTIONAL)
|
|
73
75
|
required = set(resolve_profile_workers(profile_path))
|
|
74
76
|
seen: set[str] = set()
|
|
75
77
|
out: list[str] = []
|