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
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Protocol
|
|
5
|
+
from typing import Literal, Protocol
|
|
6
|
+
|
|
7
|
+
from okstra_ctl.domain.provider import HostModelBinding
|
|
8
|
+
from okstra_ctl.domain.worker_exec import WorkerWriteCapability
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class HostModelBindingError(ValueError):
|
|
@@ -13,48 +16,76 @@ class HostModelBindingError(ValueError):
|
|
|
13
16
|
class HostModelBindingRequest:
|
|
14
17
|
host_runtime: str
|
|
15
18
|
provider: str
|
|
16
|
-
|
|
19
|
+
model_ref: str
|
|
20
|
+
version_kind: Literal["pinned", "channel"]
|
|
17
21
|
model_execution_value: str
|
|
18
22
|
runner: str
|
|
23
|
+
resolved_execution_value: str | None = None
|
|
24
|
+
worker_write_capability: WorkerWriteCapability | None = None
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def execution_value(self) -> str:
|
|
28
|
+
return self.resolved_execution_value or self.model_execution_value
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
class HostModelBindingPort(Protocol):
|
|
22
|
-
def resolve(self, request: HostModelBindingRequest) ->
|
|
32
|
+
def resolve(self, request: HostModelBindingRequest) -> HostModelBinding: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cli_binding(request: HostModelBindingRequest) -> HostModelBinding:
|
|
36
|
+
return HostModelBinding(
|
|
37
|
+
runner=request.runner,
|
|
38
|
+
catalog_execution_value=request.model_execution_value,
|
|
39
|
+
resolved_execution_value=request.execution_value,
|
|
40
|
+
host_model_value=None,
|
|
41
|
+
binding_fidelity="exact",
|
|
42
|
+
worker_write_capability=request.worker_write_capability,
|
|
43
|
+
)
|
|
23
44
|
|
|
24
45
|
|
|
25
46
|
class FailClosedHostModelBindingPort:
|
|
26
47
|
def __init__(self, host_runtime: str) -> None:
|
|
27
48
|
self._host_runtime = host_runtime
|
|
28
49
|
|
|
29
|
-
def resolve(self, request: HostModelBindingRequest) ->
|
|
50
|
+
def resolve(self, request: HostModelBindingRequest) -> HostModelBinding:
|
|
30
51
|
if request.runner == "cli-wrapper":
|
|
31
|
-
return
|
|
52
|
+
return _cli_binding(request)
|
|
32
53
|
raise HostModelBindingError(
|
|
33
54
|
"unsupported host model: "
|
|
34
55
|
f"host={self._host_runtime!r}, provider={request.provider!r}, "
|
|
35
|
-
f"model={request.
|
|
56
|
+
f"model={request.execution_value!r}"
|
|
36
57
|
)
|
|
37
58
|
|
|
38
59
|
|
|
39
60
|
class NativeExecutionValueHostModelBindingPort:
|
|
40
|
-
"""
|
|
61
|
+
"""Binding for hosts whose native API accepts provider execution values."""
|
|
41
62
|
|
|
42
63
|
def __init__(self, host_runtime: str, native_provider: str) -> None:
|
|
43
64
|
self._host_runtime = host_runtime
|
|
44
65
|
self._native_provider = native_provider
|
|
45
66
|
|
|
46
|
-
def resolve(self, request: HostModelBindingRequest) ->
|
|
67
|
+
def resolve(self, request: HostModelBindingRequest) -> HostModelBinding:
|
|
47
68
|
if request.runner == "cli-wrapper":
|
|
48
|
-
return
|
|
49
|
-
if (
|
|
50
|
-
request.runner != "native-session"
|
|
51
|
-
or request.host_runtime != self._host_runtime
|
|
52
|
-
or request.provider != self._native_provider
|
|
53
|
-
or not request.model_execution_value.strip()
|
|
54
|
-
):
|
|
69
|
+
return _cli_binding(request)
|
|
70
|
+
if not self._supports_native(request):
|
|
55
71
|
raise HostModelBindingError(
|
|
56
72
|
"unsupported host model: "
|
|
57
73
|
f"host={request.host_runtime!r}, provider={request.provider!r}, "
|
|
58
|
-
f"model={request.
|
|
74
|
+
f"model={request.execution_value!r}"
|
|
59
75
|
)
|
|
60
|
-
return
|
|
76
|
+
return HostModelBinding(
|
|
77
|
+
runner=request.runner,
|
|
78
|
+
catalog_execution_value=request.model_execution_value,
|
|
79
|
+
resolved_execution_value=request.execution_value,
|
|
80
|
+
host_model_value=request.execution_value,
|
|
81
|
+
binding_fidelity="exact",
|
|
82
|
+
worker_write_capability=request.worker_write_capability,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _supports_native(self, request: HostModelBindingRequest) -> bool:
|
|
86
|
+
return (
|
|
87
|
+
request.runner == "native-session"
|
|
88
|
+
and request.host_runtime == self._host_runtime
|
|
89
|
+
and request.provider == self._native_provider
|
|
90
|
+
and bool(request.execution_value.strip())
|
|
91
|
+
)
|
|
@@ -5,6 +5,8 @@ from dataclasses import dataclass
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Literal, Protocol
|
|
7
7
|
|
|
8
|
+
from okstra_ctl.domain.worker_exec import WorkerWriteCapability
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
@dataclass(frozen=True)
|
|
10
12
|
class WorkerDispatchRequest:
|
|
@@ -30,3 +32,5 @@ class WorkerDispatchPlan:
|
|
|
30
32
|
|
|
31
33
|
class WorkerDispatchPort(Protocol):
|
|
32
34
|
def build_plan(self, request: WorkerDispatchRequest) -> WorkerDispatchPlan: ...
|
|
35
|
+
|
|
36
|
+
def worker_write_capability(self) -> WorkerWriteCapability: ...
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Port for opening, closing, and signalling a worker execution surface."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from ..domain.worker_runtime import (
|
|
7
|
+
ProgressEvent,
|
|
8
|
+
RuntimeHandle,
|
|
9
|
+
WorkerSpawnRequest,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorkerRuntimePort(Protocol):
|
|
14
|
+
surface: str
|
|
15
|
+
|
|
16
|
+
def spawn(self, request: WorkerSpawnRequest) -> RuntimeHandle: ...
|
|
17
|
+
|
|
18
|
+
def close(self, handle: RuntimeHandle) -> None: ...
|
|
19
|
+
|
|
20
|
+
def wait(self, handle: RuntimeHandle) -> int: ...
|
|
21
|
+
|
|
22
|
+
def terminate(self, handle: RuntimeHandle) -> None: ...
|
|
23
|
+
|
|
24
|
+
def notify(self, event: ProgressEvent) -> None: ...
|
|
25
|
+
|
|
26
|
+
def restore_lead(self) -> None: ...
|
|
@@ -11,6 +11,7 @@ import json
|
|
|
11
11
|
import sys
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
+
from okstra_ctl import next_phase
|
|
14
15
|
from okstra_ctl.clarification_items import sidecar_answers, user_response_sidecars
|
|
15
16
|
from okstra_ctl.ids import slugify_task_segment
|
|
16
17
|
from okstra_ctl.incremental_scope import preview_link_availability_for_report
|
|
@@ -108,7 +109,9 @@ def assemble_recap(task_root: Path, project_root: Path) -> dict:
|
|
|
108
109
|
"fromPhase": prev_phase,
|
|
109
110
|
"toPhase": cur,
|
|
110
111
|
"lastCompletedPhase": snap.get("lastCompletedPhase", ""),
|
|
111
|
-
"nextRecommendedPhase":
|
|
112
|
+
"nextRecommendedPhase": next_phase.promote(
|
|
113
|
+
snap.get("nextRecommendedPhase")
|
|
114
|
+
),
|
|
112
115
|
"reportPath": run.get("reportPath", ""),
|
|
113
116
|
})
|
|
114
117
|
prev_phase = cur
|
|
@@ -46,7 +46,7 @@ class ProviderRegistry:
|
|
|
46
46
|
return tuple(
|
|
47
47
|
provider_id
|
|
48
48
|
for provider_id, spec in self._providers.items()
|
|
49
|
-
if
|
|
49
|
+
if spec.supports_role(role)
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
@property
|
|
@@ -72,10 +72,14 @@ def _registry_for_roots(
|
|
|
72
72
|
) -> ProviderRegistry:
|
|
73
73
|
discovered = ProviderRegistry.from_roots((bundled_root, user_root)).providers
|
|
74
74
|
bundled_order = ("claude", "antigravity", "codex", "grok", "kimi")
|
|
75
|
-
ordered = {
|
|
75
|
+
ordered = {
|
|
76
|
+
provider_id: discovered[provider_id]
|
|
77
|
+
for provider_id in bundled_order
|
|
78
|
+
if provider_id in discovered
|
|
79
|
+
}
|
|
76
80
|
ordered.update(
|
|
77
|
-
(provider_id,
|
|
78
|
-
for provider_id
|
|
81
|
+
(provider_id, discovered[provider_id])
|
|
82
|
+
for provider_id in sorted(discovered)
|
|
79
83
|
if provider_id not in ordered
|
|
80
84
|
)
|
|
81
85
|
return ProviderRegistry(ordered)
|
|
@@ -30,6 +30,7 @@ from okstra_project.dirs import TASK_MANIFEST_FILENAME, OKSTRA_DIR_NAME, project
|
|
|
30
30
|
# render_task_manifest 가 동일한 리스트/딕셔너리를 로컬에 중복 정의했는데,
|
|
31
31
|
# 이는 silent drift 위험이 있어 SSOT import 로 통합한다.
|
|
32
32
|
from . import fix_cycles
|
|
33
|
+
from . import next_phase
|
|
33
34
|
from .analysis_inputs import ANALYSIS_TASK_TYPES
|
|
34
35
|
from .application.resolve_assignment import (
|
|
35
36
|
resolve_assignment_runner,
|
|
@@ -46,7 +47,7 @@ from .registry.provider_registry import default_provider_registry
|
|
|
46
47
|
from .path_hints import compact_active_run_context, hydrate_run_context
|
|
47
48
|
from .paths import task_timeline_file
|
|
48
49
|
from .worker_artifacts import artifacts_from_context
|
|
49
|
-
from .workflow import
|
|
50
|
+
from .workflow import PHASE_SEQUENCE
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
class TokenRenderError(Exception):
|
|
@@ -84,6 +85,16 @@ def _write_json(path: Path, payload: dict) -> None:
|
|
|
84
85
|
_write_text(path, json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
def _execution_identity(ctx: Mapping[str, object]) -> dict:
|
|
89
|
+
raw = ctx.get("EXECUTION_IDENTITY_JSON")
|
|
90
|
+
if not isinstance(raw, str) or not raw.strip():
|
|
91
|
+
return {}
|
|
92
|
+
payload = json.loads(raw)
|
|
93
|
+
if not isinstance(payload, dict):
|
|
94
|
+
raise TokenRenderError("EXECUTION_IDENTITY_JSON must contain an object")
|
|
95
|
+
return payload
|
|
96
|
+
|
|
97
|
+
|
|
87
98
|
def _lead_runtime(ctx: dict) -> str:
|
|
88
99
|
return ctx.get("LEAD_RUNTIME", "") or "claude-code"
|
|
89
100
|
|
|
@@ -377,10 +388,14 @@ def _agent_contract(ctx: dict) -> dict:
|
|
|
377
388
|
return parsed
|
|
378
389
|
|
|
379
390
|
|
|
380
|
-
def _executor_contract(ctx: dict) -> dict:
|
|
391
|
+
def _executor_contract(ctx: dict) -> dict | None:
|
|
381
392
|
runner = ctx.get("EXECUTOR_RUNNER", "")
|
|
382
393
|
dispatch_mode = ctx.get("EXECUTOR_DISPATCH_MODE", "")
|
|
383
394
|
host_model_value = ctx.get("EXECUTOR_HOST_MODEL_VALUE") or None
|
|
395
|
+
if not runner and not ctx.get("EXECUTOR_PROVIDER"):
|
|
396
|
+
if ctx.get("TASK_TYPE") == "implementation":
|
|
397
|
+
raise ValueError("implementation executor assignment is required")
|
|
398
|
+
return None
|
|
384
399
|
expected_mode = {
|
|
385
400
|
"native-session": "host-native",
|
|
386
401
|
"cli-wrapper": "worker-dispatch",
|
|
@@ -549,9 +564,10 @@ def _active_workflow(ctx: dict) -> dict:
|
|
|
549
564
|
"currentPhase": ctx.get("WORKFLOW_CURRENT_PHASE", ""),
|
|
550
565
|
"currentPhaseState": ctx.get("WORKFLOW_CURRENT_PHASE_STATE", ""),
|
|
551
566
|
"lastCompletedPhase": ctx.get("WORKFLOW_LAST_COMPLETED_PHASE", ""),
|
|
552
|
-
"nextRecommendedPhase":
|
|
567
|
+
"nextRecommendedPhase": next_phase.promote(
|
|
568
|
+
ctx.get("WORKFLOW_NEXT_RECOMMENDED_PHASE")
|
|
569
|
+
),
|
|
553
570
|
"awaitingApproval": ctx.get("WORKFLOW_AWAITING_APPROVAL", "false") == "true",
|
|
554
|
-
"routingStatus": ctx.get("WORKFLOW_ROUTING_STATUS", ""),
|
|
555
571
|
"allowedOutputs": ctx.get("PHASE_ALLOWED_OUTPUTS", ""),
|
|
556
572
|
"forbiddenActions": ctx.get("PHASE_FORBIDDEN_ACTIONS", ""),
|
|
557
573
|
}
|
|
@@ -903,15 +919,14 @@ def render_task_catalog_discovery(output_path: str, ctx: dict) -> None:
|
|
|
903
919
|
"lastCompletedPhase": (workflow or {}).get("lastCompletedPhase", "")
|
|
904
920
|
if isinstance(workflow, dict)
|
|
905
921
|
else "",
|
|
906
|
-
"nextRecommendedPhase":
|
|
907
|
-
|
|
908
|
-
|
|
922
|
+
"nextRecommendedPhase": next_phase.promote(
|
|
923
|
+
(workflow or {}).get("nextRecommendedPhase")
|
|
924
|
+
if isinstance(workflow, dict)
|
|
925
|
+
else None
|
|
926
|
+
),
|
|
909
927
|
"awaitingApproval": (workflow or {}).get("awaitingApproval", False)
|
|
910
928
|
if isinstance(workflow, dict)
|
|
911
929
|
else False,
|
|
912
|
-
"routingStatus": (workflow or {}).get("routingStatus", "")
|
|
913
|
-
if isinstance(workflow, dict)
|
|
914
|
-
else "",
|
|
915
930
|
"taskRootPath": s(manifest, "taskRootPath") or rel(task_root),
|
|
916
931
|
"taskManifestPath": s(manifest, "taskManifestPath")
|
|
917
932
|
or rel(manifest_path),
|
|
@@ -1177,27 +1192,31 @@ def _derive_phase_states(existing_workflow: dict, ctx: dict) -> tuple[dict, str,
|
|
|
1177
1192
|
|
|
1178
1193
|
|
|
1179
1194
|
def _derive_next_recommended_phase(
|
|
1180
|
-
|
|
1181
|
-
) ->
|
|
1182
|
-
"""
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1195
|
+
existing_workflow: dict, current_phase_state: str
|
|
1196
|
+
) -> dict:
|
|
1197
|
+
"""prepare 시점의 포인터를 만든다.
|
|
1198
|
+
|
|
1199
|
+
대상 phase 는 리드가 정한다. prepare 는 그 값을 보존하고 진행 가능 여부만
|
|
1200
|
+
내린다 — 실행이 아직 안 끝났는데 다음을 제안하면 위저드가 현재 phase 를
|
|
1201
|
+
끝난 것으로 오인한다. (기록된 사례: implementation 이 `prepared` 로
|
|
1202
|
+
준비됐을 때 위저드가 `final-verification` 을 기본 task_type 으로 추천했다.)
|
|
1203
|
+
|
|
1204
|
+
현재 phase 가 `completed` 면 그 오인이 성립하지 않으므로 포인터를 승격만 해서
|
|
1205
|
+
그대로 돌려준다. 내리는 대상도 `ready` 하나뿐이다 — `pending` · `blocked` ·
|
|
1206
|
+
`terminal` 은 애초에 착수를 부르지 않는 상태라 내려봐야 얻는 것이 없고,
|
|
1207
|
+
리드가 판단해 적은 `blocked`(사람이 봐야 하는 상태)와 `terminal`(생애주기
|
|
1208
|
+
종료)을 `pending`("아직 아무도 정하지 않음")으로 지워버린다.
|
|
1209
|
+
"""
|
|
1210
|
+
pointer = next_phase.promote(existing_workflow.get("nextRecommendedPhase"))
|
|
1211
|
+
if current_phase_state == "completed":
|
|
1212
|
+
return pointer
|
|
1213
|
+
if pointer["status"] != next_phase.STATUS_READY:
|
|
1214
|
+
return pointer
|
|
1215
|
+
return next_phase.make(
|
|
1216
|
+
phase=pointer["phase"],
|
|
1217
|
+
status=next_phase.STATUS_PENDING,
|
|
1218
|
+
rationale=pointer["rationale"],
|
|
1192
1219
|
)
|
|
1193
|
-
existing_next = existing_workflow.get("nextRecommendedPhase") or ""
|
|
1194
|
-
if current_phase_state not in {"completed"}:
|
|
1195
|
-
# 아직 끝나지 않음 — 같은 phase 를 추천한다. sequence 상 더 앞을 가리키는
|
|
1196
|
-
# stale existing_next 는 무시하고, 같은 current_phase 를 가리키는 값만 존중.
|
|
1197
|
-
return current_phase
|
|
1198
|
-
if existing_next and existing_next != current_phase:
|
|
1199
|
-
return existing_next
|
|
1200
|
-
return canonical_next
|
|
1201
1220
|
|
|
1202
1221
|
|
|
1203
1222
|
def _derive_latest_pointers(existing: dict, ctx: dict, current_report_relative: str) -> dict:
|
|
@@ -1306,14 +1325,11 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1306
1325
|
"WORKFLOW_WORK_CATEGORY", "unknown"
|
|
1307
1326
|
)
|
|
1308
1327
|
next_recommended_phase = _derive_next_recommended_phase(
|
|
1309
|
-
|
|
1328
|
+
existing_workflow, current_phase_state
|
|
1310
1329
|
)
|
|
1311
1330
|
last_completed_phase = existing_workflow.get("lastCompletedPhase") or ctx.get(
|
|
1312
1331
|
"WORKFLOW_LAST_COMPLETED_PHASE", ""
|
|
1313
1332
|
)
|
|
1314
|
-
routing_status = existing_workflow.get("routingStatus") or ctx.get(
|
|
1315
|
-
"WORKFLOW_ROUTING_STATUS", "not-applicable"
|
|
1316
|
-
)
|
|
1317
1333
|
awaiting_approval = existing_workflow.get("awaitingApproval")
|
|
1318
1334
|
if not isinstance(awaiting_approval, bool):
|
|
1319
1335
|
awaiting_approval = ctx.get("WORKFLOW_AWAITING_APPROVAL", "false") == "true"
|
|
@@ -1326,8 +1342,14 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1326
1342
|
latest_team_state_relative = pointers["latestTeamStatePath"]
|
|
1327
1343
|
latest_resume_command_relative = pointers["latestResumeCommandPath"]
|
|
1328
1344
|
convergence_block = _build_convergence_block(ctx)
|
|
1345
|
+
execution_identity = _execution_identity(ctx)
|
|
1346
|
+
existing_v2 = (
|
|
1347
|
+
existing.get("schemaVersion") == "2.0"
|
|
1348
|
+
and existing.get("executionIdentityVersion") == 2
|
|
1349
|
+
)
|
|
1350
|
+
writes_v2 = execution_identity.get("executionIdentityVersion") == 2
|
|
1329
1351
|
payload = {
|
|
1330
|
-
"schemaVersion": "1.0",
|
|
1352
|
+
"schemaVersion": "2.0" if existing_v2 or writes_v2 else "1.0",
|
|
1331
1353
|
"projectId": ctx.get("PROJECT_ID", ""),
|
|
1332
1354
|
"taskGroup": ctx.get("TASK_GROUP", ""),
|
|
1333
1355
|
"taskId": ctx.get("TASK_ID", ""),
|
|
@@ -1379,7 +1401,6 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1379
1401
|
"lastCompletedPhase": last_completed_phase,
|
|
1380
1402
|
"nextRecommendedPhase": next_recommended_phase,
|
|
1381
1403
|
"awaitingApproval": awaiting_approval,
|
|
1382
|
-
"routingStatus": routing_status,
|
|
1383
1404
|
"lastSafeCheckpoint": last_safe_checkpoint,
|
|
1384
1405
|
},
|
|
1385
1406
|
"artifacts": {
|
|
@@ -1487,6 +1508,12 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1487
1508
|
"sessionId": ctx.get("CLAUDE_SESSION_ID", ""),
|
|
1488
1509
|
"resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
1489
1510
|
}
|
|
1511
|
+
if existing_v2 or writes_v2:
|
|
1512
|
+
existing_index = existing.get("roleExecutionIndex")
|
|
1513
|
+
payload["executionIdentityVersion"] = 2
|
|
1514
|
+
payload["roleExecutionIndex"] = (
|
|
1515
|
+
list(existing_index) if isinstance(existing_index, list) else []
|
|
1516
|
+
)
|
|
1490
1517
|
_write_json(path, payload)
|
|
1491
1518
|
|
|
1492
1519
|
|
|
@@ -1608,8 +1635,9 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1608
1635
|
for s in str(ctx.get("CONCURRENT_RUN_STAGES", "") or "").split(",")
|
|
1609
1636
|
if s.strip().isdigit()
|
|
1610
1637
|
]
|
|
1638
|
+
execution_identity = _execution_identity(ctx)
|
|
1611
1639
|
payload = {
|
|
1612
|
-
"schemaVersion": "1.0",
|
|
1640
|
+
"schemaVersion": execution_identity.get("schemaVersion", "1.0"),
|
|
1613
1641
|
"okstraVersion": ctx.get("OKSTRA_VERSION", ""),
|
|
1614
1642
|
"projectId": ctx.get("PROJECT_ID", ""),
|
|
1615
1643
|
"taskGroup": ctx.get("TASK_GROUP", ""),
|
|
@@ -1719,16 +1747,13 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1719
1747
|
"lastCompletedPhase": workflow.get(
|
|
1720
1748
|
"lastCompletedPhase", ctx.get("WORKFLOW_LAST_COMPLETED_PHASE", "")
|
|
1721
1749
|
),
|
|
1722
|
-
"nextRecommendedPhase":
|
|
1723
|
-
|
|
1750
|
+
"nextRecommendedPhase": next_phase.promote(
|
|
1751
|
+
workflow.get("nextRecommendedPhase")
|
|
1724
1752
|
),
|
|
1725
1753
|
"awaitingApproval": workflow.get(
|
|
1726
1754
|
"awaitingApproval",
|
|
1727
1755
|
ctx.get("WORKFLOW_AWAITING_APPROVAL", "false") == "true",
|
|
1728
1756
|
),
|
|
1729
|
-
"routingStatus": workflow.get(
|
|
1730
|
-
"routingStatus", ctx.get("WORKFLOW_ROUTING_STATUS", "")
|
|
1731
|
-
),
|
|
1732
1757
|
"lastSafeCheckpoint": workflow.get("lastSafeCheckpoint", {}),
|
|
1733
1758
|
},
|
|
1734
1759
|
"teamContract": {
|
|
@@ -1783,6 +1808,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1783
1808
|
)
|
|
1784
1809
|
if _lead_runtime(ctx) == "claude-code":
|
|
1785
1810
|
payload["claudeSessionId"] = ctx.get("CLAUDE_SESSION_ID", "")
|
|
1811
|
+
payload.update(execution_identity)
|
|
1786
1812
|
_guard_invocation_manifest_rewrite(existing_run_manifest, payload)
|
|
1787
1813
|
_write_json(Path(run_manifest_path), payload)
|
|
1788
1814
|
|
|
@@ -1795,6 +1821,11 @@ _MODEL_ASSIGNMENT_KEYS = {
|
|
|
1795
1821
|
"hostRuntime",
|
|
1796
1822
|
"hostModelValue",
|
|
1797
1823
|
}
|
|
1824
|
+
_V2_INVOCATION_IDENTITY_KEYS = {
|
|
1825
|
+
"participantRef",
|
|
1826
|
+
"roleExecutionRef",
|
|
1827
|
+
"dutyId",
|
|
1828
|
+
}
|
|
1798
1829
|
_AGENT_CONTRACT_KEYS = {
|
|
1799
1830
|
"schemaVersion",
|
|
1800
1831
|
"dutyRootPath",
|
|
@@ -1860,11 +1891,8 @@ def _validate_invocation_manifest(payload: Mapping[str, object]) -> None:
|
|
|
1860
1891
|
or not assignments
|
|
1861
1892
|
):
|
|
1862
1893
|
raise ValueError("incomplete invocation manifest")
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
for item in assignments.values()
|
|
1866
|
-
):
|
|
1867
|
-
raise ValueError("incomplete invocation manifest assignments")
|
|
1894
|
+
_invocation_manifest_identity_version(payload)
|
|
1895
|
+
_validate_invocation_assignment_shapes(assignments)
|
|
1868
1896
|
_validate_compatibility_projections(payload, assignments)
|
|
1869
1897
|
for key in (
|
|
1870
1898
|
item
|
|
@@ -1901,13 +1929,17 @@ def _validate_compatibility_projections(
|
|
|
1901
1929
|
workers = payload.get("workerAssignments")
|
|
1902
1930
|
if not isinstance(lead, Mapping) or not isinstance(workers, list):
|
|
1903
1931
|
raise ValueError("incomplete invocation manifest compatibility projection")
|
|
1904
|
-
if _model_projection(lead) !=
|
|
1932
|
+
if _model_projection(lead) != _model_projection_from_assignment(
|
|
1933
|
+
assignments.get("lead")
|
|
1934
|
+
):
|
|
1905
1935
|
raise ValueError("immutable invocation manifest lead projection drift")
|
|
1906
1936
|
for worker in workers:
|
|
1907
1937
|
if not isinstance(worker, Mapping):
|
|
1908
1938
|
raise ValueError("incomplete invocation manifest worker projection")
|
|
1909
1939
|
reference = f"initial/{worker.get('workerId', '')}"
|
|
1910
|
-
if _model_projection(worker) !=
|
|
1940
|
+
if _model_projection(worker) != _model_projection_from_assignment(
|
|
1941
|
+
assignments.get(reference)
|
|
1942
|
+
):
|
|
1911
1943
|
raise ValueError("immutable invocation manifest worker projection drift")
|
|
1912
1944
|
|
|
1913
1945
|
|
|
@@ -1915,6 +1947,40 @@ def _model_projection(value: Mapping[str, object]) -> dict[str, object]:
|
|
|
1915
1947
|
return {key: value.get(key) for key in _MODEL_ASSIGNMENT_KEYS}
|
|
1916
1948
|
|
|
1917
1949
|
|
|
1950
|
+
def _model_projection_from_assignment(value: object) -> dict[str, object] | None:
|
|
1951
|
+
if not isinstance(value, Mapping) or not _MODEL_ASSIGNMENT_KEYS.issubset(value):
|
|
1952
|
+
return None
|
|
1953
|
+
return _model_projection(value)
|
|
1954
|
+
|
|
1955
|
+
|
|
1956
|
+
def _invocation_manifest_identity_version(payload: Mapping[str, object]) -> int:
|
|
1957
|
+
schema = payload.get("schemaVersion")
|
|
1958
|
+
identity = payload.get("executionIdentityVersion")
|
|
1959
|
+
if schema == "2.0" and identity == 2:
|
|
1960
|
+
return 2
|
|
1961
|
+
if schema == "2.0" or identity is not None:
|
|
1962
|
+
raise ValueError("invocation manifest mixes v1 and v2 execution identity")
|
|
1963
|
+
if schema not in (None, 1, "1", "1.0"):
|
|
1964
|
+
raise ValueError("unsupported invocation manifest execution identity version")
|
|
1965
|
+
return 1
|
|
1966
|
+
|
|
1967
|
+
|
|
1968
|
+
def _validate_invocation_assignment_shapes(
|
|
1969
|
+
assignments: Mapping[str, object],
|
|
1970
|
+
) -> None:
|
|
1971
|
+
allowed = {frozenset(_MODEL_ASSIGNMENT_KEYS)}
|
|
1972
|
+
for item in assignments.values():
|
|
1973
|
+
if not isinstance(item, Mapping):
|
|
1974
|
+
raise ValueError("incomplete invocation manifest assignments")
|
|
1975
|
+
keys = set(item)
|
|
1976
|
+
if keys & _V2_INVOCATION_IDENTITY_KEYS:
|
|
1977
|
+
raise ValueError(
|
|
1978
|
+
"invocation assignment duplicates v2 execution identity"
|
|
1979
|
+
)
|
|
1980
|
+
if frozenset(keys) not in allowed:
|
|
1981
|
+
raise ValueError("incomplete invocation manifest assignments")
|
|
1982
|
+
|
|
1983
|
+
|
|
1918
1984
|
def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
1919
1985
|
task_manifest_path = Path(ctx.get("TASK_MANIFEST_PATH", ""))
|
|
1920
1986
|
task_manifest = {}
|
|
@@ -1983,9 +2049,10 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
|
1983
2049
|
"currentPhaseState": workflow.get("currentPhaseState", ""),
|
|
1984
2050
|
"phaseStates": workflow.get("phaseStates", {}),
|
|
1985
2051
|
"lastCompletedPhase": workflow.get("lastCompletedPhase", ""),
|
|
1986
|
-
"nextRecommendedPhase":
|
|
2052
|
+
"nextRecommendedPhase": next_phase.promote(
|
|
2053
|
+
workflow.get("nextRecommendedPhase")
|
|
2054
|
+
),
|
|
1987
2055
|
"awaitingApproval": workflow.get("awaitingApproval", False),
|
|
1988
|
-
"routingStatus": workflow.get("routingStatus", ""),
|
|
1989
2056
|
"lastSafeCheckpoint": workflow.get("lastSafeCheckpoint", {}),
|
|
1990
2057
|
},
|
|
1991
2058
|
}
|
|
@@ -2171,15 +2238,15 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
2171
2238
|
"lastCompletedPhase", ctx.get("WORKFLOW_LAST_COMPLETED_PHASE", "")
|
|
2172
2239
|
)
|
|
2173
2240
|
or "--",
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2241
|
+
# 토큰은 사람이 읽는 한 줄이라 문자열이어야 한다. 포인터의 `status` 는
|
|
2242
|
+
# 같은 페이지의 다른 줄이 아니라 소비자 로직이 읽는다.
|
|
2243
|
+
"{{WORKFLOW_NEXT_RECOMMENDED_PHASE}}": next_phase.promote(
|
|
2244
|
+
workflow.get("nextRecommendedPhase")
|
|
2245
|
+
)["phase"]
|
|
2246
|
+
or "--",
|
|
2177
2247
|
"{{WORKFLOW_AWAITING_APPROVAL}}": "yes"
|
|
2178
2248
|
if workflow.get("awaitingApproval", False)
|
|
2179
2249
|
else "no",
|
|
2180
|
-
"{{WORKFLOW_ROUTING_STATUS}}": workflow.get(
|
|
2181
|
-
"routingStatus", ctx.get("WORKFLOW_ROUTING_STATUS", "")
|
|
2182
|
-
),
|
|
2183
2250
|
"{{WORKFLOW_PHASE_STATE_LINES}}": "\n".join(phase_state_lines),
|
|
2184
2251
|
"{{WORKFLOW_LAST_SAFE_CHECKPOINT_LINES}}": "\n".join(checkpoint_lines),
|
|
2185
2252
|
"{{FIX_CYCLES_SUMMARY}}": _fix_cycles_index_line(task_manifest),
|
|
@@ -566,6 +566,8 @@ def _ai_markdown_context(data: dict, schema: dict | None) -> dict:
|
|
|
566
566
|
context["md_rest"] = sections.rest
|
|
567
567
|
context["md_has"] = sections.has
|
|
568
568
|
context["md_claim"] = sections.mark_rendered
|
|
569
|
+
context["executionRolesTable"] = render_execution_roles_markdown(data)
|
|
570
|
+
context["executionIdentityVersion"] = data.get("executionIdentityVersion")
|
|
569
571
|
return context
|
|
570
572
|
|
|
571
573
|
|
|
@@ -623,6 +625,23 @@ _OPTIONAL_ANALYSIS_DEFAULTS = (
|
|
|
623
625
|
)
|
|
624
626
|
|
|
625
627
|
|
|
628
|
+
def render_execution_roles_markdown(data: dict) -> str:
|
|
629
|
+
roles = data.get("executionRoles") or []
|
|
630
|
+
if data.get("executionIdentityVersion") != 2:
|
|
631
|
+
return "legacy execution roles"
|
|
632
|
+
lines = ["| Role | Label | Status |", "|---|---|---|"]
|
|
633
|
+
for row in roles:
|
|
634
|
+
lines.append(
|
|
635
|
+
f"| {row.get('role', '')} | {row.get('executionLabel', '')} | "
|
|
636
|
+
f"{row.get('status', '')} |"
|
|
637
|
+
)
|
|
638
|
+
return "\n".join(lines)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def render_execution_roles_html(data: dict) -> str:
|
|
642
|
+
return render_execution_roles_markdown(data)
|
|
643
|
+
|
|
644
|
+
|
|
626
645
|
def _with_optional_defaults(data: dict) -> dict:
|
|
627
646
|
"""Render context with schema-optional fields filled in.
|
|
628
647
|
|
|
@@ -170,6 +170,45 @@ def html_template_for(task_type: str) -> str:
|
|
|
170
170
|
raise ValueError(f"no task-specific html template for: {task_type}") from exc
|
|
171
171
|
|
|
172
172
|
|
|
173
|
+
def execution_roles_from_manifest(manifest: dict) -> list[dict]:
|
|
174
|
+
"""Copy the exact RoleExecution set. Do not invent rows."""
|
|
175
|
+
if int(manifest.get("executionIdentityVersion") or 0) != 2:
|
|
176
|
+
return []
|
|
177
|
+
roles = []
|
|
178
|
+
for row in manifest.get("roleExecutions") or []:
|
|
179
|
+
if not isinstance(row, dict):
|
|
180
|
+
continue
|
|
181
|
+
roles.append({
|
|
182
|
+
"roleExecutionRef": row.get("roleExecutionRef"),
|
|
183
|
+
"role": row.get("role"),
|
|
184
|
+
"provider": row.get("provider"),
|
|
185
|
+
"modelRef": row.get("modelRef"),
|
|
186
|
+
"ordinal": row.get("ordinal"),
|
|
187
|
+
"executionLabel": row.get("executionLabel"),
|
|
188
|
+
"status": row.get("status"),
|
|
189
|
+
})
|
|
190
|
+
return roles
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def apply_execution_roles(data: dict, manifest: dict) -> dict:
|
|
194
|
+
"""Copy the manifest RoleExecution set onto a final-report payload."""
|
|
195
|
+
updated = dict(data)
|
|
196
|
+
if int(manifest.get("executionIdentityVersion") or 0) != 2:
|
|
197
|
+
return updated
|
|
198
|
+
updated["executionIdentityVersion"] = 2
|
|
199
|
+
updated["executionRoles"] = execution_roles_from_manifest(manifest)
|
|
200
|
+
return updated
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def assert_execution_roles_match_manifest(report: dict, manifest: dict) -> None:
|
|
204
|
+
report_refs = {row.get("roleExecutionRef") for row in report.get("executionRoles") or []}
|
|
205
|
+
manifest_refs = {
|
|
206
|
+
row.get("roleExecutionRef") for row in manifest.get("roleExecutions") or []
|
|
207
|
+
}
|
|
208
|
+
if report_refs != manifest_refs:
|
|
209
|
+
raise ValueError("report executionRoles must equal manifest roleExecutions")
|
|
210
|
+
|
|
211
|
+
|
|
173
212
|
def markdown_template_for(task_type: str) -> str:
|
|
174
213
|
try:
|
|
175
214
|
return TASK_TYPE_MARKDOWN_TEMPLATE[task_type]
|
|
@@ -38,6 +38,7 @@ from pathlib import Path
|
|
|
38
38
|
from typing import Any, Callable, Mapping, Sequence
|
|
39
39
|
|
|
40
40
|
from .agent_activity import ActivityProjectionError, project_agent_activity
|
|
41
|
+
from .report_contract import apply_execution_roles
|
|
41
42
|
from .dispatch_state import DispatchError, link_agent_dispatch_result
|
|
42
43
|
from .final_report_paths import final_report_data_path, final_report_markdown_path
|
|
43
44
|
from .paths import task_dir, task_manifest_file
|
|
@@ -388,6 +389,7 @@ def run_finalize(
|
|
|
388
389
|
steps: list[dict[str, Any]] = []
|
|
389
390
|
deferred = ""
|
|
390
391
|
try:
|
|
392
|
+
write_execution_roles(ctx)
|
|
391
393
|
commands = build_commands(ctx)
|
|
392
394
|
except FinalizeError as exc:
|
|
393
395
|
return {"ok": False, "reason": str(exc), "steps": steps}
|
|
@@ -465,6 +467,20 @@ def run_finalize(
|
|
|
465
467
|
return {"ok": True, "reason": "", "steps": steps}
|
|
466
468
|
|
|
467
469
|
|
|
470
|
+
def write_execution_roles(ctx: FinalizeContext) -> None:
|
|
471
|
+
"""Write the exact manifest role set onto the final-report data.json."""
|
|
472
|
+
if not ctx.data_path.is_file() or not ctx.manifest_path.is_file():
|
|
473
|
+
return
|
|
474
|
+
manifest = _load_manifest(ctx.manifest_path)
|
|
475
|
+
data = _load_manifest(ctx.data_path)
|
|
476
|
+
updated = apply_execution_roles(dict(data), dict(manifest))
|
|
477
|
+
if updated == data:
|
|
478
|
+
return
|
|
479
|
+
tmp = ctx.data_path.with_suffix(ctx.data_path.suffix + ".tmp")
|
|
480
|
+
tmp.write_text(json.dumps(updated, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
481
|
+
tmp.replace(ctx.data_path)
|
|
482
|
+
|
|
483
|
+
|
|
468
484
|
def _link_lead_result_for_validation(ctx: FinalizeContext) -> None:
|
|
469
485
|
"""Link the lead-authored synthesis artifact before Phase 7 validation."""
|
|
470
486
|
manifest = _load_manifest(ctx.manifest_path)
|
|
@@ -155,6 +155,7 @@ def render_v2_html_view(
|
|
|
155
155
|
# Every task type ends with the same run-cost section, so it is bound
|
|
156
156
|
# here rather than in ten view models that would each rebuild it.
|
|
157
157
|
"runUsage": run_usage(data),
|
|
158
|
+
"executionRoles": data.get("executionRoles") or [],
|
|
158
159
|
"css": (root / "html/assets/base.css").read_text(encoding="utf-8"),
|
|
159
160
|
"js": response_js + "\n" + base_js,
|
|
160
161
|
}
|