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
|
@@ -1,1170 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join, relative, resolve as resolveAbs } from "node:path";
|
|
5
|
-
import { getPackageRoot } from "../../lib/version.mjs";
|
|
6
|
-
import { resolvePaths } from "../../lib/paths.mjs";
|
|
7
|
-
import { fileExists } from "../../lib/proc.mjs";
|
|
8
|
-
import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "../../lib/runtime-manifest.mjs";
|
|
9
|
-
import { normalizeHostRequest, resolveRuntime } from "../../lib/host-registry-client.mjs";
|
|
10
|
-
import { OBSOLETE_SKILL_NAMES, USER_SKILL_NAMES } from "../../lib/skill-catalog.mjs";
|
|
11
|
-
import {
|
|
12
|
-
AGENTS_MANIFEST_REL,
|
|
13
|
-
LEGACY_TRANSPORT_AGENT_NAMES,
|
|
14
|
-
SKILLS_MANIFEST_REL,
|
|
15
|
-
} from "../../lib/install-assets.mjs";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const USER_HOME = homedir();
|
|
19
|
-
const CLAUDE_HOME = join(USER_HOME, ".claude");
|
|
20
|
-
const AGENTS_HOME = join(USER_HOME, ".agents");
|
|
21
|
-
const CLAUDE_SKILLS_DIR = join(CLAUDE_HOME, "skills");
|
|
22
|
-
const AGENTS_SKILLS_DIR = join(AGENTS_HOME, "skills");
|
|
23
|
-
const CLAUDE_AGENTS_DIR = join(CLAUDE_HOME, "agents");
|
|
24
|
-
|
|
25
|
-
// Source template (relative to runtime root in copy mode, or repo root in link mode).
|
|
26
|
-
const SETTINGS_TEMPLATE_SRC_REL = ["templates", "reports", "settings.template.json"];
|
|
27
|
-
// Destination under ~/.okstra/. Project-local .claude/settings.local.json symlinks here.
|
|
28
|
-
const SETTINGS_TEMPLATE_DST_REL = ["templates", "settings.local.json"];
|
|
29
|
-
|
|
30
|
-
const PYTHON_PACKAGES = ["okstra_project", "okstra_ctl", "okstra_token_usage", "okstra_vendor"];
|
|
31
|
-
const BIN_ENTRYPOINTS = [
|
|
32
|
-
"okstra.sh",
|
|
33
|
-
"okstra-codex-exec.sh",
|
|
34
|
-
"okstra-claude-exec.sh",
|
|
35
|
-
"okstra-antigravity-exec.sh",
|
|
36
|
-
"okstra-grok-exec.sh",
|
|
37
|
-
"okstra-kimi-exec.sh",
|
|
38
|
-
"okstra-provider-exec.py",
|
|
39
|
-
"okstra-trace-cleanup.sh",
|
|
40
|
-
"okstra-team-reconcile.sh",
|
|
41
|
-
"okstra-central.sh",
|
|
42
|
-
"okstra-token-usage.py",
|
|
43
|
-
"okstra-error-log.py",
|
|
44
|
-
"okstra-render-report-views.py",
|
|
45
|
-
"okstra-render-final-report.py",
|
|
46
|
-
"okstra-wrapper-status.py",
|
|
47
|
-
"okstra-spawn-followups.py",
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
// Runtime prompt resources the launch prompt and routed coding-preflight pack
|
|
51
|
-
// depend on. Intentionally an exact allowlist, not a directory wildcard.
|
|
52
|
-
const REQUIRED_PROMPT_RESOURCE_FILES = Object.freeze([
|
|
53
|
-
["prompts", "lead", "okstra-lead-contract.md"],
|
|
54
|
-
["prompts", "lead", "context-loader.md"],
|
|
55
|
-
["prompts", "lead", "team-contract.md"],
|
|
56
|
-
["prompts", "lead", "convergence.md"],
|
|
57
|
-
["prompts", "lead", "plan-body-verification.md"],
|
|
58
|
-
["prompts", "lead", "report-writer.md"],
|
|
59
|
-
["prompts", "coding-preflight", "overview.md"],
|
|
60
|
-
["prompts", "coding-preflight", "clean-code.md"],
|
|
61
|
-
["prompts", "coding-preflight", "languages", "javascript-typescript.md"],
|
|
62
|
-
["prompts", "coding-preflight", "languages", "python.md"],
|
|
63
|
-
["prompts", "coding-preflight", "languages", "rust.md"],
|
|
64
|
-
["prompts", "coding-preflight", "languages", "java.md"],
|
|
65
|
-
["prompts", "coding-preflight", "languages", "kotlin.md"],
|
|
66
|
-
["prompts", "coding-preflight", "languages", "sql.md"],
|
|
67
|
-
["prompts", "coding-preflight", "frameworks", "node-server.md"],
|
|
68
|
-
["prompts", "coding-preflight", "architectures", "hexagonal.md"],
|
|
69
|
-
]);
|
|
70
|
-
|
|
71
|
-
const INSTALL_USAGE = `okstra install — install runtime into ~/.okstra
|
|
72
|
-
|
|
73
|
-
Usage:
|
|
74
|
-
okstra install Install/update runtime (copy mode)
|
|
75
|
-
okstra install --runtime <host-id|alias|all>
|
|
76
|
-
okstra install --link <p> Dev only: symlink ~/.okstra/{lib/python,bin}
|
|
77
|
-
to the source repo at <p>. agents/ resolves
|
|
78
|
-
to <p>/agents. Not advertised to end users.
|
|
79
|
-
okstra install --dry-run Print the plan without touching disk
|
|
80
|
-
okstra install --refresh Re-copy even files that match by hash
|
|
81
|
-
|
|
82
|
-
Effect (copy mode):
|
|
83
|
-
${"$HOME"}/.okstra/lib/python <- runtime/python
|
|
84
|
-
${"$HOME"}/.okstra/lib/validators <- runtime/validators (stage/report structure validators)
|
|
85
|
-
${"$HOME"}/.okstra/bin <- runtime/bin
|
|
86
|
-
${"$HOME"}/.okstra/templates <- runtime/templates (v1 assets + v2 AI/HTML template trees)
|
|
87
|
-
${"$HOME"}/.okstra/schemas <- runtime/schemas (final-report schema v1 + v2)
|
|
88
|
-
${"$HOME"}/.okstra/prompts <- runtime/prompts (lead contracts + coding-preflight pack)
|
|
89
|
-
${"$HOME"}/.okstra/templates/settings.local.json <- runtime/templates/reports/settings.template.json
|
|
90
|
-
${"$HOME"}/.claude/skills/<name> <- skills when ${"$HOME"}/.claude exists
|
|
91
|
-
${"$HOME"}/.agents/skills/<name> <- skills (created when needed)
|
|
92
|
-
${"$HOME"}/.claude/agents/<worker>.md <- worker agents when ${"$HOME"}/.claude exists
|
|
93
|
-
${"$HOME"}/.okstra/installed-runtimes.json <- install request/resolution manifest
|
|
94
|
-
${"$HOME"}/.okstra/installed-skills.json <- target-aware manifest of installed skills
|
|
95
|
-
${"$HOME"}/.okstra/installed-agents.json <- manifest of installed workers
|
|
96
|
-
${"$HOME"}/.okstra/version <- installed package version stamp
|
|
97
|
-
|
|
98
|
-
Effect (link mode):
|
|
99
|
-
${"$HOME"}/.okstra/lib/python/<pkg> -> <repo>/scripts/<pkg> (symlink)
|
|
100
|
-
${"$HOME"}/.okstra/bin/<name>.sh -> <repo>/scripts/<name>.sh
|
|
101
|
-
${"$HOME"}/.okstra/prompts -> <repo>/prompts
|
|
102
|
-
${"$HOME"}/.okstra/templates/settings.local.json -> <repo>/templates/reports/settings.template.json
|
|
103
|
-
${"$HOME"}/.claude/skills/<name> -> <repo>/skills/<name> when ${"$HOME"}/.claude exists
|
|
104
|
-
${"$HOME"}/.agents/skills/<name> -> <repo>/skills/<name> (created when needed)
|
|
105
|
-
${"$HOME"}/.claude/agents/<worker>.md -> <repo>/agents/workers/<worker>.md when ${"$HOME"}/.claude exists
|
|
106
|
-
${"$HOME"}/.okstra/dev-link <- <repo> path stamp
|
|
107
|
-
${"$HOME"}/.okstra/version <- installed package version stamp
|
|
108
|
-
|
|
109
|
-
The settings.local.json file is the symlink target referenced by every
|
|
110
|
-
project-local <project>/.claude/settings.local.json that okstra-setup
|
|
111
|
-
provisions, granting per-project Claude Code permissions for okstra
|
|
112
|
-
worker wrapper scripts without modifying the user's global settings.
|
|
113
|
-
|
|
114
|
-
Worker agent definitions are installed into ${"$HOME"}/.claude/agents/ only
|
|
115
|
-
when ${"$HOME"}/.claude exists, so Claude Code's subagent discovery can pick
|
|
116
|
-
them up. Agent skill targets receive skill markdown only.
|
|
117
|
-
|
|
118
|
-
Default auto runtime installation does not require host detection; it still
|
|
119
|
-
installs shared runtime files, the Agent skill target, and any Claude home it finds.
|
|
120
|
-
`;
|
|
121
|
-
|
|
122
|
-
const ENSURE_USAGE = `okstra ensure-installed — idempotent install check
|
|
123
|
-
|
|
124
|
-
Usage:
|
|
125
|
-
okstra ensure-installed Verify install; reinstall if version/files drift
|
|
126
|
-
okstra ensure-installed --runtime <host-id|alias|all>
|
|
127
|
-
okstra ensure-installed -q Same, but suppress success output
|
|
128
|
-
|
|
129
|
-
Skills call this on every run. Returns 0 quickly when the install is fresh.
|
|
130
|
-
`;
|
|
131
|
-
|
|
132
|
-
export function runtimeIncludesClaudeAssets(resolution) {
|
|
133
|
-
return resolution?.installTargets?.includes("claude") === true;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function readRuntimeFlag(args, index) {
|
|
137
|
-
const arg = args[index];
|
|
138
|
-
if (arg.startsWith("--runtime=")) {
|
|
139
|
-
return { value: arg.slice("--runtime=".length), nextIndex: index };
|
|
140
|
-
}
|
|
141
|
-
const next = args[index + 1];
|
|
142
|
-
if (!next || next.startsWith("--")) {
|
|
143
|
-
throw new Error("--runtime requires a host ID, alias, or all");
|
|
144
|
-
}
|
|
145
|
-
return { value: next, nextIndex: index + 1 };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function validateInstallRuntime(value) {
|
|
149
|
-
return normalizeHostRequest(value);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async function resolveInstallRuntime(runtime, command) {
|
|
153
|
-
const resolution = await resolveRuntime({
|
|
154
|
-
requestedRuntime: runtime,
|
|
155
|
-
command,
|
|
156
|
-
env: process.env,
|
|
157
|
-
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
158
|
-
});
|
|
159
|
-
if (resolution.ok || runtime !== "auto") return resolution;
|
|
160
|
-
return {
|
|
161
|
-
...resolution,
|
|
162
|
-
confidence: "none",
|
|
163
|
-
warnings: [resolution.reason],
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function formatInstallRuntimeLabel(runtime, resolution) {
|
|
168
|
-
if (resolution.resolvedRuntime) return `${runtime} -> ${resolution.resolvedRuntime}`;
|
|
169
|
-
return `${runtime} (host unresolved; installing target assets)`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function hashFile(path) {
|
|
173
|
-
const buf = await fs.readFile(path);
|
|
174
|
-
return createHash("sha256").update(buf).digest("hex");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async function dirExists(path) {
|
|
178
|
-
try {
|
|
179
|
-
const st = await fs.stat(path);
|
|
180
|
-
return st.isDirectory();
|
|
181
|
-
} catch {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async function pathEntryExists(path) {
|
|
187
|
-
try {
|
|
188
|
-
await fs.lstat(path);
|
|
189
|
-
return true;
|
|
190
|
-
} catch (err) {
|
|
191
|
-
if (err.code === "ENOENT") return false;
|
|
192
|
-
throw err;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function* walkFiles(root) {
|
|
197
|
-
let entries;
|
|
198
|
-
try {
|
|
199
|
-
entries = await fs.readdir(root, { withFileTypes: true });
|
|
200
|
-
} catch (err) {
|
|
201
|
-
if (err.code === "ENOENT") return;
|
|
202
|
-
throw err;
|
|
203
|
-
}
|
|
204
|
-
for (const ent of entries) {
|
|
205
|
-
if (ent.name === "__pycache__" || ent.name === ".DS_Store") continue;
|
|
206
|
-
if (ent.name.endsWith(".pyc")) continue;
|
|
207
|
-
const abs = join(root, ent.name);
|
|
208
|
-
if (ent.isDirectory()) {
|
|
209
|
-
yield* walkFiles(abs);
|
|
210
|
-
} else if (ent.isFile()) {
|
|
211
|
-
yield abs;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function writeFileAtomic(target, data, mode) {
|
|
217
|
-
await fs.mkdir(join(target, ".."), { recursive: true });
|
|
218
|
-
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
219
|
-
await fs.writeFile(tmp, data, { mode });
|
|
220
|
-
await fs.rename(tmp, target);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Files the destination has and the payload no longer ships. Left in place they
|
|
224
|
-
// are indistinguishable from current code — Python imports whatever is on disk,
|
|
225
|
-
// so a module dropped in release N+1 keeps being importable after the upgrade,
|
|
226
|
-
// and a rename leaves both spellings live at once.
|
|
227
|
-
async function pruneOrphans(srcRoot, dstRoot, opts) {
|
|
228
|
-
const { dryRun = false } = opts ?? {};
|
|
229
|
-
if (!(await dirExists(dstRoot))) return 0;
|
|
230
|
-
let pruned = 0;
|
|
231
|
-
for await (const dstPath of walkFiles(dstRoot)) {
|
|
232
|
-
const rel = relative(dstRoot, dstPath);
|
|
233
|
-
try {
|
|
234
|
-
await fs.access(join(srcRoot, rel));
|
|
235
|
-
continue;
|
|
236
|
-
} catch {
|
|
237
|
-
// not in the payload → orphan
|
|
238
|
-
}
|
|
239
|
-
if (dryRun) {
|
|
240
|
-
process.stdout.write(`[dry-run] prune ${dstPath}\n`);
|
|
241
|
-
} else {
|
|
242
|
-
await fs.rm(dstPath, { force: true });
|
|
243
|
-
}
|
|
244
|
-
pruned++;
|
|
245
|
-
}
|
|
246
|
-
return pruned;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function copyTreeIfChanged(srcRoot, dstRoot, opts) {
|
|
250
|
-
const { refresh = false, dryRun = false, mode } = opts ?? {};
|
|
251
|
-
let copied = 0;
|
|
252
|
-
let skipped = 0;
|
|
253
|
-
let pruned = 0;
|
|
254
|
-
let missingSource = false;
|
|
255
|
-
|
|
256
|
-
if (!(await dirExists(srcRoot))) {
|
|
257
|
-
missingSource = true;
|
|
258
|
-
return { copied, skipped, pruned, missingSource };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
for await (const srcPath of walkFiles(srcRoot)) {
|
|
262
|
-
const rel = relative(srcRoot, srcPath);
|
|
263
|
-
const dstPath = join(dstRoot, rel);
|
|
264
|
-
|
|
265
|
-
let needsCopy = refresh;
|
|
266
|
-
if (!needsCopy) {
|
|
267
|
-
try {
|
|
268
|
-
await fs.access(dstPath);
|
|
269
|
-
const [srcHash, dstHash] = await Promise.all([hashFile(srcPath), hashFile(dstPath)]);
|
|
270
|
-
needsCopy = srcHash !== dstHash;
|
|
271
|
-
} catch {
|
|
272
|
-
needsCopy = true;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!needsCopy) {
|
|
277
|
-
skipped++;
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (dryRun) {
|
|
282
|
-
process.stdout.write(`[dry-run] copy ${srcPath} -> ${dstPath}\n`);
|
|
283
|
-
} else {
|
|
284
|
-
const buf = await fs.readFile(srcPath);
|
|
285
|
-
await writeFileAtomic(dstPath, buf, mode);
|
|
286
|
-
}
|
|
287
|
-
copied++;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
pruned = await pruneOrphans(srcRoot, dstRoot, { dryRun });
|
|
291
|
-
return { copied, skipped, pruned, missingSource };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async function ensureSymlink(target, linkPath, opts) {
|
|
295
|
-
const { dryRun = false } = opts ?? {};
|
|
296
|
-
try {
|
|
297
|
-
const current = await fs.readlink(linkPath);
|
|
298
|
-
if (current === target) return "skipped";
|
|
299
|
-
} catch (err) {
|
|
300
|
-
if (err.code !== "ENOENT" && err.code !== "EINVAL") throw err;
|
|
301
|
-
}
|
|
302
|
-
if (dryRun) {
|
|
303
|
-
process.stdout.write(`[dry-run] symlink ${linkPath} -> ${target}\n`);
|
|
304
|
-
return "linked";
|
|
305
|
-
}
|
|
306
|
-
await fs.mkdir(join(linkPath, ".."), { recursive: true });
|
|
307
|
-
// readlink returns EINVAL for both a missing link and an existing non-symlink
|
|
308
|
-
// file, so unlink alone would silently destroy a real file. lstat lets us
|
|
309
|
-
// refuse to clobber any existing entry that is not a symlink.
|
|
310
|
-
let existing;
|
|
311
|
-
try {
|
|
312
|
-
existing = await fs.lstat(linkPath);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
if (err.code !== "ENOENT") throw err;
|
|
315
|
-
}
|
|
316
|
-
if (existing && !existing.isSymbolicLink()) {
|
|
317
|
-
throw new Error(
|
|
318
|
-
`refuse to replace ${linkPath} (not a symlink). Remove it manually and retry, or run 'okstra uninstall' first.`,
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
if (existing) await fs.unlink(linkPath);
|
|
322
|
-
await fs.symlink(target, linkPath);
|
|
323
|
-
return "linked";
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async function installLinkMode(repoPath, paths, opts) {
|
|
327
|
-
const { dryRun, quiet, runtime } = opts;
|
|
328
|
-
const repoAbs = resolveAbs(repoPath);
|
|
329
|
-
|
|
330
|
-
if (!(await dirExists(repoAbs))) {
|
|
331
|
-
throw new Error(`repo path not found: ${repoAbs}`);
|
|
332
|
-
}
|
|
333
|
-
const scriptsDir = join(repoAbs, "scripts");
|
|
334
|
-
if (!(await dirExists(scriptsDir))) {
|
|
335
|
-
throw new Error(`<repo>/scripts not found: ${scriptsDir}`);
|
|
336
|
-
}
|
|
337
|
-
const agentsDir = join(repoAbs, "agents");
|
|
338
|
-
if (!(await dirExists(agentsDir))) {
|
|
339
|
-
throw new Error(`<repo>/agents not found: ${agentsDir}`);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (!quiet) {
|
|
343
|
-
process.stdout.write(`installing okstra runtime in LINK mode (package ${paths.package})\n`);
|
|
344
|
-
process.stdout.write(` runtime: ${runtime}\n`);
|
|
345
|
-
process.stdout.write(` repo: ${repoAbs}\n`);
|
|
346
|
-
process.stdout.write(` home: ${paths.home}\n`);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (!dryRun) {
|
|
350
|
-
await fs.mkdir(paths.pythonpath, { recursive: true });
|
|
351
|
-
await fs.mkdir(paths.bin, { recursive: true });
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
for (const pkg of PYTHON_PACKAGES) {
|
|
355
|
-
const src = join(scriptsDir, pkg);
|
|
356
|
-
const dst = join(paths.pythonpath, pkg);
|
|
357
|
-
if (!(await dirExists(src))) {
|
|
358
|
-
if (!quiet) process.stdout.write(` python/${pkg}: missing in repo — skipped\n`);
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
const action = await ensureSymlink(src, dst, { dryRun });
|
|
362
|
-
if (!quiet) process.stdout.write(` python/${pkg}: ${action}\n`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
for (const name of BIN_ENTRYPOINTS) {
|
|
366
|
-
const src = join(scriptsDir, name);
|
|
367
|
-
const dst = join(paths.bin, name);
|
|
368
|
-
if (!(await fileExists(src))) {
|
|
369
|
-
if (!quiet) process.stdout.write(` bin/${name}: missing in repo — skipped\n`);
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
const action = await ensureSymlink(src, dst, { dryRun });
|
|
373
|
-
if (!quiet) process.stdout.write(` bin/${name}: ${action}\n`);
|
|
374
|
-
}
|
|
375
|
-
const promptsSrc = join(repoAbs, "prompts");
|
|
376
|
-
const promptsDst = join(paths.home, "prompts");
|
|
377
|
-
if (await dirExists(promptsSrc)) {
|
|
378
|
-
const action = await ensureSymlink(promptsSrc, promptsDst, { dryRun });
|
|
379
|
-
if (!quiet) process.stdout.write(` prompts: ${action}\n`);
|
|
380
|
-
} else if (!quiet) {
|
|
381
|
-
process.stdout.write(" prompts: missing in repo — skipped\n");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const skillTargets = await detectSkillTargets();
|
|
386
|
-
const installedSkillTargets = await installSkillTargetsLink(repoAbs, skillTargets, { dryRun, quiet });
|
|
387
|
-
await writeSkillsManifest(paths.home, installedSkillTargets, { dryRun });
|
|
388
|
-
|
|
389
|
-
let agentResult = { installed: [] };
|
|
390
|
-
if (skillTargets.some((target) => target.provider === "claude")) {
|
|
391
|
-
await pruneOwnedLegacyTransportAgents(paths.home, { dryRun, quiet });
|
|
392
|
-
agentResult = await installAgentsLink(repoAbs, { dryRun, quiet });
|
|
393
|
-
}
|
|
394
|
-
await writeAgentsManifest(paths.home, agentResult.installed, { dryRun });
|
|
395
|
-
|
|
396
|
-
await installNamedTemplate(repoAbs, paths, { mode: "link", dryRun, quiet }, SETTINGS_TEMPLATE_DESCRIPTOR);
|
|
397
|
-
await writeRuntimeManifest(paths.home, runtime, {
|
|
398
|
-
dryRun,
|
|
399
|
-
resolution: opts.resolution,
|
|
400
|
-
installedAssets: installedAssetOverrides(installedSkillTargets, agentResult.installed),
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
if (!dryRun) {
|
|
404
|
-
await writeFileAtomic(join(paths.home, "dev-link"), repoAbs + "\n", 0o644);
|
|
405
|
-
await writeFileAtomic(join(paths.home, "version"), paths.package + "\n", 0o644);
|
|
406
|
-
}
|
|
407
|
-
if (!quiet) {
|
|
408
|
-
process.stdout.write(` dev-link stamp: ${repoAbs}\n`);
|
|
409
|
-
process.stdout.write(` version stamp: ${paths.package}\n`);
|
|
410
|
-
process.stdout.write("done.\n");
|
|
411
|
-
process.stdout.write(
|
|
412
|
-
"\nNext step: register the current project.\n" +
|
|
413
|
-
" cd <your project> && npx -y okstra@latest setup --project-id <id>\n" +
|
|
414
|
-
" (or run /okstra-setup inside a Harness Agent session (e.g. claude codex, codex, pi.. ))\n" +
|
|
415
|
-
"\nTip: to use a bare 'okstra' command instead of npx, run:\n" +
|
|
416
|
-
" npm i -g okstra\n",
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
return 0;
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
async function detectSkillTargets() {
|
|
424
|
-
const targets = [];
|
|
425
|
-
const hasClaude = await dirExists(CLAUDE_HOME);
|
|
426
|
-
if (hasClaude) {
|
|
427
|
-
targets.push({ provider: "claude", home: CLAUDE_HOME, skillsDir: CLAUDE_SKILLS_DIR });
|
|
428
|
-
}
|
|
429
|
-
targets.push({ provider: "agent", home: AGENTS_HOME, skillsDir: AGENTS_SKILLS_DIR });
|
|
430
|
-
return targets;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function skillTargetManifest(targets) {
|
|
434
|
-
return targets.map((target) => ({
|
|
435
|
-
provider: target.provider,
|
|
436
|
-
root: target.skillsDir,
|
|
437
|
-
skills: Array.from(new Set(target.installed)).sort(),
|
|
438
|
-
}));
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function installedAssetOverrides(skillTargets, agentNames) {
|
|
442
|
-
const providers = new Set(skillTargets.map((target) => target.provider));
|
|
443
|
-
return {
|
|
444
|
-
claudeSkills: providers.has("claude"),
|
|
445
|
-
claudeAgents: agentNames.length > 0,
|
|
446
|
-
codexSkills: providers.has("codex"),
|
|
447
|
-
skillTargets: Array.from(providers).sort(),
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function installedSkillNames(targets) {
|
|
452
|
-
return Array.from(new Set(targets.flatMap((target) => target.installed))).sort();
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
async function writeSkillsManifest(home, targets, opts) {
|
|
457
|
-
const { dryRun = false } = opts ?? {};
|
|
458
|
-
const manifestTargets = skillTargetManifest(targets);
|
|
459
|
-
const data = {
|
|
460
|
-
version: 2,
|
|
461
|
-
installedAt: new Date().toISOString(),
|
|
462
|
-
skills: installedSkillNames(targets),
|
|
463
|
-
targets: manifestTargets,
|
|
464
|
-
};
|
|
465
|
-
if (dryRun) {
|
|
466
|
-
process.stdout.write(
|
|
467
|
-
`[dry-run] write skills manifest: ${data.skills.length} skills across ${data.targets.length} targets\n`,
|
|
468
|
-
);
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
await writeFileAtomic(
|
|
472
|
-
join(home, SKILLS_MANIFEST_REL),
|
|
473
|
-
JSON.stringify(data, null, 2) + "\n",
|
|
474
|
-
0o644,
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
async function writeAgentsManifest(home, names, opts) {
|
|
479
|
-
const { dryRun = false } = opts ?? {};
|
|
480
|
-
const data = {
|
|
481
|
-
version: 1,
|
|
482
|
-
installedAt: new Date().toISOString(),
|
|
483
|
-
agents: Array.from(new Set(names)).sort(),
|
|
484
|
-
};
|
|
485
|
-
if (dryRun) {
|
|
486
|
-
process.stdout.write(`[dry-run] write agents manifest: ${data.agents.length} entries\n`);
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
await writeFileAtomic(
|
|
490
|
-
join(home, AGENTS_MANIFEST_REL),
|
|
491
|
-
JSON.stringify(data, null, 2) + "\n",
|
|
492
|
-
0o644,
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
async function pruneOwnedLegacyTransportAgents(home, opts) {
|
|
497
|
-
const { dryRun = false, quiet = false } = opts ?? {};
|
|
498
|
-
let previous = [];
|
|
499
|
-
try {
|
|
500
|
-
const data = JSON.parse(
|
|
501
|
-
await fs.readFile(join(home, AGENTS_MANIFEST_REL), "utf8"),
|
|
502
|
-
);
|
|
503
|
-
if (Array.isArray(data?.agents)) previous = data.agents;
|
|
504
|
-
} catch (err) {
|
|
505
|
-
if (err.code !== "ENOENT" && !(err instanceof SyntaxError)) throw err;
|
|
506
|
-
}
|
|
507
|
-
const ownedLegacy = LEGACY_TRANSPORT_AGENT_NAMES.filter((name) => (
|
|
508
|
-
previous.includes(name)
|
|
509
|
-
));
|
|
510
|
-
for (const name of ownedLegacy) {
|
|
511
|
-
const target = join(CLAUDE_AGENTS_DIR, name);
|
|
512
|
-
if (dryRun) {
|
|
513
|
-
process.stdout.write(`[dry-run] remove retired agent ${target}\n`);
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
try {
|
|
517
|
-
await fs.unlink(target);
|
|
518
|
-
if (!quiet) process.stdout.write(` agents/${name}: removed retired wrapper\n`);
|
|
519
|
-
} catch (err) {
|
|
520
|
-
if (err.code !== "ENOENT") throw err;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
async function writeRuntimeManifest(home, runtime, opts) {
|
|
526
|
-
const { dryRun = false, resolution = null, installedAssets = null } = opts ?? {};
|
|
527
|
-
const data = buildRuntimeManifest(
|
|
528
|
-
runtime,
|
|
529
|
-
new Date().toISOString(),
|
|
530
|
-
resolution,
|
|
531
|
-
installedAssets,
|
|
532
|
-
);
|
|
533
|
-
if (dryRun) {
|
|
534
|
-
process.stdout.write(`[dry-run] write runtime manifest: ${data.installedRuntimes.join(",")}\n`);
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
await writeFileAtomic(
|
|
538
|
-
join(home, RUNTIMES_MANIFEST_REL),
|
|
539
|
-
JSON.stringify(data, null, 2) + "\n",
|
|
540
|
-
0o644,
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
async function listWorkerFiles(workersRoot) {
|
|
545
|
-
try {
|
|
546
|
-
const entries = await fs.readdir(workersRoot, { withFileTypes: true });
|
|
547
|
-
return entries
|
|
548
|
-
.filter((e) => (
|
|
549
|
-
e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_")
|
|
550
|
-
))
|
|
551
|
-
.map((e) => e.name);
|
|
552
|
-
} catch (err) {
|
|
553
|
-
if (err.code === "ENOENT") return [];
|
|
554
|
-
throw err;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
async function installAgentsCopy(runtimeRoot, opts) {
|
|
559
|
-
const { refresh, dryRun, quiet } = opts;
|
|
560
|
-
const srcRoot = join(runtimeRoot, "agents", "workers");
|
|
561
|
-
const names = await listWorkerFiles(srcRoot);
|
|
562
|
-
if (names.length === 0) {
|
|
563
|
-
if (!quiet) process.stdout.write(" agents: runtime/agents/workers empty — skipped\n");
|
|
564
|
-
return { installed: [] };
|
|
565
|
-
}
|
|
566
|
-
if (!dryRun) await fs.mkdir(CLAUDE_AGENTS_DIR, { recursive: true });
|
|
567
|
-
let copied = 0;
|
|
568
|
-
let skipped = 0;
|
|
569
|
-
for (const name of names) {
|
|
570
|
-
const src = join(srcRoot, name);
|
|
571
|
-
const dst = join(CLAUDE_AGENTS_DIR, name);
|
|
572
|
-
let needsCopy = refresh;
|
|
573
|
-
if (!needsCopy) {
|
|
574
|
-
try {
|
|
575
|
-
await fs.access(dst);
|
|
576
|
-
const [a, b] = await Promise.all([hashFile(src), hashFile(dst)]);
|
|
577
|
-
needsCopy = a !== b;
|
|
578
|
-
} catch {
|
|
579
|
-
needsCopy = true;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
if (!needsCopy) {
|
|
583
|
-
skipped++;
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
if (dryRun) {
|
|
587
|
-
process.stdout.write(`[dry-run] copy ${src} -> ${dst}\n`);
|
|
588
|
-
} else {
|
|
589
|
-
const buf = await fs.readFile(src);
|
|
590
|
-
await writeFileAtomic(dst, buf, 0o644);
|
|
591
|
-
}
|
|
592
|
-
copied++;
|
|
593
|
-
}
|
|
594
|
-
if (!quiet) {
|
|
595
|
-
process.stdout.write(
|
|
596
|
-
` agents: copied=${copied} skipped=${skipped} -> ${CLAUDE_AGENTS_DIR}/ (${names.length} workers)\n`,
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
return { installed: names };
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
async function installAgentsLink(repoAbs, opts) {
|
|
603
|
-
const { dryRun, quiet } = opts;
|
|
604
|
-
const srcRoot = join(repoAbs, "agents", "workers");
|
|
605
|
-
const sourceNames = await listWorkerFiles(srcRoot);
|
|
606
|
-
const names = [...sourceNames].sort();
|
|
607
|
-
if (names.length === 0) {
|
|
608
|
-
if (!quiet) process.stdout.write(" agents: <repo>/agents/workers missing — skipped\n");
|
|
609
|
-
return { installed: [] };
|
|
610
|
-
}
|
|
611
|
-
if (!dryRun) await fs.mkdir(CLAUDE_AGENTS_DIR, { recursive: true });
|
|
612
|
-
for (const name of sourceNames) {
|
|
613
|
-
const src = join(srcRoot, name);
|
|
614
|
-
const dst = join(CLAUDE_AGENTS_DIR, name);
|
|
615
|
-
const action = await ensureSymlink(src, dst, { dryRun });
|
|
616
|
-
if (!quiet) process.stdout.write(` agents/${name}: ${action}\n`);
|
|
617
|
-
}
|
|
618
|
-
return { installed: names };
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
async function installNamedTemplate(srcRoot, paths, opts, descriptor) {
|
|
622
|
-
const { mode, refresh = false, dryRun = false, quiet = false } = opts;
|
|
623
|
-
const { srcRel, dstRel, label } = descriptor;
|
|
624
|
-
const src = join(srcRoot, ...srcRel);
|
|
625
|
-
const dst = join(paths.home, ...dstRel);
|
|
626
|
-
|
|
627
|
-
if (!(await fileExists(src))) {
|
|
628
|
-
if (!quiet) process.stdout.write(` ${label}: source missing — skipped (${src})\n`);
|
|
629
|
-
return { installed: false };
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
if (!dryRun) await fs.mkdir(join(dst, ".."), { recursive: true });
|
|
633
|
-
|
|
634
|
-
if (mode === "link") {
|
|
635
|
-
const action = await ensureSymlink(src, dst, { dryRun });
|
|
636
|
-
if (!quiet) process.stdout.write(` ${label}: ${action} (${dst} -> ${src})\n`);
|
|
637
|
-
return { installed: action !== "skipped" };
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// copy mode — hash-skip mirrors copyTreeIfChanged behavior for a single file.
|
|
641
|
-
let needsCopy = refresh;
|
|
642
|
-
if (!needsCopy) {
|
|
643
|
-
try {
|
|
644
|
-
await fs.access(dst);
|
|
645
|
-
const [srcHash, dstHash] = await Promise.all([hashFile(src), hashFile(dst)]);
|
|
646
|
-
needsCopy = srcHash !== dstHash;
|
|
647
|
-
} catch {
|
|
648
|
-
needsCopy = true;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
if (!needsCopy) {
|
|
653
|
-
if (!quiet) process.stdout.write(` ${label}: skipped (hash match)\n`);
|
|
654
|
-
return { installed: false };
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (dryRun) {
|
|
658
|
-
process.stdout.write(`[dry-run] copy ${src} -> ${dst}\n`);
|
|
659
|
-
} else {
|
|
660
|
-
const buf = await fs.readFile(src);
|
|
661
|
-
await writeFileAtomic(dst, buf, 0o644);
|
|
662
|
-
}
|
|
663
|
-
if (!quiet) process.stdout.write(` ${label}: copied -> ${dst}\n`);
|
|
664
|
-
return { installed: true };
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
const SETTINGS_TEMPLATE_DESCRIPTOR = {
|
|
668
|
-
srcRel: SETTINGS_TEMPLATE_SRC_REL,
|
|
669
|
-
dstRel: SETTINGS_TEMPLATE_DST_REL,
|
|
670
|
-
label: "settings template",
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
async function installSkillsCopy(runtimeRoot, target, opts) {
|
|
674
|
-
const { refresh, dryRun, quiet } = opts;
|
|
675
|
-
const srcRoot = join(runtimeRoot, "skills");
|
|
676
|
-
let copied = 0;
|
|
677
|
-
let skipped = 0;
|
|
678
|
-
for (const name of USER_SKILL_NAMES) {
|
|
679
|
-
const r = await copyTreeIfChanged(
|
|
680
|
-
join(srcRoot, name),
|
|
681
|
-
join(target.skillsDir, name),
|
|
682
|
-
{ refresh, dryRun, mode: 0o644 },
|
|
683
|
-
);
|
|
684
|
-
copied += r.copied;
|
|
685
|
-
skipped += r.skipped;
|
|
686
|
-
}
|
|
687
|
-
const pruned = await pruneObsoleteSkills(target, opts);
|
|
688
|
-
if (!quiet) {
|
|
689
|
-
process.stdout.write(
|
|
690
|
-
` ${target.provider} skills: copied=${copied} skipped=${skipped} pruned=${pruned} -> ${target.skillsDir}/ (${USER_SKILL_NAMES.length} skills)\n`,
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
return { ...target, installed: [...USER_SKILL_NAMES] };
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
async function installSkillsLink(repoAbs, target, opts) {
|
|
697
|
-
const { dryRun, quiet } = opts;
|
|
698
|
-
const srcRoot = join(repoAbs, "skills");
|
|
699
|
-
if (!dryRun) await fs.mkdir(target.skillsDir, { recursive: true });
|
|
700
|
-
for (const name of USER_SKILL_NAMES) {
|
|
701
|
-
const src = join(srcRoot, name);
|
|
702
|
-
const dst = join(target.skillsDir, name);
|
|
703
|
-
const action = await ensureSymlink(src, dst, { dryRun });
|
|
704
|
-
if (!quiet) process.stdout.write(` ${target.provider} skills/${name}: ${action}\n`);
|
|
705
|
-
}
|
|
706
|
-
const pruned = await pruneObsoleteSkills(target, opts);
|
|
707
|
-
if (!quiet && pruned > 0) {
|
|
708
|
-
process.stdout.write(` ${target.provider} obsolete internal skills: pruned=${pruned}\n`);
|
|
709
|
-
}
|
|
710
|
-
return { ...target, installed: [...USER_SKILL_NAMES] };
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Remove former skill directories left by older installs. Exact-name only
|
|
714
|
-
// (OBSOLETE_SKILL_NAMES) — never wildcard-delete okstra-*.
|
|
715
|
-
async function pruneObsoleteSkills(target, opts) {
|
|
716
|
-
let pruned = 0;
|
|
717
|
-
for (const name of OBSOLETE_SKILL_NAMES) {
|
|
718
|
-
const path = join(target.skillsDir, name);
|
|
719
|
-
if (!(await pathEntryExists(path))) continue;
|
|
720
|
-
pruned += 1;
|
|
721
|
-
if (opts.dryRun) {
|
|
722
|
-
process.stdout.write(`[dry-run] rm ${path}\n`);
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
await fs.rm(path, { recursive: true, force: true });
|
|
726
|
-
}
|
|
727
|
-
return pruned;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
async function installSkillTargetsCopy(runtimeRoot, targets, opts) {
|
|
731
|
-
if (targets.length === 0) {
|
|
732
|
-
if (!opts.quiet) process.stdout.write(" skills: no skill target found — skipped\n");
|
|
733
|
-
return [];
|
|
734
|
-
}
|
|
735
|
-
return await Promise.all(targets.map((target) => installSkillsCopy(runtimeRoot, target, opts)));
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
async function installSkillTargetsLink(repoAbs, targets, opts) {
|
|
739
|
-
if (targets.length === 0) {
|
|
740
|
-
if (!opts.quiet) process.stdout.write(" skills: no skill target found — skipped\n");
|
|
741
|
-
return [];
|
|
742
|
-
}
|
|
743
|
-
return await Promise.all(targets.map((target) => installSkillsLink(repoAbs, target, opts)));
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
export function parseInstallArgs(args) {
|
|
747
|
-
const result = {
|
|
748
|
-
dryRun: false,
|
|
749
|
-
refresh: false,
|
|
750
|
-
quiet: false,
|
|
751
|
-
linkRepo: null,
|
|
752
|
-
runtime: "auto",
|
|
753
|
-
};
|
|
754
|
-
for (let i = 0; i < args.length; i++) {
|
|
755
|
-
const a = args[i];
|
|
756
|
-
if (a === "--dry-run") result.dryRun = true;
|
|
757
|
-
else if (a === "--refresh") result.refresh = true;
|
|
758
|
-
else if (a === "-q" || a === "--quiet") result.quiet = true;
|
|
759
|
-
else if (a === "--runtime" || a.startsWith("--runtime=")) {
|
|
760
|
-
const parsed = readRuntimeFlag(args, i);
|
|
761
|
-
result.runtime = validateInstallRuntime(parsed.value);
|
|
762
|
-
i = parsed.nextIndex;
|
|
763
|
-
} else if (a === "--link") {
|
|
764
|
-
const next = args[i + 1];
|
|
765
|
-
if (!next || next.startsWith("--")) {
|
|
766
|
-
throw new Error("--link requires a repo path");
|
|
767
|
-
}
|
|
768
|
-
result.linkRepo = next;
|
|
769
|
-
i++;
|
|
770
|
-
} else {
|
|
771
|
-
throw new Error(`unknown argument '${a}'`);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
return result;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
export async function runInstall(args) {
|
|
778
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
779
|
-
process.stdout.write(INSTALL_USAGE);
|
|
780
|
-
return 0;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
let opts;
|
|
784
|
-
try {
|
|
785
|
-
opts = parseInstallArgs(args);
|
|
786
|
-
} catch (err) {
|
|
787
|
-
process.stderr.write(`error: ${err.message}\n`);
|
|
788
|
-
return 2;
|
|
789
|
-
}
|
|
790
|
-
const pkgRoot = getPackageRoot();
|
|
791
|
-
const runtimeRoot = join(pkgRoot, "runtime");
|
|
792
|
-
const paths = await resolvePaths();
|
|
793
|
-
const runtimeResolution = await resolveInstallRuntime(opts.runtime, "install");
|
|
794
|
-
if (!runtimeResolution.ok && opts.runtime !== "auto") {
|
|
795
|
-
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
796
|
-
return 2;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
if (!opts.dryRun) {
|
|
800
|
-
await fs.mkdir(paths.home, { recursive: true });
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
if (opts.linkRepo) {
|
|
804
|
-
return await installLinkMode(opts.linkRepo, paths, { ...opts, resolution: runtimeResolution });
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// copy mode — if a prior dev-link is present, refuse rather than silently overwriting.
|
|
808
|
-
if (paths["dev-link"]) {
|
|
809
|
-
throw new Error(
|
|
810
|
-
`~/.okstra/dev-link is set to ${paths["dev-link"]}. Run 'okstra uninstall' first, then 'okstra install' in copy mode.`,
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
if (!opts.quiet) {
|
|
815
|
-
process.stdout.write(`installing okstra runtime (package ${paths.package})\n`);
|
|
816
|
-
process.stdout.write(` runtime: ${formatInstallRuntimeLabel(opts.runtime, runtimeResolution)}\n`);
|
|
817
|
-
process.stdout.write(` source: ${runtimeRoot}\n`);
|
|
818
|
-
process.stdout.write(` home: ${paths.home}\n`);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const pythonResult = await copyTreeIfChanged(
|
|
822
|
-
join(runtimeRoot, "python"),
|
|
823
|
-
paths.pythonpath,
|
|
824
|
-
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o644 },
|
|
825
|
-
);
|
|
826
|
-
const binResult = await copyTreeIfChanged(
|
|
827
|
-
join(runtimeRoot, "bin"),
|
|
828
|
-
paths.bin,
|
|
829
|
-
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o755 },
|
|
830
|
-
);
|
|
831
|
-
// templates/ tree — the v1 report.css/report.js compatibility assets, the
|
|
832
|
-
// v2 AI handoff template, and the task-specific html/** tree are consumed by
|
|
833
|
-
// okstra-render-report-views.py and final-report assembly. They
|
|
834
|
-
// are NOT covered by installNamedTemplate (which only handles the renamed
|
|
835
|
-
// settings.local.json sidecar), so without this step copy-mode installs
|
|
836
|
-
// miss every asset other than that single file. See
|
|
837
|
-
// okstra-render-report-views.py _TEMPLATES_DIRS for the lookup path.
|
|
838
|
-
const templatesResult = await copyTreeIfChanged(
|
|
839
|
-
join(runtimeRoot, "templates"),
|
|
840
|
-
join(paths.home, "templates"),
|
|
841
|
-
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o644 },
|
|
842
|
-
);
|
|
843
|
-
// schemas/ tree — version-selected final-report v1/v2 schemas are loaded by
|
|
844
|
-
// okstra_ctl.final_report_schema. They support bundle excerpts, legacy
|
|
845
|
-
// re-renders, new task HTML, and validate-run. Link mode resolves the same
|
|
846
|
-
// files through the repo symlink.
|
|
847
|
-
const schemasResult = await copyTreeIfChanged(
|
|
848
|
-
join(runtimeRoot, "schemas"),
|
|
849
|
-
join(paths.home, "schemas"),
|
|
850
|
-
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o644 },
|
|
851
|
-
);
|
|
852
|
-
// validators/ tree — validate-implementation-plan-stages.py (+ lib/) is
|
|
853
|
-
// resolved at runtime by run.py / validate-run.py via the installed package's
|
|
854
|
-
// `parents[2]/validators`, i.e. ~/.okstra/lib/validators. Without this step a
|
|
855
|
-
// copy-mode UPGRADE bumps the version stamp but never refreshes the
|
|
856
|
-
// validators, so a stale validator (e.g. a pre-renumber section schema)
|
|
857
|
-
// survives and render-bundle's stage-structure check diverges from the
|
|
858
|
-
// current template (observed: render-bundle demanding `## 4.5 Stage Map`
|
|
859
|
-
// while the report + wizard validator use `## 5.5`). Mode 0o755 preserves the
|
|
860
|
-
// executable .sh helpers in the tree.
|
|
861
|
-
const validatorsResult = await copyTreeIfChanged(
|
|
862
|
-
join(runtimeRoot, "validators"),
|
|
863
|
-
join(paths.home, "lib", "validators"),
|
|
864
|
-
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o755 },
|
|
865
|
-
);
|
|
866
|
-
// prompts/ tree — lead/internal operating contracts (prompts/lead/*.md) and
|
|
867
|
-
// the coding-preflight resource pack (prompts/coding-preflight/*) are read at
|
|
868
|
-
// runtime by the lead and executor via absolute ~/.okstra/prompts/ paths
|
|
869
|
-
// (paths.py OKSTRA_LEAD_CONTRACT_PATH / OKSTRA_CODING_PREFLIGHT_DIR). Run-prep
|
|
870
|
-
// reads prompts/profiles + launch.template from the package runtime dir, but
|
|
871
|
-
// the lead's runtime Read needs them under the home tree like templates.
|
|
872
|
-
const promptsResult = await copyTreeIfChanged(
|
|
873
|
-
join(runtimeRoot, "prompts"),
|
|
874
|
-
join(paths.home, "prompts"),
|
|
875
|
-
{ refresh: opts.refresh, dryRun: opts.dryRun, mode: 0o644 },
|
|
876
|
-
);
|
|
877
|
-
|
|
878
|
-
if (!opts.quiet) {
|
|
879
|
-
summarise("python", pythonResult, paths.pythonpath);
|
|
880
|
-
summarise("bin", binResult, paths.bin);
|
|
881
|
-
summarise("templates", templatesResult, join(paths.home, "templates"));
|
|
882
|
-
summarise("schemas", schemasResult, join(paths.home, "schemas"));
|
|
883
|
-
summarise("validators", validatorsResult, join(paths.home, "lib", "validators"));
|
|
884
|
-
summarise("prompts", promptsResult, join(paths.home, "prompts"));
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
if (pythonResult.missingSource && binResult.missingSource) {
|
|
888
|
-
process.stderr.write(
|
|
889
|
-
"warning: runtime/{python,bin} are both empty. Runtime sync (build step) has not been performed.\n",
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
if (templatesResult.missingSource) {
|
|
893
|
-
process.stderr.write(
|
|
894
|
-
"warning: runtime/templates is empty. final-report Markdown and HTML templates will be missing — re-run the build step.\n",
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
if (schemasResult.missingSource) {
|
|
898
|
-
process.stderr.write(
|
|
899
|
-
"warning: runtime/schemas is empty. final-report schema validation + excerpt generation will be unavailable — re-run the build step.\n",
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
if (validatorsResult.missingSource) {
|
|
903
|
-
process.stderr.write(
|
|
904
|
-
"warning: runtime/validators is empty. stage-structure / report validation will use stale or missing validators — re-run the build step.\n",
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
if (promptsResult.missingSource) {
|
|
908
|
-
process.stderr.write(
|
|
909
|
-
"warning: runtime/prompts is empty. lead contracts + coding-preflight resources will be missing — re-run the build step.\n",
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
const skillTargets = await detectSkillTargets();
|
|
914
|
-
const installedSkillTargets = await installSkillTargetsCopy(runtimeRoot, skillTargets, opts);
|
|
915
|
-
await writeSkillsManifest(paths.home, installedSkillTargets, { dryRun: opts.dryRun });
|
|
916
|
-
|
|
917
|
-
let agentResult = { installed: [] };
|
|
918
|
-
if (skillTargets.some((target) => target.provider === "claude")) {
|
|
919
|
-
await pruneOwnedLegacyTransportAgents(paths.home, opts);
|
|
920
|
-
agentResult = await installAgentsCopy(runtimeRoot, opts);
|
|
921
|
-
}
|
|
922
|
-
await writeAgentsManifest(paths.home, agentResult.installed, { dryRun: opts.dryRun });
|
|
923
|
-
|
|
924
|
-
await installNamedTemplate(runtimeRoot, paths, { mode: "copy", ...opts }, SETTINGS_TEMPLATE_DESCRIPTOR);
|
|
925
|
-
await writeRuntimeManifest(paths.home, opts.runtime, {
|
|
926
|
-
dryRun: opts.dryRun,
|
|
927
|
-
resolution: runtimeResolution,
|
|
928
|
-
installedAssets: installedAssetOverrides(installedSkillTargets, agentResult.installed),
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
if (!opts.dryRun) {
|
|
932
|
-
await writeFileAtomic(join(paths.home, "version"), paths.package + "\n", 0o644);
|
|
933
|
-
}
|
|
934
|
-
if (!opts.quiet) {
|
|
935
|
-
// The source is already named at the top of the run; what the stamp line
|
|
936
|
-
// could not say is whether this install moved anything. `copied=0 skipped=N`
|
|
937
|
-
// plus a bare version reads identically for "already current" and "an older
|
|
938
|
-
// payload just landed over a newer home" — the latter cost a full run,
|
|
939
|
-
// caught only by hand-diffing installed files.
|
|
940
|
-
process.stdout.write(
|
|
941
|
-
` version stamp: ${formatVersionTransition(paths.version, paths.package)}\n`,
|
|
942
|
-
);
|
|
943
|
-
process.stdout.write("done.\n");
|
|
944
|
-
process.stdout.write(
|
|
945
|
-
"\nNext step: register the current project.\n" +
|
|
946
|
-
" cd <your project> && npx -y okstra@latest setup --project-id <id>\n" +
|
|
947
|
-
" (or run /okstra-setup inside a Harness Agent session (e.g. claude codex, codex, pi.. ))\n" +
|
|
948
|
-
"\nTip: to use a bare 'okstra' command instead of npx, run:\n" +
|
|
949
|
-
" npm i -g okstra\n",
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
return 0;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
async function skillTargetDriftReasons(targets) {
|
|
956
|
-
const reasons = [];
|
|
957
|
-
for (const target of targets) {
|
|
958
|
-
for (const name of USER_SKILL_NAMES) {
|
|
959
|
-
const skillPath = join(target.skillsDir, name, "SKILL.md");
|
|
960
|
-
if (!(await fileExists(skillPath))) {
|
|
961
|
-
reasons.push(`missing ${skillPath}`);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
return reasons;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
async function promptResourceDriftReasons(paths) {
|
|
969
|
-
const reasons = [];
|
|
970
|
-
for (const parts of REQUIRED_PROMPT_RESOURCE_FILES) {
|
|
971
|
-
const path = join(paths.home, ...parts);
|
|
972
|
-
if (!(await fileExists(path))) reasons.push(`missing ${path}`);
|
|
973
|
-
}
|
|
974
|
-
return reasons;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
async function hostRelayDriftReasons(paths) {
|
|
978
|
-
const reasons = [];
|
|
979
|
-
const hostsRoot = join(paths.pythonpath, "okstra_ctl", "adapters", "hosts");
|
|
980
|
-
let entries;
|
|
981
|
-
try {
|
|
982
|
-
entries = await fs.readdir(hostsRoot, { withFileTypes: true });
|
|
983
|
-
} catch (err) {
|
|
984
|
-
if (err.code === "ENOENT") return [`missing ${hostsRoot}`];
|
|
985
|
-
throw err;
|
|
986
|
-
}
|
|
987
|
-
for (const entry of entries) {
|
|
988
|
-
if (!entry.isDirectory()) continue;
|
|
989
|
-
const adapterRoot = join(hostsRoot, entry.name);
|
|
990
|
-
const manifestPath = join(adapterRoot, "manifest.json");
|
|
991
|
-
if (!(await fileExists(manifestPath))) continue;
|
|
992
|
-
let manifest;
|
|
993
|
-
try {
|
|
994
|
-
manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
995
|
-
} catch {
|
|
996
|
-
reasons.push(`invalid ${manifestPath}`);
|
|
997
|
-
continue;
|
|
998
|
-
}
|
|
999
|
-
const relayContract = manifest?.relayContract;
|
|
1000
|
-
if (typeof relayContract !== "string" || !relayContract) {
|
|
1001
|
-
reasons.push(`invalid relayContract in ${manifestPath}`);
|
|
1002
|
-
continue;
|
|
1003
|
-
}
|
|
1004
|
-
const relayPath = resolveAbs(adapterRoot, relayContract);
|
|
1005
|
-
const relayRelative = relative(adapterRoot, relayPath);
|
|
1006
|
-
if (relayRelative.startsWith("..") || resolveAbs(adapterRoot) === relayPath) {
|
|
1007
|
-
reasons.push(`invalid relayContract in ${manifestPath}`);
|
|
1008
|
-
} else if (!(await fileExists(relayPath))) {
|
|
1009
|
-
reasons.push(`missing ${relayPath}`);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
return reasons;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
async function agentDriftReasons(paths) {
|
|
1016
|
-
const reasons = [];
|
|
1017
|
-
const workersDir = join(paths.agents, "workers");
|
|
1018
|
-
const expected = await listWorkerFiles(workersDir);
|
|
1019
|
-
if (expected.length === 0) return reasons;
|
|
1020
|
-
for (const name of expected) {
|
|
1021
|
-
const target = join(CLAUDE_AGENTS_DIR, name);
|
|
1022
|
-
try {
|
|
1023
|
-
const lst = await fs.lstat(target);
|
|
1024
|
-
if (lst.isSymbolicLink()) {
|
|
1025
|
-
try {
|
|
1026
|
-
await fs.stat(target);
|
|
1027
|
-
} catch {
|
|
1028
|
-
reasons.push(`dangling symlink ${target}`);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
} catch {
|
|
1032
|
-
reasons.push(`missing agent ${target}`);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
return reasons;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
// What the stamp did, not just where it landed: `0.165.3 -> 0.166.0` and
|
|
1039
|
-
// `0.166.0 (unchanged)` are the two cases a bare version line cannot tell apart,
|
|
1040
|
-
// and they mean opposite things about whether the install did anything.
|
|
1041
|
-
export function formatVersionTransition(previous, next) {
|
|
1042
|
-
const from = String(previous ?? "").trim();
|
|
1043
|
-
if (!from) return next;
|
|
1044
|
-
return from === next ? `${next} (unchanged)` : `${from} -> ${next}`;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
function summarise(label, result, target) {
|
|
1048
|
-
if (result.missingSource) {
|
|
1049
|
-
process.stdout.write(` ${label}: source directory missing — skipped\n`);
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
process.stdout.write(
|
|
1053
|
-
` ${label}: copied=${result.copied} skipped=${result.skipped} -> ${target}\n`,
|
|
1054
|
-
);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
// `0.156.0` → [0, 156, 0]. Anything else → null, so a caller falls back to the
|
|
1058
|
-
// plain inequality test rather than guessing an ordering.
|
|
1059
|
-
function parseSemver(value) {
|
|
1060
|
-
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(String(value ?? "").trim());
|
|
1061
|
-
return match ? match.slice(1, 4).map(Number) : null;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
export function installedStampIsNewer(stamp, packageVersion) {
|
|
1065
|
-
const installed = parseSemver(stamp);
|
|
1066
|
-
const running = parseSemver(packageVersion);
|
|
1067
|
-
if (!installed || !running) return false;
|
|
1068
|
-
for (let i = 0; i < 3; i++) {
|
|
1069
|
-
if (installed[i] !== running[i]) return installed[i] > running[i];
|
|
1070
|
-
}
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
export function parseEnsureInstalledArgs(args) {
|
|
1075
|
-
const result = {
|
|
1076
|
-
quiet: false,
|
|
1077
|
-
runtime: "auto",
|
|
1078
|
-
};
|
|
1079
|
-
for (let i = 0; i < args.length; i++) {
|
|
1080
|
-
const arg = args[i];
|
|
1081
|
-
if (arg === "-q" || arg === "--quiet") {
|
|
1082
|
-
result.quiet = true;
|
|
1083
|
-
} else if (arg === "--runtime" || arg.startsWith("--runtime=")) {
|
|
1084
|
-
const parsed = readRuntimeFlag(args, i);
|
|
1085
|
-
result.runtime = validateInstallRuntime(parsed.value);
|
|
1086
|
-
i = parsed.nextIndex;
|
|
1087
|
-
} else {
|
|
1088
|
-
throw new Error(`unknown argument '${arg}'`);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
return result;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
export async function runEnsureInstalled(args) {
|
|
1095
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
1096
|
-
process.stdout.write(ENSURE_USAGE);
|
|
1097
|
-
return 0;
|
|
1098
|
-
}
|
|
1099
|
-
let opts;
|
|
1100
|
-
try {
|
|
1101
|
-
opts = parseEnsureInstalledArgs(args);
|
|
1102
|
-
} catch (err) {
|
|
1103
|
-
process.stderr.write(`error: ${err.message}\n`);
|
|
1104
|
-
return 2;
|
|
1105
|
-
}
|
|
1106
|
-
const paths = await resolvePaths();
|
|
1107
|
-
const runtimeResolution = await resolveInstallRuntime(opts.runtime, "ensure-installed");
|
|
1108
|
-
if (!runtimeResolution.ok && opts.runtime !== "auto") {
|
|
1109
|
-
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
1110
|
-
return 2;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
// A newer install than the package running this check means an upgrade
|
|
1114
|
-
// arrived by another route — `npx -y okstra@latest install`, or an install
|
|
1115
|
-
// from a checkout — while this command still resolves an older payload.
|
|
1116
|
-
// Reinstalling here would quietly DOWNGRADE the user's runtime, and every
|
|
1117
|
-
// skill preflight calls this, so the downgrade lands on the next command
|
|
1118
|
-
// after the upgrade. Refuse and say so; an explicit `okstra install` is
|
|
1119
|
-
// still honoured, because that one is the user's own decision.
|
|
1120
|
-
if (installedStampIsNewer(paths.version, paths.package)) {
|
|
1121
|
-
process.stderr.write(
|
|
1122
|
-
`okstra: installed runtime ${paths.version} is newer than this package ` +
|
|
1123
|
-
`(${paths.package}) — skipping auto-install so it is not downgraded. ` +
|
|
1124
|
-
`Run 'okstra install' explicitly to force this package's payload, or ` +
|
|
1125
|
-
`update the okstra package itself.\n`,
|
|
1126
|
-
);
|
|
1127
|
-
return 0;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
const reasons = [];
|
|
1131
|
-
if (!paths.version) reasons.push("no version stamp");
|
|
1132
|
-
else if (paths.version !== paths.package) {
|
|
1133
|
-
reasons.push(`version drift (stamp=${paths.version}, package=${paths.package})`);
|
|
1134
|
-
}
|
|
1135
|
-
if (!(await dirExists(paths.pythonpath))) reasons.push(`missing ${paths.pythonpath}`);
|
|
1136
|
-
if (!(await dirExists(paths.agents))) reasons.push(`missing agents dir ${paths.agents}`);
|
|
1137
|
-
for (const relative of [
|
|
1138
|
-
["schemas", "final-report-v2.0.schema.json"],
|
|
1139
|
-
["templates", "reports", "final-report-v2.template.md"],
|
|
1140
|
-
["templates", "reports", "html", "base.template.html"],
|
|
1141
|
-
]) {
|
|
1142
|
-
const installedPath = join(paths.home, ...relative);
|
|
1143
|
-
if (!(await fileExists(installedPath))) reasons.push(`missing ${installedPath}`);
|
|
1144
|
-
}
|
|
1145
|
-
for (const reason of await promptResourceDriftReasons(paths)) {
|
|
1146
|
-
reasons.push(reason);
|
|
1147
|
-
}
|
|
1148
|
-
for (const reason of await hostRelayDriftReasons(paths)) {
|
|
1149
|
-
reasons.push(reason);
|
|
1150
|
-
}
|
|
1151
|
-
const skillTargets = await detectSkillTargets();
|
|
1152
|
-
for (const reason of await skillTargetDriftReasons(skillTargets)) {
|
|
1153
|
-
reasons.push(reason);
|
|
1154
|
-
}
|
|
1155
|
-
if (skillTargets.some((target) => target.provider === "claude")) {
|
|
1156
|
-
for (const reason of await agentDriftReasons(paths)) {
|
|
1157
|
-
reasons.push(reason);
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (reasons.length === 0) {
|
|
1162
|
-
if (!opts.quiet) process.stdout.write(`okstra runtime OK (package ${paths.package})\n`);
|
|
1163
|
-
return 0;
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
process.stderr.write(`okstra runtime needs install: ${reasons.join("; ")}\n`);
|
|
1167
|
-
const installArgs = ["--runtime", opts.runtime];
|
|
1168
|
-
if (opts.quiet) installArgs.push("-q");
|
|
1169
|
-
return await runInstall(installArgs);
|
|
1170
|
-
}
|