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
|
@@ -36,6 +36,20 @@ class DoctorCheck:
|
|
|
36
36
|
return asdict(self)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def model_pool_diagnostics(
|
|
40
|
+
*,
|
|
41
|
+
host_runtime: str = "claude-code",
|
|
42
|
+
cwd: str | Path | None = None,
|
|
43
|
+
) -> dict[str, object]:
|
|
44
|
+
"""Diagnose the model pool without making an inference call."""
|
|
45
|
+
from .model_cli import model_diagnostics
|
|
46
|
+
|
|
47
|
+
return model_diagnostics(
|
|
48
|
+
host_runtime=host_runtime,
|
|
49
|
+
cwd=None if cwd is None else Path(cwd),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
39
53
|
def phase_diagnostics(
|
|
40
54
|
phase: str,
|
|
41
55
|
*,
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
"""Host selection and session value objects."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Literal, Mapping
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class CurrentSessionModelAttestation:
|
|
11
|
+
"""Host-observed identity of the already running lead session."""
|
|
12
|
+
|
|
13
|
+
provider_id: str
|
|
14
|
+
observed_model: str | None
|
|
15
|
+
normalized_model_ref: str | None
|
|
16
|
+
level: Literal["exact", "channel", "unknown"]
|
|
17
|
+
source: Literal[
|
|
18
|
+
"host-session-metadata",
|
|
19
|
+
"native-api",
|
|
20
|
+
"cli-handoff",
|
|
21
|
+
"unavailable",
|
|
22
|
+
]
|
|
23
|
+
# 호스트가 알려 줄 때만 채운다. 스크레이핑하지 않는다.
|
|
24
|
+
effort: str | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def unknown(
|
|
28
|
+
cls,
|
|
29
|
+
provider_id: str,
|
|
30
|
+
source: Literal["unavailable"] = "unavailable",
|
|
31
|
+
) -> CurrentSessionModelAttestation:
|
|
32
|
+
return cls(provider_id, None, None, "unknown", source, None)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _unavailable_current_model() -> CurrentSessionModelAttestation:
|
|
36
|
+
return CurrentSessionModelAttestation.unknown("")
|
|
37
|
+
|
|
38
|
+
|
|
9
39
|
@dataclass(frozen=True)
|
|
10
40
|
class HostDescriptor:
|
|
11
41
|
id: str
|
|
@@ -29,6 +59,9 @@ class HostSessionContext:
|
|
|
29
59
|
entry_mode: Literal["spawn-process", "current-session"]
|
|
30
60
|
available_functions: frozenset[str]
|
|
31
61
|
interaction_surface: str
|
|
62
|
+
current_model: CurrentSessionModelAttestation = field(
|
|
63
|
+
default_factory=_unavailable_current_model
|
|
64
|
+
)
|
|
32
65
|
|
|
33
66
|
|
|
34
67
|
@dataclass(frozen=True)
|
|
@@ -1,19 +1,168 @@
|
|
|
1
1
|
"""Provider and model value objects shared by the registry and adapters."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING, Literal, Mapping, Optional, Protocol
|
|
7
|
+
|
|
8
|
+
from .role import RoleCatalogError, normalize_role, ROLE_CATALOG
|
|
9
|
+
from .worker_exec import WorkerWriteCapability
|
|
6
10
|
|
|
7
11
|
if TYPE_CHECKING:
|
|
8
12
|
from okstra_ctl.domain.worker_exec import ExecutionStrategy
|
|
9
13
|
|
|
10
14
|
|
|
15
|
+
_MISSING = object()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class ServedModelAttestation:
|
|
20
|
+
"""Provider-observed model identity after one execution attempt."""
|
|
21
|
+
|
|
22
|
+
observed_model: str | None
|
|
23
|
+
normalized_model_ref: str | None
|
|
24
|
+
level: Literal["exact", "channel", "unknown"]
|
|
25
|
+
source: Literal[
|
|
26
|
+
"provider-output",
|
|
27
|
+
"native-api",
|
|
28
|
+
"cli-handoff",
|
|
29
|
+
"unavailable",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def unknown(cls) -> ServedModelAttestation:
|
|
34
|
+
return cls(None, None, "unknown", "unavailable")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
ServedModelNormalizer = Callable[[str | None], ServedModelAttestation]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def unavailable_served_model(_raw_model: str | None) -> ServedModelAttestation:
|
|
41
|
+
return ServedModelAttestation.unknown()
|
|
42
|
+
|
|
43
|
+
|
|
11
44
|
@dataclass(frozen=True)
|
|
45
|
+
class HostModelBinding:
|
|
46
|
+
"""Final provider value and host-native value for one selected model."""
|
|
47
|
+
|
|
48
|
+
runner: str
|
|
49
|
+
catalog_execution_value: str
|
|
50
|
+
resolved_execution_value: str
|
|
51
|
+
host_model_value: str | None
|
|
52
|
+
binding_fidelity: Literal["exact", "channel", "unsupported"]
|
|
53
|
+
worker_write_capability: WorkerWriteCapability | None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, init=False)
|
|
12
57
|
class ModelSpec:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
58
|
+
"""One canonical model plus provider-owned aliases and execution value."""
|
|
59
|
+
|
|
60
|
+
model_id: str
|
|
61
|
+
display_name: str
|
|
62
|
+
execution_value: str
|
|
63
|
+
aliases: tuple[str, ...]
|
|
64
|
+
version_kind: Literal["pinned", "channel"]
|
|
65
|
+
channel_family: str | None
|
|
66
|
+
supported_roles: frozenset[str] | None
|
|
67
|
+
selectable: bool
|
|
68
|
+
pricing: Optional[tuple[float, float, float]]
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
model_id: str,
|
|
73
|
+
display_name: str,
|
|
74
|
+
execution_value: str | tuple[float, float, float] | None | object = _MISSING,
|
|
75
|
+
aliases: tuple[str, ...] = (),
|
|
76
|
+
version_kind: Literal["pinned", "channel"] = "pinned",
|
|
77
|
+
channel_family: str | None = None,
|
|
78
|
+
supported_roles: frozenset[str] | None = None,
|
|
79
|
+
selectable: bool = True,
|
|
80
|
+
pricing: Optional[tuple[float, float, float]] = None,
|
|
81
|
+
*,
|
|
82
|
+
in_picker: bool | None = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Accept the former display/execution form while adapters migrate."""
|
|
85
|
+
is_legacy = execution_value is _MISSING or not isinstance(execution_value, str)
|
|
86
|
+
if is_legacy:
|
|
87
|
+
legacy_pricing = execution_value if isinstance(execution_value, tuple) else None
|
|
88
|
+
canonical_id = display_name
|
|
89
|
+
resolved_display = model_id
|
|
90
|
+
resolved_execution = display_name
|
|
91
|
+
resolved_pricing = legacy_pricing
|
|
92
|
+
else:
|
|
93
|
+
canonical_id = model_id
|
|
94
|
+
resolved_display = display_name
|
|
95
|
+
resolved_execution = execution_value
|
|
96
|
+
resolved_pricing = pricing
|
|
97
|
+
object.__setattr__(self, "model_id", canonical_id)
|
|
98
|
+
object.__setattr__(self, "display_name", resolved_display)
|
|
99
|
+
object.__setattr__(self, "execution_value", resolved_execution)
|
|
100
|
+
object.__setattr__(self, "aliases", aliases)
|
|
101
|
+
object.__setattr__(self, "version_kind", version_kind)
|
|
102
|
+
object.__setattr__(self, "channel_family", channel_family)
|
|
103
|
+
object.__setattr__(self, "supported_roles", supported_roles)
|
|
104
|
+
object.__setattr__(self, "selectable", selectable if in_picker is None else in_picker)
|
|
105
|
+
object.__setattr__(self, "pricing", resolved_pricing)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def display(self) -> str:
|
|
109
|
+
"""Legacy display-name view."""
|
|
110
|
+
return self.display_name
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def execution(self) -> str:
|
|
114
|
+
"""Legacy execution-value view."""
|
|
115
|
+
return self.execution_value
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def in_picker(self) -> bool:
|
|
119
|
+
"""Legacy picker view."""
|
|
120
|
+
return self.selectable
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dataclass(frozen=True)
|
|
124
|
+
class ExecutionNormalization:
|
|
125
|
+
"""One precomputed CLI execution value or its discovery-time error."""
|
|
126
|
+
|
|
127
|
+
resolved_execution_value: str | None
|
|
128
|
+
error: str = ""
|
|
129
|
+
|
|
130
|
+
def __post_init__(self) -> None:
|
|
131
|
+
if bool(self.resolved_execution_value) == bool(self.error):
|
|
132
|
+
raise ValueError(
|
|
133
|
+
"execution normalization requires exactly one value or error"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ExecutionNormalizer(Protocol):
|
|
138
|
+
"""Provider-owned batch snapshot boundary for ambient CLI discovery."""
|
|
139
|
+
|
|
140
|
+
def snapshot(
|
|
141
|
+
self,
|
|
142
|
+
models: tuple[ModelSpec, ...],
|
|
143
|
+
roles: tuple[str, ...],
|
|
144
|
+
) -> Mapping[tuple[str, str], ExecutionNormalization]: ...
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def snapshot_execution_normalizations(
|
|
148
|
+
models: tuple[ModelSpec, ...],
|
|
149
|
+
roles: tuple[str, ...],
|
|
150
|
+
normalize: Callable[[str, str], str],
|
|
151
|
+
) -> Mapping[tuple[str, str], ExecutionNormalization]:
|
|
152
|
+
"""Evaluate a pure provider normalizer for every model/role pair."""
|
|
153
|
+
outcomes: dict[tuple[str, str], ExecutionNormalization] = {}
|
|
154
|
+
for model in models:
|
|
155
|
+
for role in roles:
|
|
156
|
+
try:
|
|
157
|
+
resolved = normalize(model.execution_value, role)
|
|
158
|
+
except Exception as exc: # provider errors become immutable facts
|
|
159
|
+
outcomes[(model.model_id, role)] = ExecutionNormalization(
|
|
160
|
+
None,
|
|
161
|
+
str(exc),
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
outcomes[(model.model_id, role)] = ExecutionNormalization(resolved)
|
|
165
|
+
return outcomes
|
|
17
166
|
|
|
18
167
|
|
|
19
168
|
@dataclass(frozen=True)
|
|
@@ -37,8 +186,37 @@ class ProviderSpec:
|
|
|
37
186
|
default_models: Mapping[str, str]
|
|
38
187
|
wrapper: str
|
|
39
188
|
supported_roles: frozenset[str]
|
|
189
|
+
execution_capabilities: frozenset[str] = frozenset()
|
|
40
190
|
lead_launch: Optional[LeadLaunchSpec] = None
|
|
41
191
|
exec_strategy: Optional["ExecutionStrategy"] = None
|
|
192
|
+
execution_normalizer: Optional[ExecutionNormalizer] = None
|
|
193
|
+
served_model_normalizer: ServedModelNormalizer = unavailable_served_model
|
|
194
|
+
_legacy_role_restriction: frozenset[str] = field(
|
|
195
|
+
init=False,
|
|
196
|
+
repr=False,
|
|
197
|
+
compare=False,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def __post_init__(self) -> None:
|
|
201
|
+
legacy_roles = _normalized_legacy_roles(self.supported_roles)
|
|
202
|
+
if self.execution_capabilities:
|
|
203
|
+
capabilities = self.execution_capabilities
|
|
204
|
+
restriction = frozenset()
|
|
205
|
+
else:
|
|
206
|
+
capabilities = _legacy_execution_capabilities(legacy_roles)
|
|
207
|
+
restriction = legacy_roles
|
|
208
|
+
object.__setattr__(self, "execution_capabilities", capabilities)
|
|
209
|
+
object.__setattr__(self, "_legacy_role_restriction", restriction)
|
|
210
|
+
|
|
211
|
+
def supports_role(self, role: str) -> bool:
|
|
212
|
+
try:
|
|
213
|
+
canonical_role = normalize_role(role)
|
|
214
|
+
except RoleCatalogError:
|
|
215
|
+
return False
|
|
216
|
+
if self._legacy_role_restriction:
|
|
217
|
+
return canonical_role in self._legacy_role_restriction
|
|
218
|
+
required = ROLE_CATALOG.required_capabilities(canonical_role)
|
|
219
|
+
return required.issubset(self.execution_capabilities)
|
|
42
220
|
|
|
43
221
|
|
|
44
222
|
@dataclass(frozen=True)
|
|
@@ -60,6 +238,8 @@ class LeadProviderAssignment:
|
|
|
60
238
|
class ModelAssignment:
|
|
61
239
|
display: str
|
|
62
240
|
execution: str
|
|
241
|
+
model_ref: str = ""
|
|
242
|
+
version_kind: Literal["pinned", "channel"] = "pinned"
|
|
63
243
|
|
|
64
244
|
|
|
65
245
|
class UnknownModelError(ValueError):
|
|
@@ -68,3 +248,29 @@ class UnknownModelError(ValueError):
|
|
|
68
248
|
|
|
69
249
|
class UnknownProviderError(ValueError):
|
|
70
250
|
"""Raised when a requested provider is absent from the registry."""
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _normalized_legacy_roles(roles: frozenset[str]) -> frozenset[str]:
|
|
254
|
+
normalized: set[str] = set()
|
|
255
|
+
for role in roles:
|
|
256
|
+
try:
|
|
257
|
+
normalized.add(normalize_role(role))
|
|
258
|
+
except RoleCatalogError:
|
|
259
|
+
continue
|
|
260
|
+
return frozenset(normalized)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _legacy_execution_capabilities(roles: frozenset[str]) -> frozenset[str]:
|
|
264
|
+
capabilities: set[str] = set()
|
|
265
|
+
for role in roles:
|
|
266
|
+
if role == "leader":
|
|
267
|
+
capabilities.add("lead-session")
|
|
268
|
+
continue
|
|
269
|
+
capabilities.add("worker-artifact-io")
|
|
270
|
+
if role == "analyser":
|
|
271
|
+
capabilities.add("source-readonly")
|
|
272
|
+
if role == "implementer":
|
|
273
|
+
capabilities.add("project-mutation")
|
|
274
|
+
if role in {"report-writer", "translator"}:
|
|
275
|
+
capabilities.add("extended-artifact-authoring")
|
|
276
|
+
return frozenset(capabilities)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Canonical role identities and the duties that execute them."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Mapping, Sequence
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RoleCatalogError(ValueError):
|
|
9
|
+
"""Raised when a role or duty is absent from the canonical catalog."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class RoleDefinition:
|
|
14
|
+
id: str
|
|
15
|
+
legacy_aliases: tuple[str, ...]
|
|
16
|
+
required_capabilities: frozenset[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_READONLY_CAPABILITIES = frozenset({"worker-artifact-io", "source-readonly"})
|
|
20
|
+
|
|
21
|
+
ROLE_DEFINITIONS = (
|
|
22
|
+
RoleDefinition("leader", ("lead",), frozenset({"lead-session"})),
|
|
23
|
+
RoleDefinition("analyser", (), _READONLY_CAPABILITIES),
|
|
24
|
+
RoleDefinition("critic", (), _READONLY_CAPABILITIES),
|
|
25
|
+
RoleDefinition("designer", (), _READONLY_CAPABILITIES),
|
|
26
|
+
RoleDefinition("planner", (), _READONLY_CAPABILITIES),
|
|
27
|
+
RoleDefinition(
|
|
28
|
+
"implementer",
|
|
29
|
+
("executor",),
|
|
30
|
+
frozenset({"worker-artifact-io", "project-mutation"}),
|
|
31
|
+
),
|
|
32
|
+
RoleDefinition("verifier", (), _READONLY_CAPABILITIES),
|
|
33
|
+
RoleDefinition(
|
|
34
|
+
"report-writer",
|
|
35
|
+
(),
|
|
36
|
+
_READONLY_CAPABILITIES | frozenset({"extended-artifact-authoring"}),
|
|
37
|
+
),
|
|
38
|
+
RoleDefinition(
|
|
39
|
+
"translator",
|
|
40
|
+
(),
|
|
41
|
+
_READONLY_CAPABILITIES | frozenset({"extended-artifact-authoring"}),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
DUTY_ROLE_IDS = {
|
|
46
|
+
"lead": "leader",
|
|
47
|
+
"analysis-worker": "analyser",
|
|
48
|
+
"discovery-worker": "analyser",
|
|
49
|
+
"diagnosis-worker": "analyser",
|
|
50
|
+
"scope-critic": "critic",
|
|
51
|
+
"acceptance-critic": "critic",
|
|
52
|
+
"direction-selection-worker": "designer",
|
|
53
|
+
"planning-worker": "planner",
|
|
54
|
+
"implementation-executor": "implementer",
|
|
55
|
+
"implementation-verifier": "verifier",
|
|
56
|
+
"acceptance-verifier": "verifier",
|
|
57
|
+
"reverification-worker": "verifier",
|
|
58
|
+
"code-reviewer": "verifier",
|
|
59
|
+
"schedule-verifier": "verifier",
|
|
60
|
+
"report-writer": "report-writer",
|
|
61
|
+
"translator": "translator",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class RoleCatalog:
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
definitions: Sequence[RoleDefinition],
|
|
69
|
+
duty_role_ids: Mapping[str, str],
|
|
70
|
+
) -> None:
|
|
71
|
+
definition_list = tuple(definitions)
|
|
72
|
+
self._definition_count = len(definition_list)
|
|
73
|
+
self._definitions = {definition.id: definition for definition in definition_list}
|
|
74
|
+
self._aliases = {
|
|
75
|
+
alias: definition.id
|
|
76
|
+
for definition in definition_list
|
|
77
|
+
for alias in definition.legacy_aliases
|
|
78
|
+
}
|
|
79
|
+
self._duty_role_ids = dict(duty_role_ids)
|
|
80
|
+
self._validate()
|
|
81
|
+
|
|
82
|
+
def normalize(self, raw: str) -> str:
|
|
83
|
+
role = raw.strip()
|
|
84
|
+
canonical = self._aliases.get(role, role)
|
|
85
|
+
if canonical not in self._definitions:
|
|
86
|
+
raise RoleCatalogError(f"unknown role: {raw}")
|
|
87
|
+
return canonical
|
|
88
|
+
|
|
89
|
+
def required_capabilities(self, role: str) -> frozenset[str]:
|
|
90
|
+
return self._definitions[self.normalize(role)].required_capabilities
|
|
91
|
+
|
|
92
|
+
def role_for_duty(self, duty_id: str) -> str:
|
|
93
|
+
try:
|
|
94
|
+
return self._duty_role_ids[duty_id]
|
|
95
|
+
except KeyError as exc:
|
|
96
|
+
raise RoleCatalogError(f"unknown duty: {duty_id}") from exc
|
|
97
|
+
|
|
98
|
+
def _validate(self) -> None:
|
|
99
|
+
if len(self._definitions) != self._definition_count:
|
|
100
|
+
raise RoleCatalogError("duplicate canonical role")
|
|
101
|
+
if len(self._aliases) != sum(
|
|
102
|
+
len(definition.legacy_aliases) for definition in self._definitions.values()
|
|
103
|
+
):
|
|
104
|
+
raise RoleCatalogError("duplicate legacy role alias")
|
|
105
|
+
for role in self._duty_role_ids.values():
|
|
106
|
+
self.normalize(role)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
ROLE_CATALOG = RoleCatalog(ROLE_DEFINITIONS, DUTY_ROLE_IDS)
|
|
110
|
+
|
|
111
|
+
# Ids plus legacy aliases. Bundled adapters declare this set so every
|
|
112
|
+
# catalog model can be assigned to every role.
|
|
113
|
+
ALL_ROLE_TOKENS = frozenset(
|
|
114
|
+
token
|
|
115
|
+
for definition in ROLE_DEFINITIONS
|
|
116
|
+
for token in (definition.id, *definition.legacy_aliases)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def normalize_role(raw: str) -> str:
|
|
121
|
+
return ROLE_CATALOG.normalize(raw)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def role_for_duty(duty_id: str) -> str:
|
|
125
|
+
return ROLE_CATALOG.role_for_duty(duty_id)
|
|
@@ -24,6 +24,11 @@ class WizardPrompt:
|
|
|
24
24
|
multi: bool = False
|
|
25
25
|
questions: tuple["WizardPrompt", ...] = ()
|
|
26
26
|
|
|
27
|
+
@property
|
|
28
|
+
def id(self) -> str:
|
|
29
|
+
"""Stable public identifier shared by static and role-instance prompts."""
|
|
30
|
+
return self.step
|
|
31
|
+
|
|
27
32
|
|
|
28
33
|
@dataclass(frozen=True)
|
|
29
34
|
class AnswerProtocol:
|
|
@@ -66,6 +71,19 @@ def _normalize_numbered_item(prompt: WizardPrompt, answer: str) -> str:
|
|
|
66
71
|
number = int(candidate)
|
|
67
72
|
if 1 <= number <= len(prompt.options):
|
|
68
73
|
return prompt.options[number - 1].value
|
|
74
|
+
# 옵션 값 자체가 숫자인 role-count 는 목록 밖 번호를 값으로도 허용한다
|
|
75
|
+
# (예: 옵션 [3,4,5] 에서 "4" → 수량 4). 목록에 없으면 범위 오류.
|
|
76
|
+
if prompt.step.startswith("role-count:"):
|
|
77
|
+
exact_count = _exact_value_match(prompt, candidate)
|
|
78
|
+
if exact_count is not None:
|
|
79
|
+
return exact_count
|
|
80
|
+
role = prompt.step.split(":", 1)[1]
|
|
81
|
+
minimum = prompt.options[0].value if prompt.options else "?"
|
|
82
|
+
maximum = prompt.options[-1].value if prompt.options else "?"
|
|
83
|
+
raise WizardAnswerError(
|
|
84
|
+
f"role {role!r} count must be in {minimum}..{maximum}: "
|
|
85
|
+
f"{candidate}"
|
|
86
|
+
)
|
|
69
87
|
out_of_list = _exact_value_match(prompt, candidate)
|
|
70
88
|
if out_of_list is not None:
|
|
71
89
|
return out_of_list
|
|
@@ -9,15 +9,26 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from dataclasses import dataclass, field
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from
|
|
12
|
+
from collections.abc import Callable, Mapping
|
|
13
|
+
from typing import Any, Literal, Protocol, runtime_checkable
|
|
13
14
|
|
|
14
15
|
from .worker_stream import Normalise, no_events
|
|
16
|
+
from ..write_policy import WriteEnforcement, WritePolicy
|
|
15
17
|
|
|
16
18
|
# What ``ExecCommand.stream_format`` may hold. The value decides whether the
|
|
17
19
|
# runner pipes the CLI's output through the stream formatter or forwards it
|
|
18
20
|
# unchanged, so it is part of the contract rather than a display hint.
|
|
19
21
|
STREAM_JSON = "stream-json"
|
|
20
22
|
TEXT = "text"
|
|
23
|
+
SERVED_MODEL_MISMATCH_EXIT_CODE = 78
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def no_model_observation(_event: Mapping[str, Any]) -> str | None:
|
|
27
|
+
"""Truthful default for provider output that exposes no model identity."""
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
ObserveServedModel = Callable[[Mapping[str, Any]], str | None]
|
|
21
32
|
|
|
22
33
|
|
|
23
34
|
@dataclass(frozen=True)
|
|
@@ -33,6 +44,8 @@ class ExecutionPolicy:
|
|
|
33
44
|
|
|
34
45
|
auto_approve: bool
|
|
35
46
|
write_scope: tuple[Path, ...]
|
|
47
|
+
write_policy: WritePolicy | None = None
|
|
48
|
+
write_enforcement: WriteEnforcement | None = None
|
|
36
49
|
|
|
37
50
|
|
|
38
51
|
@dataclass(frozen=True)
|
|
@@ -84,6 +97,20 @@ class ExecCommand:
|
|
|
84
97
|
stream_format: str
|
|
85
98
|
cwd: Path
|
|
86
99
|
normalise: Normalise = field(default=no_events)
|
|
100
|
+
observe_served_model: ObserveServedModel = field(
|
|
101
|
+
default=no_model_observation
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True)
|
|
106
|
+
class WorkerWriteCapability:
|
|
107
|
+
"""Maximum write-boundary precision exposed by a worker runner."""
|
|
108
|
+
|
|
109
|
+
max_boundary_precision: Literal[
|
|
110
|
+
"exact-path",
|
|
111
|
+
"directory-boundary",
|
|
112
|
+
"none",
|
|
113
|
+
]
|
|
87
114
|
|
|
88
115
|
|
|
89
116
|
@dataclass(frozen=True)
|
|
@@ -99,6 +126,12 @@ class PolicySupport:
|
|
|
99
126
|
can_bound_write_scope: bool
|
|
100
127
|
note: str = ""
|
|
101
128
|
|
|
129
|
+
def worker_write_capability(self) -> WorkerWriteCapability:
|
|
130
|
+
precision = (
|
|
131
|
+
"directory-boundary" if self.can_bound_write_scope else "none"
|
|
132
|
+
)
|
|
133
|
+
return WorkerWriteCapability(precision)
|
|
134
|
+
|
|
102
135
|
|
|
103
136
|
@runtime_checkable
|
|
104
137
|
class ExecutionStrategy(Protocol):
|
|
@@ -9,6 +9,8 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
|
|
12
|
+
from .role import RoleCatalogError, normalize_role
|
|
13
|
+
|
|
12
14
|
# Implementation executor / verifier dispatches run whole build + test suites
|
|
13
15
|
# that legitimately emit nothing for minutes; the 600s cap reaped them mid-suite
|
|
14
16
|
# (observed: a silent jest + tsc build TERM'd at ~929s). This stays below the
|
|
@@ -16,7 +18,7 @@ from dataclasses import dataclass
|
|
|
16
18
|
_BUILD_RUNNING_IDLE_SECONDS = 1500
|
|
17
19
|
_DEFAULT_IDLE_SECONDS = 600
|
|
18
20
|
|
|
19
|
-
_BUILD_RUNNING_ROLES = frozenset({"
|
|
21
|
+
_BUILD_RUNNING_ROLES = frozenset({"implementer", "verifier"})
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
@dataclass(frozen=True)
|
|
@@ -26,9 +28,17 @@ class WorkerRoleSpec:
|
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
def role_spec(role: str) -> WorkerRoleSpec:
|
|
31
|
+
canonical_role = _canonical_role_or_raw(role)
|
|
29
32
|
seconds = (
|
|
30
33
|
_BUILD_RUNNING_IDLE_SECONDS
|
|
31
|
-
if
|
|
34
|
+
if canonical_role in _BUILD_RUNNING_ROLES
|
|
32
35
|
else _DEFAULT_IDLE_SECONDS
|
|
33
36
|
)
|
|
34
|
-
return WorkerRoleSpec(role=
|
|
37
|
+
return WorkerRoleSpec(role=canonical_role, idle_timeout_seconds=seconds)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _canonical_role_or_raw(role: str) -> str:
|
|
41
|
+
try:
|
|
42
|
+
return normalize_role(role)
|
|
43
|
+
except RoleCatalogError:
|
|
44
|
+
return role
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Execution-surface value objects.
|
|
2
|
+
|
|
3
|
+
The surface is where a worker process starts and where its output lands.
|
|
4
|
+
It is not a host, a provider, or a role.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
SURFACE_CMUX_PANE = "cmux-pane"
|
|
13
|
+
SURFACE_CLI_WRAPPER = "cli-wrapper"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SurfaceUnavailable(Exception):
|
|
17
|
+
"""The surface could not be opened. The application may try the next one."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EnvironmentBlocked(Exception):
|
|
21
|
+
"""A sandbox hid the surface and the worker CLI config. Do not degrade."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class WorkerSpawnRequest:
|
|
26
|
+
command: tuple[str, ...]
|
|
27
|
+
cwd: Path
|
|
28
|
+
title: str
|
|
29
|
+
owned_surface_ids: tuple[str, ...] = ()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class RuntimeHandle:
|
|
34
|
+
surface: str
|
|
35
|
+
identifier: str
|
|
36
|
+
waitable: bool
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class ProgressEvent:
|
|
41
|
+
message: str
|
|
42
|
+
level: str = "info"
|
|
43
|
+
notify_title: str | None = None
|
|
44
|
+
notify_body: str | None = None
|
|
@@ -88,6 +88,30 @@ StreamEvent = Text | ToolCall | ToolResult | Denial | Result
|
|
|
88
88
|
Normalise = Callable[[Mapping[str, Any]], tuple[StreamEvent, ...]]
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def host_event_row(
|
|
92
|
+
*,
|
|
93
|
+
event_id: str,
|
|
94
|
+
sequence: int,
|
|
95
|
+
event_type: str,
|
|
96
|
+
invocation_ref: str,
|
|
97
|
+
attempt: int,
|
|
98
|
+
payload: Mapping[str, Any],
|
|
99
|
+
timestamp: str,
|
|
100
|
+
) -> dict[str, Any]:
|
|
101
|
+
"""Build one orchestrator-owned host-event row without I/O."""
|
|
102
|
+
row = {
|
|
103
|
+
"schemaVersion": "1.0",
|
|
104
|
+
"eventId": event_id,
|
|
105
|
+
"sequence": sequence,
|
|
106
|
+
"timestamp": timestamp,
|
|
107
|
+
"eventType": event_type,
|
|
108
|
+
"invocationRef": invocation_ref,
|
|
109
|
+
"attempt": attempt,
|
|
110
|
+
}
|
|
111
|
+
row.update(dict(payload))
|
|
112
|
+
return row
|
|
113
|
+
|
|
114
|
+
|
|
91
115
|
def no_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
92
116
|
"""The normaliser for a CLI that emits no events at all.
|
|
93
117
|
|