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
|
@@ -8,12 +8,13 @@ write scope in the order the CLIs are told it, and the role's idle budget.
|
|
|
8
8
|
"""
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
+
import json
|
|
11
12
|
import os
|
|
12
13
|
import shutil
|
|
13
14
|
import sys
|
|
14
15
|
from dataclasses import dataclass
|
|
15
16
|
from pathlib import Path
|
|
16
|
-
from typing import Any, Mapping
|
|
17
|
+
from typing import Any, Callable, Mapping
|
|
17
18
|
|
|
18
19
|
_HERE = Path(__file__).resolve().parent
|
|
19
20
|
# ``okstra_ctl`` sits beside this file in the repo (``scripts/``) but under
|
|
@@ -26,7 +27,13 @@ sys.path.insert(0, str(_HERE))
|
|
|
26
27
|
if _HOME_LIB.is_dir() and str(_HOME_LIB) not in sys.path:
|
|
27
28
|
sys.path.append(str(_HOME_LIB))
|
|
28
29
|
|
|
29
|
-
from okstra_ctl.domain.provider import
|
|
30
|
+
from okstra_ctl.domain.provider import ( # noqa: E402
|
|
31
|
+
ProviderSpec,
|
|
32
|
+
ServedModelAttestation,
|
|
33
|
+
ServedModelNormalizer,
|
|
34
|
+
UnknownProviderError,
|
|
35
|
+
)
|
|
36
|
+
from okstra_ctl.domain.role import normalize_role, role_for_duty # noqa: E402
|
|
30
37
|
from okstra_ctl.domain.worker_exec import ( # noqa: E402
|
|
31
38
|
ExecutionStrategy,
|
|
32
39
|
WorkerExecRequest,
|
|
@@ -36,15 +43,32 @@ from okstra_ctl.registry.provider_registry import ( # noqa: E402
|
|
|
36
43
|
)
|
|
37
44
|
from okstra_ctl.worker_request import build_request, idle_timeout # noqa: E402
|
|
38
45
|
from okstra_ctl.worker_runner import LIVE, QUIET, run_worker # noqa: E402
|
|
46
|
+
from okstra_ctl.dispatch_core import verify_served_model # noqa: E402
|
|
47
|
+
from okstra_ctl.dispatch_state import DispatchError # noqa: E402
|
|
48
|
+
from okstra_ctl.execution_manifest import read_execution_manifest # noqa: E402
|
|
49
|
+
from okstra_ctl.write_policy import ( # noqa: E402
|
|
50
|
+
WriteEnforcement,
|
|
51
|
+
WritePolicy,
|
|
52
|
+
write_enforcement_from_payload,
|
|
53
|
+
write_policy_from_payload,
|
|
54
|
+
)
|
|
55
|
+
from okstra_ctl.model_pool import ModelPool # noqa: E402
|
|
56
|
+
from okstra_ctl.agent_invocation import ( # noqa: E402
|
|
57
|
+
AgentInvocationError,
|
|
58
|
+
agent_model_assignment_from_payload,
|
|
59
|
+
invocation_metadata_identity,
|
|
60
|
+
)
|
|
39
61
|
|
|
40
62
|
_USAGE = (
|
|
41
63
|
"usage: okstra-provider-exec.py <provider> <project-root> "
|
|
42
64
|
"<model-execution-value> <prompt-path> [worktree-path] [role] "
|
|
43
|
-
"[idle-timeout-seconds] [--presentation live|quiet] [--session-id <uuid>]"
|
|
65
|
+
"[idle-timeout-seconds] [--presentation live|quiet] [--session-id <uuid>] "
|
|
66
|
+
"[--invocation-metadata path]"
|
|
44
67
|
)
|
|
45
68
|
|
|
46
69
|
_PRESENTATION_FLAG = "--presentation"
|
|
47
70
|
_SESSION_ID_FLAG = "--session-id"
|
|
71
|
+
_INVOCATION_METADATA_FLAG = "--invocation-metadata"
|
|
48
72
|
_PRESENTATIONS = (LIVE, QUIET)
|
|
49
73
|
|
|
50
74
|
|
|
@@ -62,6 +86,16 @@ class Invocation:
|
|
|
62
86
|
log_path: Path
|
|
63
87
|
status_path: Path
|
|
64
88
|
status_extra: Mapping[str, Any]
|
|
89
|
+
served_model_normalizer: ServedModelNormalizer | None
|
|
90
|
+
served_model_verifier: Callable[[ServedModelAttestation], str] | None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class _V2InvocationContext:
|
|
95
|
+
status_extra: Mapping[str, Any]
|
|
96
|
+
verify_attestation: Callable[[ServedModelAttestation], str]
|
|
97
|
+
write_policy: WritePolicy
|
|
98
|
+
write_enforcement: WriteEnforcement
|
|
65
99
|
|
|
66
100
|
|
|
67
101
|
def parse_invocation(argv: list[str]) -> Invocation:
|
|
@@ -70,6 +104,7 @@ def parse_invocation(argv: list[str]) -> Invocation:
|
|
|
70
104
|
# Empty unless the dispatcher issued one. Without it the CLI picks its own
|
|
71
105
|
# id and nothing downstream can map that session back to this worker.
|
|
72
106
|
positional, session_id = _take_flag(positional, _SESSION_ID_FLAG, "")
|
|
107
|
+
positional, metadata_raw = _take_flag(positional, _INVOCATION_METADATA_FLAG, "")
|
|
73
108
|
if not 4 <= len(positional) <= 7:
|
|
74
109
|
raise PreflightError(64, _USAGE)
|
|
75
110
|
provider_id, project_root_raw, model, prompt_raw = positional[:4]
|
|
@@ -77,7 +112,6 @@ def parse_invocation(argv: list[str]) -> Invocation:
|
|
|
77
112
|
role = positional[5] if len(positional) >= 6 and positional[5] else "worker"
|
|
78
113
|
timeout_raw = positional[6] if len(positional) >= 7 and positional[6] else ""
|
|
79
114
|
|
|
80
|
-
spec = _provider_spec(provider_id)
|
|
81
115
|
project_root = _existing_dir(project_root_raw, 65, "project-root")
|
|
82
116
|
if not model:
|
|
83
117
|
raise PreflightError(66, "model-execution-value is empty")
|
|
@@ -86,6 +120,32 @@ def parse_invocation(argv: list[str]) -> Invocation:
|
|
|
86
120
|
worktree = (
|
|
87
121
|
_existing_dir(worktree_raw, 68, "worktree-path") if worktree_raw else None
|
|
88
122
|
)
|
|
123
|
+
spec = _provider_spec(provider_id)
|
|
124
|
+
status_extra: dict[str, Any] = {"wrapper": spec.wrapper, "role": role}
|
|
125
|
+
served_model_normalizer: ServedModelNormalizer | None = None
|
|
126
|
+
served_model_verifier: Callable[[ServedModelAttestation], str] | None = None
|
|
127
|
+
write_policy: WritePolicy | None = None
|
|
128
|
+
write_enforcement: WriteEnforcement | None = None
|
|
129
|
+
if metadata_raw:
|
|
130
|
+
context = _v2_status_extra(
|
|
131
|
+
metadata_path=_existing_file(
|
|
132
|
+
metadata_raw, 64, "invocation-metadata"
|
|
133
|
+
),
|
|
134
|
+
project_root=project_root,
|
|
135
|
+
prompt_path=prompt_path,
|
|
136
|
+
provider_id=provider_id,
|
|
137
|
+
model=model,
|
|
138
|
+
role=role,
|
|
139
|
+
)
|
|
140
|
+
status_extra.update(context.status_extra)
|
|
141
|
+
served_model_normalizer = spec.served_model_normalizer
|
|
142
|
+
served_model_verifier = context.verify_attestation
|
|
143
|
+
write_policy = context.write_policy
|
|
144
|
+
write_enforcement = context.write_enforcement
|
|
145
|
+
if not spec.supports_role(role):
|
|
146
|
+
raise PreflightError(
|
|
147
|
+
64, f"provider {provider_id!r} does not support role {role!r}"
|
|
148
|
+
)
|
|
89
149
|
|
|
90
150
|
request = build_request(
|
|
91
151
|
prompt_text=prompt_path.read_text(encoding="utf-8"),
|
|
@@ -95,6 +155,8 @@ def parse_invocation(argv: list[str]) -> Invocation:
|
|
|
95
155
|
role=role,
|
|
96
156
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
97
157
|
session_id=session_id,
|
|
158
|
+
write_policy=write_policy,
|
|
159
|
+
write_enforcement=write_enforcement,
|
|
98
160
|
)
|
|
99
161
|
strategy = spec.exec_strategy
|
|
100
162
|
_check_command(strategy, request)
|
|
@@ -104,7 +166,9 @@ def parse_invocation(argv: list[str]) -> Invocation:
|
|
|
104
166
|
presentation=presentation,
|
|
105
167
|
log_path=_log_path(prompt_path),
|
|
106
168
|
status_path=Path(f"{prompt_path}.status.json"),
|
|
107
|
-
status_extra=
|
|
169
|
+
status_extra=status_extra,
|
|
170
|
+
served_model_normalizer=served_model_normalizer,
|
|
171
|
+
served_model_verifier=served_model_verifier,
|
|
108
172
|
)
|
|
109
173
|
|
|
110
174
|
|
|
@@ -149,6 +213,152 @@ def _take_presentation(argv: list[str]) -> tuple[list[str], str]:
|
|
|
149
213
|
return positional, presentation
|
|
150
214
|
|
|
151
215
|
|
|
216
|
+
def _v2_status_extra(
|
|
217
|
+
*,
|
|
218
|
+
metadata_path: Path,
|
|
219
|
+
project_root: Path,
|
|
220
|
+
prompt_path: Path,
|
|
221
|
+
provider_id: str,
|
|
222
|
+
model: str,
|
|
223
|
+
role: str,
|
|
224
|
+
) -> _V2InvocationContext:
|
|
225
|
+
try:
|
|
226
|
+
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
|
|
227
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
228
|
+
raise PreflightError(64, "invocation metadata is invalid") from exc
|
|
229
|
+
if not isinstance(metadata, dict):
|
|
230
|
+
raise PreflightError(64, "invocation metadata must be an object")
|
|
231
|
+
try:
|
|
232
|
+
identity = invocation_metadata_identity(metadata)
|
|
233
|
+
assignment = agent_model_assignment_from_payload(
|
|
234
|
+
metadata.get("modelAssignment")
|
|
235
|
+
)
|
|
236
|
+
except AgentInvocationError as exc:
|
|
237
|
+
raise PreflightError(64, str(exc)) from exc
|
|
238
|
+
if identity is None:
|
|
239
|
+
raise PreflightError(64, "new worker dispatch requires v2 invocation metadata")
|
|
240
|
+
if (
|
|
241
|
+
assignment.provider != provider_id
|
|
242
|
+
or assignment.model_execution_value != model
|
|
243
|
+
):
|
|
244
|
+
raise PreflightError(64, "invocation metadata model assignment does not match wrapper")
|
|
245
|
+
try:
|
|
246
|
+
expected_role = role_for_duty(identity.duty_id)
|
|
247
|
+
actual_role = normalize_role(role)
|
|
248
|
+
except ValueError as exc:
|
|
249
|
+
raise PreflightError(64, f"invocation metadata role is invalid: {exc}") from exc
|
|
250
|
+
if expected_role != actual_role:
|
|
251
|
+
raise PreflightError(64, "invocation metadata duty does not match role")
|
|
252
|
+
prompt = metadata.get("prompt")
|
|
253
|
+
recorded_path = prompt.get("path") if isinstance(prompt, Mapping) else None
|
|
254
|
+
if not isinstance(recorded_path, str):
|
|
255
|
+
raise PreflightError(64, "invocation metadata prompt path is invalid")
|
|
256
|
+
recorded = Path(recorded_path)
|
|
257
|
+
if not recorded.is_absolute():
|
|
258
|
+
recorded = project_root / recorded
|
|
259
|
+
if recorded.resolve(strict=False) != prompt_path.resolve(strict=True):
|
|
260
|
+
raise PreflightError(64, "invocation metadata prompt does not match wrapper")
|
|
261
|
+
role_execution, invocation, pool = _execution_authority(
|
|
262
|
+
metadata=metadata,
|
|
263
|
+
project_root=project_root,
|
|
264
|
+
role_execution_ref=identity.role_execution_ref,
|
|
265
|
+
invocation_ref=identity.invocation_ref,
|
|
266
|
+
)
|
|
267
|
+
binding = role_execution.binding
|
|
268
|
+
if (
|
|
269
|
+
role_execution.participant_ref != identity.participant_ref
|
|
270
|
+
or role_execution.role != expected_role
|
|
271
|
+
or role_execution.provider != assignment.provider
|
|
272
|
+
or binding is None
|
|
273
|
+
or binding.runner != assignment.runner
|
|
274
|
+
or binding.resolved_execution_value != assignment.model_execution_value
|
|
275
|
+
):
|
|
276
|
+
raise PreflightError(
|
|
277
|
+
64, "invocation metadata role execution binding does not match run manifest"
|
|
278
|
+
)
|
|
279
|
+
if role_execution.execution_label != identity.execution_label:
|
|
280
|
+
raise PreflightError(
|
|
281
|
+
64, "invocation metadata execution label does not match run manifest"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def verify_attestation(attestation: ServedModelAttestation) -> str:
|
|
285
|
+
try:
|
|
286
|
+
verify_served_model(role_execution, attestation, pool=pool)
|
|
287
|
+
except DispatchError as exc:
|
|
288
|
+
return str(exc)
|
|
289
|
+
return ""
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
write_policy = write_policy_from_payload(invocation.write_policy)
|
|
293
|
+
write_enforcement = write_enforcement_from_payload(
|
|
294
|
+
invocation.write_enforcement
|
|
295
|
+
)
|
|
296
|
+
except ValueError as exc:
|
|
297
|
+
raise PreflightError(64, f"invocation write contract is invalid: {exc}") from exc
|
|
298
|
+
|
|
299
|
+
return _V2InvocationContext({
|
|
300
|
+
"schemaVersion": "2.0",
|
|
301
|
+
"executionIdentityVersion": 2,
|
|
302
|
+
"participantRef": identity.participant_ref,
|
|
303
|
+
"roleExecutionRef": identity.role_execution_ref,
|
|
304
|
+
"executionLabel": identity.execution_label,
|
|
305
|
+
"dutyId": identity.duty_id,
|
|
306
|
+
"invocationRef": identity.invocation_ref,
|
|
307
|
+
"attempt": identity.attempt,
|
|
308
|
+
"servedModelAttestation": {
|
|
309
|
+
"observedModel": None,
|
|
310
|
+
"normalizedModelRef": None,
|
|
311
|
+
"level": "unknown",
|
|
312
|
+
"source": "unavailable",
|
|
313
|
+
},
|
|
314
|
+
"writePolicyDigest": invocation.write_policy_digest,
|
|
315
|
+
"writeEnforcement": write_enforcement.to_payload(),
|
|
316
|
+
}, verify_attestation, write_policy, write_enforcement)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _execution_authority(
|
|
320
|
+
*,
|
|
321
|
+
metadata: Mapping[str, Any],
|
|
322
|
+
project_root: Path,
|
|
323
|
+
role_execution_ref: str,
|
|
324
|
+
invocation_ref: str,
|
|
325
|
+
):
|
|
326
|
+
source = metadata.get("contractSource")
|
|
327
|
+
manifest_raw = source.get("runManifestPath") if isinstance(source, Mapping) else None
|
|
328
|
+
if not isinstance(manifest_raw, str) or not manifest_raw:
|
|
329
|
+
raise PreflightError(64, "invocation metadata run manifest is invalid")
|
|
330
|
+
manifest_path = Path(manifest_raw)
|
|
331
|
+
if not manifest_path.is_absolute():
|
|
332
|
+
manifest_path = project_root / manifest_path
|
|
333
|
+
try:
|
|
334
|
+
manifest_path = manifest_path.resolve(strict=True)
|
|
335
|
+
manifest_path.relative_to(project_root.resolve(strict=True))
|
|
336
|
+
manifest = read_execution_manifest(manifest_path)
|
|
337
|
+
except (OSError, ValueError) as exc:
|
|
338
|
+
raise PreflightError(64, "invocation metadata run manifest is invalid") from exc
|
|
339
|
+
role_execution = next((
|
|
340
|
+
row for row in manifest.role_executions
|
|
341
|
+
if row.role_execution_ref == role_execution_ref
|
|
342
|
+
), None)
|
|
343
|
+
if role_execution is None:
|
|
344
|
+
raise PreflightError(
|
|
345
|
+
64, "invocation metadata role execution does not match run manifest"
|
|
346
|
+
)
|
|
347
|
+
invocation = next((
|
|
348
|
+
row for row in manifest.invocations
|
|
349
|
+
if row.invocation_ref == invocation_ref
|
|
350
|
+
), None)
|
|
351
|
+
if invocation is None or invocation.role_execution_ref != role_execution_ref:
|
|
352
|
+
raise PreflightError(
|
|
353
|
+
64, "invocation metadata invocation does not match run manifest"
|
|
354
|
+
)
|
|
355
|
+
return (
|
|
356
|
+
role_execution,
|
|
357
|
+
invocation,
|
|
358
|
+
ModelPool.from_registry(default_provider_registry()),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
152
362
|
def _provider_spec(provider_id: str) -> ProviderSpec:
|
|
153
363
|
try:
|
|
154
364
|
spec = default_provider_registry().resolve(provider_id)
|
|
@@ -214,6 +424,8 @@ def main(argv: list[str]) -> int:
|
|
|
214
424
|
log_path=invocation.log_path,
|
|
215
425
|
status_path=invocation.status_path,
|
|
216
426
|
status_extra=invocation.status_extra,
|
|
427
|
+
served_model_normalizer=invocation.served_model_normalizer,
|
|
428
|
+
served_model_verifier=invocation.served_model_verifier,
|
|
217
429
|
)
|
|
218
430
|
except PreflightError as exc:
|
|
219
431
|
print(f"okstra-provider-exec: {exc}", file=sys.stderr)
|
|
@@ -50,6 +50,7 @@ from pathlib import Path
|
|
|
50
50
|
|
|
51
51
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
52
52
|
|
|
53
|
+
from okstra_ctl import next_phase # noqa: E402
|
|
53
54
|
from okstra_ctl.final_report_paths import final_report_markdown_path # noqa: E402
|
|
54
55
|
from okstra_ctl.paths import task_manifest_file # noqa: E402
|
|
55
56
|
from okstra_ctl.workflow import PHASE_SEQUENCE # noqa: E402
|
|
@@ -199,10 +200,11 @@ def _spawn_one(
|
|
|
199
200
|
"workflow": {
|
|
200
201
|
"currentPhase": suggested,
|
|
201
202
|
"currentPhaseState": "not-started",
|
|
202
|
-
"nextRecommendedPhase":
|
|
203
|
+
"nextRecommendedPhase": next_phase.make(
|
|
204
|
+
suggested, next_phase.STATUS_READY, "follow-up 으로 생성됨"
|
|
205
|
+
),
|
|
203
206
|
"phaseStates": {},
|
|
204
207
|
"awaitingApproval": False,
|
|
205
|
-
"routingStatus": "follow-up-spawned",
|
|
206
208
|
},
|
|
207
209
|
}
|
|
208
210
|
_write_manifest(task_manifest_file(task_root), manifest_payload)
|
package/runtime/bin/okstra.sh
CHANGED
|
@@ -222,6 +222,16 @@ PY_ARGS=(
|
|
|
222
222
|
[[ -n "${DIRECTIVE-}" ]] && PY_ARGS+=(--directive "$DIRECTIVE")
|
|
223
223
|
[[ -n "${FIX_CYCLE-}" ]] && PY_ARGS+=(--fix-cycle "$FIX_CYCLE")
|
|
224
224
|
[[ -n "${WORKERS_OVERRIDE-}" ]] && PY_ARGS+=(--workers "$WORKERS_OVERRIDE")
|
|
225
|
+
for role_count in "${ROLE_COUNTS[@]-}"; do
|
|
226
|
+
[[ -z "$role_count" ]] && continue
|
|
227
|
+
PY_ARGS+=(--role-count "$role_count")
|
|
228
|
+
done
|
|
229
|
+
for role_model in "${ROLE_MODELS[@]-}"; do
|
|
230
|
+
[[ -z "$role_model" ]] && continue
|
|
231
|
+
PY_ARGS+=(--role-model "$role_model")
|
|
232
|
+
done
|
|
233
|
+
[[ -n "${HOST_SESSION_CONTEXT_JSON-}" ]] && \
|
|
234
|
+
PY_ARGS+=(--host-session-context-json "$HOST_SESSION_CONTEXT_JSON")
|
|
225
235
|
[[ -n "${LEAD_PROVIDER_OVERRIDE-}" ]] && PY_ARGS+=(--lead-provider "$LEAD_PROVIDER_OVERRIDE")
|
|
226
236
|
[[ -n "${LEAD_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--lead-model "$LEAD_MODEL_OVERRIDE")
|
|
227
237
|
[[ -n "${CLAUDE_MODEL_OVERRIDE-}" ]] && PY_ARGS+=(--claude-model "$CLAUDE_MODEL_OVERRIDE")
|
|
@@ -18,11 +18,11 @@ For a new `implementation-planning` run, the plan-body sequence is initial verif
|
|
|
18
18
|
{{PHASE_ALLOWED_OUTPUTS}}
|
|
19
19
|
- Forbidden actions in this phase:
|
|
20
20
|
{{PHASE_FORBIDDEN_ACTIONS}}
|
|
21
|
-
- This run executes `{{WORKFLOW_CURRENT_PHASE}}` only. Do not start
|
|
21
|
+
- This run executes `{{WORKFLOW_CURRENT_PHASE}}` only. Do not start any later phase inside this run, even if the user says "proceed to the next step" or similar. Which phase comes next is not decided yet — this run's final report decides it.
|
|
22
22
|
{{STAGE_BATCH_DIRECTIVE}}
|
|
23
23
|
{{VERIFICATION_TARGET}}
|
|
24
24
|
{{STAGE_INTEGRATION}}
|
|
25
|
-
- Phase advancement requires a new okstra invocation launched with `--task-type
|
|
25
|
+
- Phase advancement requires a new okstra invocation, launched with an explicit `--task-type` after this run's final report is written and approved. The target of that run comes from the pointer this run's report authors into `workflow.nextRecommendedPhase`, and only when that pointer's `status` says it can be started. The lead must not write source code, run builds/migrations/deployments, or otherwise produce artifacts of a different phase from inside this run.
|
|
26
26
|
- See `Lifecycle Phase Boundaries` in the lifecycle core contract (`{{OKSTRA_LEAD_CONTRACT_PATH}}`) for the canonical rules and the phase-transition checklist.
|
|
27
27
|
|
|
28
28
|
{{TEAM_CREATION_GATE}}
|
|
@@ -46,9 +46,8 @@
|
|
|
46
46
|
| `workflow.currentPhaseState` | Current lifecycle phase state |
|
|
47
47
|
| `workflow.phaseStates` | Phase-by-phase lifecycle state map |
|
|
48
48
|
| `workflow.lastCompletedPhase` | Last completed lifecycle phase |
|
|
49
|
-
| `workflow.nextRecommendedPhase` | Next
|
|
49
|
+
| `workflow.nextRecommendedPhase` | Next-Phase Pointer — an object carrying the target phase, whether it can be started, and why. A projection of this report's Phase Routing; not authored here. Written per the Artifact Persistence Checklist in [report-writer](./report-writer.md); do not re-derive its rules here. |
|
|
50
50
|
| `workflow.awaitingApproval` | Approval wait marker |
|
|
51
|
-
| `workflow.routingStatus` | Routing decision status |
|
|
52
51
|
| `workflow.lastSafeCheckpoint` | Safe resume checkpoint metadata |
|
|
53
52
|
| `instructionSetPath` | Path to the `instruction-set/` **directory** containing `analysis-packet.md`, `analysis-profile.md`, `analysis-material.md`, `reference-expectations.md`, `task-brief.md`, `final-report-template.md`, and — only for task types whose host orchestration carries gates — `host-orchestration-rules.md` (see Step 4). Not a single-file path. |
|
|
54
53
|
| `referenceExpectationsPath` | config/deployment expectation artifact path |
|
|
@@ -84,7 +84,7 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
|
|
|
84
84
|
- Same semantics but disjoint ticket sets → separate groups (do NOT over-merge across tickets).
|
|
85
85
|
- Only one worker confirms a finding → one single-source group.
|
|
86
86
|
4. When grouping is ambiguous, prefer splitting over merging (avoid over-merging). Semantic matching, ticket-set equality, and evidence interpretation remain lead judgments; the engine does not perform fuzzy matching or decide whether evidence is credible.
|
|
87
|
-
5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and
|
|
87
|
+
5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and the version-selected convergence-groups schema enforce the required array field and reject non-string or blank entries while allowing the empty array. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. In a v2 run, set top-level `schemaVersion: "2.0"`, `executionIdentityVersion: 2`, and `runManifestPath` to the current run manifest's exact canonical project-relative path. Write the groups artifact under that same resolved run directory's `state/` directory. Every v2 worker row also carries the paired `participantRef` and `sourceRoleExecutionRef` from that run manifest's canonical role state. Set `sourceRoleExecutionRef` to the selected source `RoleExecution` row's `roleExecutionRef`, not that row's `sourceRoleExecutionRef` field; a static source row has null in the latter field. Copy `participantRef` from that same selected row. Never derive those references from the worker name, provider, model, or execution label. A legacy v1 document keeps `schemaVersion: "1.0"` and omits `executionIdentityVersion`, `runManifestPath`, and both worker reference fields. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` and selects an `analyser`, `designer`, `planner`, or `verifier` source role. Only the report author uses `report-writer`, paired with a `report-writer` source role. There is no `implementation-verifier` audience here; map an implementation verifier to `analysis`.
|
|
88
88
|
6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` classifies Round 0 by mode:
|
|
89
89
|
- Collaborative mode: multi-source groups become `full-consensus` immediately; only single-source groups enter the working queue.
|
|
90
90
|
- Adversarial mode: every finding enters the working queue regardless of source count. Semantic grouping merges provenance only; it does not decide a finding is reliable.
|
|
@@ -106,8 +106,8 @@ convergence-<task-type>-<seq>.json
|
|
|
106
106
|
|
|
107
107
|
Follow this protocol exactly:
|
|
108
108
|
|
|
109
|
-
0.
|
|
110
|
-
1. Run `okstra convergence seed --groups <groups> --work-state <work> --final-state <final> --migration-dir <state/migrations
|
|
109
|
+
0. Version-selected schemas describe what the reducer reads: `schemas/convergence-groups-v1.0.schema.json` accepts only legacy groups, `schemas/convergence-groups-v2.0.schema.json` accepts only explicit v2 execution identity, and `schemas/convergence-round-results-v1.0.schema.json` feeds step 4's `apply-round --results`. `schemas/convergence-critic-results-v1.0.schema.json` is a fourth shape but **not** a reducer input — it describes the critic worker's own result document. Step 6's `apply-critic-gaps --results` takes the coverage batch you assemble from those candidates plus each analyser's vote (`{schemaVersion, taskKey, mode, provider, modelExecutionValue, dispatches[], gaps[]}`, spelled out in §"Coverage critic pass" §"State"); feeding the critic document straight in is rejected, by design. `okstra convergence example --kind <groups|round-results|critic-results>` prints a deterministic valid v1 instance of each and writes only JSON to stdout.
|
|
110
|
+
1. Run `okstra convergence seed --groups <groups> --run-manifest <current-run-manifest> --work-state <work> --final-state <final> --migration-dir <state/migrations>` for a v2 worker roster. `--run-manifest` must be the exact current manifest named by the groups document's `runManifestPath`; a previous run from the same task is not interchangeable. Omit the flag for a legacy v1 roster. A `reuse-final` action means validate the existing final and continue to Phase 6. `create-work`, `resume-work`, and `restart-round0` continue with planning.
|
|
111
111
|
2. Run `okstra convergence plan-round --work-state <work> --plan <round-plan>`. This is read-only with respect to the working state.
|
|
112
112
|
3. When the plan action is `dispatch`, create exactly one reverify prompt for each `dispatches[]` row and dispatch it through the selected runtime adapter. Its findings are exactly that row's `findingIds`.
|
|
113
113
|
4. Convert parsed verdicts and every terminal dispatch outcome into `convergence-round-<N>-results-<task-type>-<seq>.json`; then run `okstra convergence apply-round --work-state <work> --plan <round-plan> --results <round-results>`.
|
|
@@ -228,21 +228,30 @@ Design intent: one `counter-evidence` refute denies a claim consensus (it cannot
|
|
|
228
228
|
|
|
229
229
|
For every finding reverify row and critic-gap verification row, first write a
|
|
230
230
|
call-specific task-instructions file under the current run's `state/`
|
|
231
|
-
directory.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
231
|
+
directory. For a v2 run, take `participantRef` and
|
|
232
|
+
`sourceRoleExecutionRef` from the canonical round-plan dispatch row or the
|
|
233
|
+
matching canonical working-state worker row. That stored reference is the
|
|
234
|
+
selected source `RoleExecution` row's `roleExecutionRef`, not that row's
|
|
235
|
+
`sourceRoleExecutionRef` field. Then run `okstra agent-prompt
|
|
236
|
+
materialize` with `--audience
|
|
237
|
+
reverification-worker`, `--assignment-ref reverify/<workerId>`,
|
|
238
|
+
`--source-role-execution-ref
|
|
239
|
+
<sourceRoleExecutionRef>`, the exact `--worker-id`, `--dispatch-kind
|
|
240
|
+
reverify-r<N>`, and the authorized prompt/result/audit paths. Do not derive the
|
|
241
|
+
source role execution from `workerId`, provider, model, or execution-label
|
|
242
|
+
text. A legacy v1 run omits `--source-role-execution-ref`; the materializer
|
|
243
|
+
keeps its legacy read-only identity resolution for that schema version. The
|
|
244
|
+
returned `promptPath` is the only body that may be dispatched; do not append
|
|
245
|
+
role prose or reconstruct model headers after materialization.
|
|
246
|
+
|
|
247
|
+
If the dispatch gate then rejects that prompt, preserve its prompt, metadata,
|
|
248
|
+
reservation, and append-only Invocation bytes. A v2 reverify correction uses a
|
|
249
|
+
fresh `--invocation-id` and fresh prompt/metadata path; the materializer rejects
|
|
250
|
+
`--replace-undispatched` for this dynamic identity path. A legacy v1 correction
|
|
251
|
+
may fix the task-instructions file and re-run the same `materialize` call with
|
|
252
|
+
`--replace-undispatched` while keeping the `--invocation-id`. Once any dispatch
|
|
253
|
+
row names the invocation, replacement is rejected even for v1 and the prompt is
|
|
254
|
+
history. Do not delete prompt, metadata, or reservation files by hand.
|
|
246
255
|
|
|
247
256
|
Run `okstra agent-prompt verify --run-manifest <path> --metadata
|
|
248
257
|
<metadataPath> --json` immediately before dispatch. A failed verification is a
|
|
@@ -44,9 +44,10 @@ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule-gen`)
|
|
|
44
44
|
|
|
45
45
|
## Core operating contract
|
|
46
46
|
|
|
47
|
-
- The `
|
|
48
|
-
-
|
|
49
|
-
-
|
|
47
|
+
- The `leader` owns orchestration, convergence supervision, and final-report review/approval. It does not author the final-report file when `Report writer worker` is in the roster. `lead` is a compatibility alias for `leader` and must not be written on new artifacts.
|
|
48
|
+
- Dispatch consumes stored role executions, not provider-named worker IDs. Canonical roles are `leader`, `analyser`, `critic`, `designer`, `planner`, `implementer`, `verifier`, `report-writer`, and `translator`. `executor` is a compatibility alias for `implementer`.
|
|
49
|
+
- Pane titles and operational rows use the stored `executionLabel`. Do not rebuild that label from a provider name or model string.
|
|
50
|
+
- `report-writer`, when in the roster, is the **author** of the final-report file. Lead reviews the draft and may request a revision via a follow-up dispatch, but MUST NOT write the report itself as a "shortcut". The only legal lead-authored fallback is when a Report writer worker dispatch was actually attempted and recorded a terminal status of `error`/`timeout`/`not-run` with an explicit reason in team-state — see [report-writer](./report-writer.md) "Lead-authored fallback".
|
|
50
51
|
- "Session resume", "team is no longer alive", and similar are NOT valid reasons to skip Report writer worker dispatch — see [report-writer](./report-writer.md) "Resume-safe dispatch".
|
|
51
52
|
- A shell command the lead runs must not be able to ask a question. The lead's shell is the user's own, where `cp`, `mv`, and `rm` are commonly aliased to their `-i` form; the confirmation that alias raises has nobody to answer it, so the call hangs until it is killed — observed as a `cp` over an existing state file stalling a whole self-fix round. Invoke these as `command cp` / `command mv` / `command rm`, which skips alias expansion and leaves the tool's own behaviour untouched. `-f` is not a substitute: it changes what the tool does on failure (`rm -f` reports success on a path that never existed).
|
|
52
53
|
- If the brief is incomplete, continue with explicit uncertainty markers rather than fabricating confidence.
|
|
@@ -71,14 +72,14 @@ Phase-transition checklist (lead, end of run):
|
|
|
71
72
|
|
|
72
73
|
1. Confirm the current phase's required outputs are complete and recorded in the final report.
|
|
73
74
|
2. Set `workflow.phaseStates.<currentPhase>.state = "completed"` in `task-manifest.json` (validator does this when the run passes; verify the value).
|
|
74
|
-
3. Update `workflow.lastCompletedPhase
|
|
75
|
+
3. Record **Phase Routing** in the final report — it is the source the Next-Phase Pointer is projected from, and Phase 7 validation recomputes the pointer from it. Update `workflow.lastCompletedPhase`. The Next-Phase Pointer (`workflow.nextRecommendedPhase`) itself is written per the Artifact Persistence Checklist in [report-writer](./report-writer.md), which owns its shape and its rules.
|
|
75
76
|
4. **Do NOT start the next phase inside the current run.** A new okstra invocation with the new `--task-type` is the only legal way to advance.
|
|
76
77
|
|
|
77
78
|
User-utterance interpretation rule:
|
|
78
79
|
|
|
79
80
|
- "proceed to the next step" / "move on to the next step" / equivalent phrases are scoped to **the current phase only**. Interpret them as "produce the remaining outputs of the current phase," never as "start the next lifecycle phase."
|
|
80
81
|
- If the current phase's outputs are already complete and the user clearly wants to advance, reply with the phase-transition checklist above and the exact next-run command. Wait for explicit user confirmation before any action that belongs to the next phase.
|
|
81
|
-
- If `nextRecommendedPhase` is `implementation-planning`, the next run produces a **plan**, not code. The next run after that is `implementation`.
|
|
82
|
+
- If the Next-Phase Pointer target (`nextRecommendedPhase.phase`) is `implementation-planning`, the next run produces a **plan**, not code. The next run after that is `implementation`.
|
|
82
83
|
|
|
83
84
|
## Progress reporting (BLOCKING)
|
|
84
85
|
|
|
@@ -202,7 +202,14 @@ Before each verifier call, write one task-instructions file under the current
|
|
|
202
202
|
run's `state/` directory and run `okstra agent-prompt materialize` with
|
|
203
203
|
`--audience reverification-worker`,
|
|
204
204
|
`--assignment-ref reverify/<workerId>`, the exact `--worker-id`, and
|
|
205
|
-
`--dispatch-kind reverify-r<N>`.
|
|
205
|
+
`--dispatch-kind reverify-r<N>`. For a v2 run, select the verifier's canonical
|
|
206
|
+
source `RoleExecution` row from the run manifest's static role state. Use its
|
|
207
|
+
`participantRef`, and set `sourceRoleExecutionRef` to the selected source
|
|
208
|
+
`RoleExecution` row's `roleExecutionRef`, not that row's
|
|
209
|
+
`sourceRoleExecutionRef` field. Pass `--source-role-execution-ref
|
|
210
|
+
<sourceRoleExecutionRef>`. Do not derive that reference from `workerId`,
|
|
211
|
+
provider, model, or execution-label text. A legacy v1 run omits this flag. Run
|
|
212
|
+
`okstra agent-prompt verify` against the
|
|
206
213
|
returned `metadataPath` before dispatch and use the returned `promptPath`
|
|
207
214
|
without modification. Native-session calls use only `hostModelValue`; before
|
|
208
215
|
the host primitive, run `okstra agent-prompt record-dispatch` with the project
|
|
@@ -268,9 +275,9 @@ round before any host or provider process starts.
|
|
|
268
275
|
- **Round completion.** A round is complete only after the renderer has run on the corrected data.json, lead has appended the round to the state file per step 6, lead has reconciled instructed groups against applied corrections — every `itemIds` entry either carries a `selfFixNote` or is still recorded as broken — **`okstra plan-verify --report <report>` exits 0** (step 5), and lead has then set that round's immutable `completedAt`. A round left with a non-zero exit carries its defect into the next round's inputs, which is how a mis-scored gate survives a whole self-fix budget. A round that was instructed but never rendered has not happened, and counting it inflates the budget that gates promotion. The state-file append is not optional bookkeeping: the next re-verification overwrites data.json's `planItems[].verdicts`, so a round that never reached `roundHistory[]` leaves no record anywhere of what it blocked on — which is the whole reason this file exists. **Enforced:** `validators/validate-run.py` `_validate_plan_body_state_rounds` requires one `roundHistory[]` entry per round `1..roundCount`, each carrying its own `gateResult` and cited by at least one item's `rounds[]`, and requires the file's `selfFixRoundsApplied` to match the report's. For a resolved correctness-critical user response, `_validate_target_round_causality` additionally requires the cited round's `completedAt` to be after every linked canonical `user-decision-required` event and no later than every linked canonical `user-decision-evaluated` event.
|
|
269
276
|
- **Loop termination.** Lead — not the report-writer worker — records the round count in `planBodyVerification.selfFixRoundsApplied` at each round's end, and why the loop stopped in `planBodyVerification.selfFixStopReason`. The count must equal the highest `round` in `selfFixGroups[]`, so it is derivable from recorded work rather than self-reported. A user-directed correction does not consume the automatic self-fix limit, and a verification failure after that correction does not restart the automatic loop:
|
|
270
277
|
- `all-resolved` — no planner-fixable `majority-disagree` item remains. Exit.
|
|
271
|
-
- `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning.
|
|
278
|
+
- `no-progress` — the round resolved **zero** planner-fixable items relative to the previous round. Exit even with budget left: the same rewrite would repeat. Newly *introduced* defects count against progress, so a rewrite that trades one defect for another stops the loop rather than churning. A round that re-targets only what the previous round left unresolved is this same conclusion reached one dispatch earlier — exit on it under this reason rather than paying for the round that proves it. **Enforced (advisory):** `validators/validate-run.py` `_detect_self_fix_recurrence` warns on that shape and names this stop reason.
|
|
272
279
|
- `max-rounds-reached` — `selfFixRoundsApplied == selfFixMaxRounds`. Exit.
|
|
273
|
-
- `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run MUST NOT emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced:** `validators/validate-run.py` `_validate_activity_contract_plan_limits`.
|
|
280
|
+
- `cause-group-recurrence` — legacy read-only value for reports produced before activity contract v1. A new activity-contract-v1 run MUST NOT emit it because there is no second automatic self-fix round in which a cause group can recur. **Enforced:** `validators/validate-run.py` `_validate_activity_contract_plan_limits`. A legacy report that already carries it is still an *exhausted* loop, so step 8 may promote its surviving items: `_SELF_FIX_EXHAUSTED_REASONS` admits this value alongside `no-progress` / `max-rounds-reached`. Refusing it there left such a report with no exit at all — the loop may not run again, and the item may not be promoted either.
|
|
274
281
|
- `not-attempted` — the loop never ran because no item qualified.
|
|
275
282
|
The `no-progress` and `max-rounds-reached` exits are what make the loop terminate; `selfFixMaxRounds` alone is the backstop.
|
|
276
283
|
- a `majority-disagree` item with a majority of its deciding `DISAGREE` votes at `needs-user-input` is NOT a self-fix target — after correctness-critical precedence, it goes straight to the next step as `user-decision` rather than generic `noncritical-dissent`. **Enforced:** `validators/validate-run.py` `_expected_approval_classification`.
|
|
@@ -357,6 +357,8 @@ Every field MUST anchor its claim with at least one evidence reference — a `pa
|
|
|
357
357
|
2. **Evidence and Detailed Analysis** — primary evidence rows (file path, line, snippet); secondary evidence / alternate interpretations. If `reference-expectations.md` lists explicit expected values, record match/gap per row.
|
|
358
358
|
- **Error-analysis diagnosis and routing.** When `header.taskType` is `error-analysis`, populate the required `errorAnalysis` object. Copy `errorAnalysis.symptomVerbatim` byte-for-byte from the symptom stated in the brief's `Source Material`; do not paraphrase it. Every `causeCandidates[]` row includes the full `supportingEvidence`, `falsifyingEvidenceChecked`, `confidence`, and `disproveWith` fields. When a candidate is a step in a propagation chain rather than a competing explanation — the analysis calls it a downstream step, a second stage, or a consequence of another candidate — set its `downstreamOf` to the ids of the candidates immediately upstream of it; leave the field absent for a candidate that stands on its own. Every id listed MUST be another candidate in the same report, no row may name itself, and the links MUST NOT form a cycle; `validators/validate-run.py::_validate_cause_chain` rejects all three. This is the only place the chain is machine-readable — prose calling a candidate "the second step of the chain" while `downstreamOf` is absent leaves the report's figure claiming the candidates are alternatives. Route `errorAnalysis.routing.nextTaskType=implementation-option-selection` with `direction=begin-option-selection`, or route `errorAnalysis.routing.nextTaskType=error-analysis` with `direction=continue-investigation`; no other pairing is valid. `verdictCard.nextStep`, `finalVerdict.nextStep`, the first `recommendedNextSteps` action and command, and the unique `followUpTasks` row whose `origin` is `phase-continuation` MUST all point to the same `errorAnalysis.routing.nextTaskType` target. The schema enforces only the presence of a `phase-continuation` row; `validators/validate-run.py::_validate_error_analysis_consistency` enforces exact target agreement and uniqueness.
|
|
359
359
|
- **Implementation-option-selection comparison.** When `header.taskType` is `implementation-option-selection`, populate `implementationOptionSelection` from the converged direction-selection findings. Preserve every merged or rejected raw candidate in `candidateAudit`, and put at most three selectable candidates in `rankedOptions`. Each displayed candidate carries its requirement coverage, scope commitments, criterion scores, feasibility votes, safety blockers, unresolved feasibility facts, planning invariants, and exact coverage summary. In each displayed candidate, `expectedChangeAreas` names direction-level change surfaces, never exact file paths or an exact file list. `expectedVerification` names direction-level verification signals, never a stage list or executable test commands. `schemas/final-report-v2.0.schema.json` enforces the displayed-summary constants and the three-option cap; semantic recalculation belongs to `validators/validate-run.py`.
|
|
360
|
+
- **Routing.** `implementationOptionSelection.routing` is a required **string enum** — not an object — with exactly three values: `implementation-planning`, `pending-direction-selection`, `blocked`. It is the only field in this report that records where the task goes next, and Phase 7 projects `workflow.nextRecommendedPhase` from it (`scripts/okstra_ctl/next_phase.py`): `implementation-planning` becomes a `ready` pointer naming that phase, while `pending-direction-selection` and `blocked` become `pending` and `blocked` pointers carrying no phase. Only the first proposes a next run.
|
|
361
|
+
- **The value is determined by this run's mode and candidate set, not chosen freely.** `preselected-validation` mode routes to `implementation-planning` — the direction was already selected and this run only validated it. `candidate-comparison` mode that displays any candidate routes to `pending-direction-selection` — the user still owes the direction pick, so a comparison never routes straight to planning. `blocked` is legal only when no valid candidate exists at all, and is required in that case. **Enforced:** `scripts/okstra_ctl/implementation_options.py::validate_implementation_option_selection` rejects all three mismatches (`validated preselected direction must route to implementation-planning`, `candidate-comparison with options must await direction selection`, `routing must be blocked only when no valid options exist` / `routing may be blocked only when no valid options exist`).
|
|
360
362
|
- **Implementation-planning direction branch.** When `implementationPlanning.planningContract == "selected-direction"`, read `selectedDirectionRef` and the snapshot before authoring. Materialize the snapshot into `directionRealization`, stages, validation, rollback, and bidirectional original-requirement links. Author exactly one `P-Dir-1`; its payload is the complete `directionRealization`. Its verification covers the core mechanism, architecture boundaries, planning invariants, and any hidden direction change against `selectedDirectionRef`. Do not author Option Candidates, candidate scores, a Recommended Option, or user candidate-selection fields. When current evidence requires changing the direction, author `outcome: "direction-invalidated"` and omit the execution plan. Legacy candidate-comparison reruns retain `P-Opt-*`, Option Candidates, trade-off, and Recommended Option semantics.
|
|
361
363
|
|
|
362
364
|
```json
|
|
@@ -370,6 +372,12 @@ Every field MUST anchor its claim with at least one evidence reference — a `pa
|
|
|
370
372
|
```
|
|
371
373
|
|
|
372
374
|
- **Implementation-option-selection is non-terminal.** Its `followUpTasks` includes a `phase-continuation` row with `autoSpawn: "no"` and `priority: "P0"`; the schema's non-terminal conditional enforces row presence.
|
|
375
|
+
- **Implementation and final-verification routing.** Both phases record where the task goes next in a `routingRecommendation` **object** with exactly two fields: `target` is one enum value, `rationale` is the sentence that justifies it. Neither field takes free-form routing prose, and a target named only in the prose does not count — Phase 7 projects `workflow.nextRecommendedPhase` from `target` alone (`scripts/okstra_ctl/next_phase.py`), so the value you write there is the route the task actually takes.
|
|
376
|
+
- `implementation.routingRecommendation.target` is one of `final-verification`, `error-analysis`, `implementation-planning`, `implementation`. Pick `final-verification` when this stage's plan items landed and validation passed; `error-analysis` when a failure's cause is not understood; `implementation-planning` when the approved plan itself no longer fits the evidence; `implementation` when work remains inside this stage (the next run is a fix run).
|
|
377
|
+
- `finalVerification.routingRecommendation.target` is one of `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, `done`. Both `release-handoff` values require the `accepted` verdict, and plain `release-handoff` additionally requires `verificationScope` `whole-task` — a `single-stage` accepted run routes to `release-handoff(stage-group)` instead. `done` ends the lifecycle here. `error-analysis` / `implementation-option-selection` / `implementation-planning` follow the cause-vs-direction-vs-plan split of the verdict token table above.
|
|
378
|
+
- `rationale` is one or two sentences on why that target and nothing else, citing the blocker ids or evidence rows behind the choice. It is the only free-form half of the field; the digest sections still point here for the full reasoning.
|
|
379
|
+
- `releaseHandoff.routingRecommendation` is unchanged — it stays a single prose field, because `release-handoff` is terminal and nothing projects a next phase from it.
|
|
380
|
+
- **Enforced:** `schemas/final-report-v2.0.schema.json` rejects a `target` outside the enum, a missing `rationale`, and any string value in either field; `validators/validate-run.py::_validate_final_verification_consistency` rejects a final-verification report whose `routingRecommendation.target` is absent, and rejects the verdict↔routing and scope↔routing combinations named above.
|
|
373
381
|
3. **Recommended Next Steps** — prioritized actions. After Phase 7's follow-up spawner runs, append a row per newly created task-key (see "Phase 6 → Phase 7 execution sequence" above). **Approval-gate consistency:** when §1 carries any `Blocks: approval` row with `Status` ∈ {open, answered}, the Verdict Card `Next Step` and the first recommended step MUST point to the clarification rerun (`resume-clarification` of the SAME task-type) — never to "flip frontmatter `approved: true` → jump straight to `implementation`". Run-prep enforces this gate (`run.py _validate_approved_plan` fail-closes on those rows and on a blocking data.json `gateResult`), so a direct-implementation next-step is an instruction the reader cannot actually follow. **Cross-project pointer rule:** for cross-project dependencies (another repo / a different top-level deployment module / a published package), `crossProjectDependencies` (§5.4 Cross-Project Dependencies) is authoritative — do NOT duplicate that substance (prerequisite work / verification signals / handoff) into `recommendedNextSteps`; put only a one-line pointer to that section (no double-recording).
|
|
374
382
|
4. **Follow-up Tasks** — auto-spawn-eligible table. Each row drives `okstra-spawn-followups.py`; see template §4 for the row schema.
|
|
375
383
|
5. **Missing Information and Risks** — uncertain / "I don't know" items. `implementation-planning` adds §5.5 (see heading contract below); `release-handoff` adds §5.6.
|
|
@@ -417,7 +425,13 @@ Persistence steps that must be performed in Phase 7:
|
|
|
417
425
|
- [ ] 4. **Update task-manifest.json**: Reflect task-level status and workflow lifecycle metadata
|
|
418
426
|
- Update `workCategory` if the run produced a confident classification
|
|
419
427
|
- Update `workflow.currentPhase`, `workflow.currentPhaseState`, `workflow.lastCompletedPhase`, and `workflow.phaseStates`
|
|
420
|
-
-
|
|
428
|
+
- Write `workflow.nextRecommendedPhase` as an object with exactly three string fields — `phase`, `status`, `rationale`. **This checklist item is the canonical statement of that field**; the lead contract, the context loader and every okstra skill point here instead of restating it, so a change to the rule belongs in this bullet.
|
|
429
|
+
- `status` is one of `ready` (the named phase can be started now), `pending` (this run did not settle where the task goes next), `blocked` (something outside this run must change before any phase can start), or `terminal` (the lifecycle ends here; there is no next phase).
|
|
430
|
+
- `phase` carries a lifecycle phase name only when `status` is `ready`; under the other three, write the empty string. This is an authoring rule for the value **you** write, not a constraint the struct enforces or a shape you can rely on when reading. `prepare` lowers a `ready` pointer to `pending` and keeps its `phase` (`scripts/okstra_ctl/render.py::_derive_next_recommended_phase`), so every in-flight task's manifest holds a non-`ready` pointer that still names a phase. Never infer launchability from a non-empty `phase` — read `status`, which is the one field that answers it.
|
|
431
|
+
- `rationale` is one sentence saying why. It is the only free-form field, and it is the only part of the pointer that survives a correction.
|
|
432
|
+
- **`phase` and `status` MUST agree with this report's own routing field.** Which field that is depends on the task-type: `requirementsDiscovery.routing.nextTaskType`, `errorAnalysis.routing.nextTaskType`, `implementationOptionSelection.routing`, `implementationPlanning.outcome`, `implementation.routingRecommendation.target`, or `finalVerification.routingRecommendation.target` (see "Implementation and final-verification routing" above for those two enums). Two task-type groups have no routing field that Phase 7 projects from: `release-handoff` (its `routingRecommendation` is prose that nothing reads) is always `terminal`, and the analysis sidetracks — `improvement-discovery`, `project-analysis`, `feature-analysis`, `change-impact-analysis` — are always `pending`, whatever a per-candidate recommendation inside the report says. One routing value is not a phase name: when `finalVerification.routingRecommendation.target` is `release-handoff(stage-group)`, write `phase` as `release-handoff`. The parenthesised part names the handoff's scope, not a different phase, and Phase 7 projects it that way — writing the parenthesised form here records a correction against a report that was right.
|
|
433
|
+
- **Enforced:** Phase 7 validation recomputes `phase` and `status` from that routing field (`scripts/okstra_ctl/next_phase.py::project`). On a passing run whose authored pair disagrees with the recomputed pair, `validators/validate-run.py` overwrites both with the recomputed values, replaces your `rationale` with a pointer sentence, and preserves what you wrote under `workflow.nextRecommendedPhaseCorrection.authored`. A run whose validation fails ends with the pointer `blocked`, keeping your `rationale`. So the routing field is what actually moves the task — a pointer authored against the report body changes nothing but the audit trail.
|
|
434
|
+
- Update `workflow.awaitingApproval`
|
|
421
435
|
- Update `workflow.lastSafeCheckpoint` to the best resume point for the current task
|
|
422
436
|
- [ ] 5. **Update task-index.md**: Refresh human-readable summary
|
|
423
437
|
- [ ] 6. **Generate final status file**: `runs/<task-type>/status/final-<task-type>-<seq>.status` (if necessary)
|