okstra 0.102.1 → 0.102.3
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.kr.md +5 -6
- package/README.md +5 -6
- package/docs/kr/architecture/storage-model.md +1 -1
- package/docs/kr/architecture.md +8 -12
- package/docs/kr/cli.md +6 -6
- package/docs/kr/container.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +2 -2
- package/docs/project-structure-overview.md +2 -2
- package/package.json +7 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-antigravity-exec.sh +1 -2
- package/runtime/bin/okstra-codex-exec.sh +1 -2
- package/runtime/bin/okstra-render-final-report.py +2 -9
- package/runtime/bin/okstra-spawn-followups.py +4 -6
- package/runtime/bin/okstra-trace-cleanup.sh +24 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +9 -11
- package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
- package/runtime/python/okstra_ctl/consumers.py +35 -0
- package/runtime/python/okstra_ctl/container.py +4 -6
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
- package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
- package/runtime/python/okstra_ctl/handoff.py +2 -1
- package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
- package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +7 -4
- package/runtime/python/okstra_ctl/run.py +3 -10
- package/runtime/python/okstra_ctl/stage_targets.py +18 -0
- package/runtime/python/okstra_ctl/wizard.py +24 -25
- package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
- package/runtime/python/okstra_token_usage/report.py +2 -5
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +1 -11
- package/src/cli-registry.mjs +1 -1
- package/src/commands/lifecycle/install.mjs +10 -20
- package/src/commands/lifecycle/uninstall.mjs +7 -10
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
- package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
- package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
- package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
- package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
- package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
- package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
- package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
- package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
- package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
- package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
- package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
- package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
- package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
- package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
- package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
- package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
- package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
- package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
- package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
- package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
- package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
- package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
- package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
- package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
- package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
- package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
- package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
- package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
- package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
- package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
- package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
- package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
- package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
- package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
- package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
- package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
- package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
- package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
- package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
- package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
- package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
- package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
- package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
- package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
- package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
- package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
- package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
- package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
- package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
- package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
- package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
- package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
- package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
- package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
- package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
- package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
- package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
- package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
- package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
- package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
- package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
- package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
- package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
- package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
- package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
- package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
- package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
- package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
- package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
- package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
- package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
- package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
- package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
- package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
- package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
- package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
- package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
- package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
|
@@ -1,1775 +0,0 @@
|
|
|
1
|
-
# Host Runtime Auto Execution Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Add host-based runtime auto-detection and auto-execution so `okstra` can pick Claude Code, Codex, or external/tmux execution safely from the current host.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Add a Node runtime resolver as the single reference point for host/capability decisions. Keep Python `prepare_task_bundle()` provider-neutral and render-only for non-Claude runtimes; Node high-level orchestration resolves runtime and calls the existing low-level commands. Replace ambiguous v1 install manifest adapter flags with v2 request/resolution/assets fields while preserving v1 reads.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Node ESM, `node:test`, Python 3.11, pytest, existing `okstra_ctl.run` renderer, existing `okstra team` and `okstra codex-*` CLIs.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## File Structure
|
|
14
|
-
|
|
15
|
-
- Create `src/runtime-resolver.mjs`
|
|
16
|
-
- Owns runtime request validation, host signal detection, capability normalization, safe fallback, and resolution payload shape.
|
|
17
|
-
- Create `tests-js/runtime-resolver.test.mjs`
|
|
18
|
-
- Unit-test resolver decisions without shelling out.
|
|
19
|
-
- Modify `src/runtime-manifest.mjs`
|
|
20
|
-
- Write v2 install manifests and read both v1/v2 for Claude asset checks.
|
|
21
|
-
- Modify `src/install.mjs`
|
|
22
|
-
- Accept `auto`, default install/ensure to `auto`, resolve before copying runtime-specific assets, write v2 manifest.
|
|
23
|
-
- Modify `tests-js/install-runtime.test.mjs`
|
|
24
|
-
- Cover auto defaults, v2 manifest, external host install, v1 compatibility.
|
|
25
|
-
- Modify `src/doctor.mjs`
|
|
26
|
-
- Accept/default `auto`, resolve before selecting checks, emit `runtimeResolution` in JSON.
|
|
27
|
-
- Modify `tests-js/doctor-runtime.test.mjs`
|
|
28
|
-
- Cover auto defaults and JSON resolution.
|
|
29
|
-
- Modify `src/render-bundle.mjs`
|
|
30
|
-
- Default `--lead-runtime auto`, resolve to explicit runtime before calling Python, pass resolution metadata.
|
|
31
|
-
- Modify `scripts/okstra_ctl/run.py`
|
|
32
|
-
- Accept `--lead-runtime-request` and `--runtime-resolution-json`, store them in `ctx`, print `okstra run manifest:` summary line.
|
|
33
|
-
- Modify `scripts/okstra_ctl/render.py`
|
|
34
|
-
- Write `leadRuntimeRequest` and `runtimeResolution` into run manifest.
|
|
35
|
-
- Modify `tests/test_lead_runtime_marker.py`
|
|
36
|
-
- Cover run manifest request/resolution fields.
|
|
37
|
-
- Create `src/run.mjs`
|
|
38
|
-
- High-level CLI driver for Codex/external auto execution; fail with `/okstra-run` guidance for bare Claude Code resolution.
|
|
39
|
-
- Create `tests-js/run.test.mjs`
|
|
40
|
-
- Unit-test command planning and stdout parsing without dispatching real workers.
|
|
41
|
-
- Modify `src/cli-registry.mjs`
|
|
42
|
-
- Register `run` and update command counts/help.
|
|
43
|
-
- Modify `tests-js/cli-registry.test.mjs`
|
|
44
|
-
- Update command counts and help assertions.
|
|
45
|
-
- Modify `skills/okstra-run/SKILL.md`
|
|
46
|
-
- Mark Claude Code host explicitly via `OKSTRA_RUNTIME_HOST=claude-code` and keep slash-skill path as the Claude front door.
|
|
47
|
-
- Modify `docs/project-structure-overview.md`, `docs/kr/cli.md`, `docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md` only if implementation details diverge from the approved design.
|
|
48
|
-
- Modify `tests/test_docs_runtime_contract.py`
|
|
49
|
-
- Pin auto/default/runtime manifest v2 documentation.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
### Task 1: Runtime Resolver
|
|
54
|
-
|
|
55
|
-
**Files:**
|
|
56
|
-
- Create: `src/runtime-resolver.mjs`
|
|
57
|
-
- Create: `tests-js/runtime-resolver.test.mjs`
|
|
58
|
-
|
|
59
|
-
- [ ] **Step 1: Write the failing resolver tests**
|
|
60
|
-
|
|
61
|
-
Create `tests-js/runtime-resolver.test.mjs` with:
|
|
62
|
-
|
|
63
|
-
```js
|
|
64
|
-
import test from "node:test";
|
|
65
|
-
import assert from "node:assert/strict";
|
|
66
|
-
|
|
67
|
-
import {
|
|
68
|
-
RUNTIME_AUTO,
|
|
69
|
-
VALID_RUNTIME_REQUESTS,
|
|
70
|
-
normalizeRuntimeRequest,
|
|
71
|
-
resolveRuntime,
|
|
72
|
-
} from "../src/runtime-resolver.mjs";
|
|
73
|
-
|
|
74
|
-
const BASE_CAPABILITIES = Object.freeze({
|
|
75
|
-
claudeAssetsInstalled: false,
|
|
76
|
-
codexCliAvailable: false,
|
|
77
|
-
tmuxAvailable: false,
|
|
78
|
-
claudeSkillHandoff: false,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
function resolve(overrides = {}) {
|
|
82
|
-
return resolveRuntime({
|
|
83
|
-
requestedRuntime: overrides.requestedRuntime ?? RUNTIME_AUTO,
|
|
84
|
-
command: overrides.command ?? "run",
|
|
85
|
-
env: overrides.env ?? {},
|
|
86
|
-
capabilities: { ...BASE_CAPABILITIES, ...(overrides.capabilities ?? {}) },
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
test("valid runtime requests include auto and explicit runtimes", () => {
|
|
91
|
-
assert.deepEqual(VALID_RUNTIME_REQUESTS, [
|
|
92
|
-
"auto",
|
|
93
|
-
"claude-code",
|
|
94
|
-
"codex",
|
|
95
|
-
"external",
|
|
96
|
-
"all",
|
|
97
|
-
]);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("normalizeRuntimeRequest defaults blank values to auto", () => {
|
|
101
|
-
assert.equal(normalizeRuntimeRequest(""), "auto");
|
|
102
|
-
assert.equal(normalizeRuntimeRequest(undefined), "auto");
|
|
103
|
-
assert.equal(normalizeRuntimeRequest(" external "), "external");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("normalizeRuntimeRequest rejects unknown values", () => {
|
|
107
|
-
assert.throws(
|
|
108
|
-
() => normalizeRuntimeRequest("cursor"),
|
|
109
|
-
/unknown runtime 'cursor'/,
|
|
110
|
-
);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("explicit runtime bypasses host auto detection", () => {
|
|
114
|
-
const result = resolve({
|
|
115
|
-
requestedRuntime: "codex",
|
|
116
|
-
env: { OKSTRA_RUNTIME_HOST: "external" },
|
|
117
|
-
capabilities: { tmuxAvailable: true },
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
assert.equal(result.requestedRuntime, "codex");
|
|
121
|
-
assert.equal(result.resolvedRuntime, "codex");
|
|
122
|
-
assert.equal(result.host, "explicit");
|
|
123
|
-
assert.equal(result.fallbackFrom, null);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("auto uses explicit OKSTRA_RUNTIME_HOST for Claude Code", () => {
|
|
127
|
-
const result = resolve({
|
|
128
|
-
env: { OKSTRA_RUNTIME_HOST: "claude-code" },
|
|
129
|
-
capabilities: { claudeAssetsInstalled: true, claudeSkillHandoff: true },
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
assert.equal(result.resolvedRuntime, "claude-code");
|
|
133
|
-
assert.equal(result.host, "claude-code");
|
|
134
|
-
assert.match(result.reason, /OKSTRA_RUNTIME_HOST=claude-code/);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("auto uses explicit OKSTRA_RUNTIME_HOST for Codex", () => {
|
|
138
|
-
const result = resolve({
|
|
139
|
-
env: { OKSTRA_RUNTIME_HOST: "codex" },
|
|
140
|
-
capabilities: { codexCliAvailable: true },
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
assert.equal(result.resolvedRuntime, "codex");
|
|
144
|
-
assert.equal(result.host, "codex");
|
|
145
|
-
assert.match(result.reason, /OKSTRA_RUNTIME_HOST=codex/);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("auto uses explicit OKSTRA_RUNTIME_HOST for external", () => {
|
|
149
|
-
const result = resolve({
|
|
150
|
-
env: { OKSTRA_RUNTIME_HOST: "external" },
|
|
151
|
-
capabilities: { tmuxAvailable: true },
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
assert.equal(result.resolvedRuntime, "external");
|
|
155
|
-
assert.equal(result.host, "external");
|
|
156
|
-
assert.match(result.reason, /OKSTRA_RUNTIME_HOST=external/);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("binary availability alone does not choose Codex host", () => {
|
|
160
|
-
const result = resolve({ capabilities: { codexCliAvailable: true, tmuxAvailable: true } });
|
|
161
|
-
|
|
162
|
-
assert.equal(result.resolvedRuntime, "external");
|
|
163
|
-
assert.equal(result.host, "generic-terminal");
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("generic terminal with tmux resolves external", () => {
|
|
167
|
-
const result = resolve({ capabilities: { tmuxAvailable: true } });
|
|
168
|
-
|
|
169
|
-
assert.equal(result.resolvedRuntime, "external");
|
|
170
|
-
assert.equal(result.host, "generic-terminal");
|
|
171
|
-
assert.match(result.reason, /tmux is available/);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("unknown host without tmux fails fast", () => {
|
|
175
|
-
const result = resolve();
|
|
176
|
-
|
|
177
|
-
assert.equal(result.resolvedRuntime, null);
|
|
178
|
-
assert.equal(result.host, "unknown");
|
|
179
|
-
assert.equal(result.ok, false);
|
|
180
|
-
assert.match(result.reason, /host unknown/);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("all remains an explicit admin runtime", () => {
|
|
184
|
-
const result = resolve({ requestedRuntime: "all" });
|
|
185
|
-
|
|
186
|
-
assert.equal(result.resolvedRuntime, "all");
|
|
187
|
-
assert.equal(result.host, "explicit");
|
|
188
|
-
});
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
- [ ] **Step 2: Run the resolver tests and verify they fail**
|
|
192
|
-
|
|
193
|
-
Run:
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
node --test tests-js/runtime-resolver.test.mjs
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Expected: FAIL with `Cannot find module ... src/runtime-resolver.mjs`.
|
|
200
|
-
|
|
201
|
-
- [ ] **Step 3: Implement the minimal resolver**
|
|
202
|
-
|
|
203
|
-
Create `src/runtime-resolver.mjs` with:
|
|
204
|
-
|
|
205
|
-
```js
|
|
206
|
-
export const RUNTIME_AUTO = "auto";
|
|
207
|
-
export const EXPLICIT_RUNTIMES = ["claude-code", "codex", "external"];
|
|
208
|
-
export const VALID_RUNTIME_REQUESTS = [RUNTIME_AUTO, ...EXPLICIT_RUNTIMES, "all"];
|
|
209
|
-
|
|
210
|
-
const VALID_HOSTS = new Set(EXPLICIT_RUNTIMES);
|
|
211
|
-
const VALID_REQUESTS = new Set(VALID_RUNTIME_REQUESTS);
|
|
212
|
-
|
|
213
|
-
function clean(value) {
|
|
214
|
-
return String(value ?? "").trim();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export function normalizeRuntimeRequest(value) {
|
|
218
|
-
const runtime = clean(value) || RUNTIME_AUTO;
|
|
219
|
-
if (!VALID_REQUESTS.has(runtime)) {
|
|
220
|
-
throw new Error(`unknown runtime '${runtime}' (expected: ${VALID_RUNTIME_REQUESTS.join(", ")})`);
|
|
221
|
-
}
|
|
222
|
-
return runtime;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function normalizeCapabilities(capabilities = {}) {
|
|
226
|
-
return {
|
|
227
|
-
claudeAssetsInstalled: capabilities.claudeAssetsInstalled === true,
|
|
228
|
-
codexCliAvailable: capabilities.codexCliAvailable === true,
|
|
229
|
-
tmuxAvailable: capabilities.tmuxAvailable === true,
|
|
230
|
-
claudeSkillHandoff: capabilities.claudeSkillHandoff === true,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function explicitResult(runtime) {
|
|
235
|
-
return {
|
|
236
|
-
ok: true,
|
|
237
|
-
requestedRuntime: runtime,
|
|
238
|
-
resolvedRuntime: runtime,
|
|
239
|
-
host: "explicit",
|
|
240
|
-
confidence: "high",
|
|
241
|
-
reason: `Explicit runtime '${runtime}' requested.`,
|
|
242
|
-
fallbackFrom: null,
|
|
243
|
-
warnings: [],
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function hostOverride(env) {
|
|
248
|
-
const host = clean(env?.OKSTRA_RUNTIME_HOST);
|
|
249
|
-
return VALID_HOSTS.has(host) ? host : "";
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function okResult({ requestedRuntime, resolvedRuntime, host, reason, warnings = [] }) {
|
|
253
|
-
return {
|
|
254
|
-
ok: true,
|
|
255
|
-
requestedRuntime,
|
|
256
|
-
resolvedRuntime,
|
|
257
|
-
host,
|
|
258
|
-
confidence: "high",
|
|
259
|
-
reason,
|
|
260
|
-
fallbackFrom: null,
|
|
261
|
-
warnings,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function failResult(requestedRuntime, reason, warnings = []) {
|
|
266
|
-
return {
|
|
267
|
-
ok: false,
|
|
268
|
-
requestedRuntime,
|
|
269
|
-
resolvedRuntime: null,
|
|
270
|
-
host: "unknown",
|
|
271
|
-
confidence: "low",
|
|
272
|
-
reason,
|
|
273
|
-
fallbackFrom: null,
|
|
274
|
-
warnings,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function resolveRuntime({
|
|
279
|
-
requestedRuntime = RUNTIME_AUTO,
|
|
280
|
-
command = "run",
|
|
281
|
-
env = {},
|
|
282
|
-
capabilities = {},
|
|
283
|
-
} = {}) {
|
|
284
|
-
const request = normalizeRuntimeRequest(requestedRuntime);
|
|
285
|
-
const caps = normalizeCapabilities(capabilities);
|
|
286
|
-
void command;
|
|
287
|
-
|
|
288
|
-
if (request !== RUNTIME_AUTO) return explicitResult(request);
|
|
289
|
-
|
|
290
|
-
const host = hostOverride(env);
|
|
291
|
-
if (host) {
|
|
292
|
-
return okResult({
|
|
293
|
-
requestedRuntime: request,
|
|
294
|
-
resolvedRuntime: host,
|
|
295
|
-
host,
|
|
296
|
-
reason: `OKSTRA_RUNTIME_HOST=${host} selected runtime '${host}'.`,
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (caps.claudeSkillHandoff) {
|
|
301
|
-
return okResult({
|
|
302
|
-
requestedRuntime: request,
|
|
303
|
-
resolvedRuntime: "claude-code",
|
|
304
|
-
host: "claude-code",
|
|
305
|
-
reason: "Claude Code skill handoff selected runtime 'claude-code'.",
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (caps.tmuxAvailable) {
|
|
310
|
-
return okResult({
|
|
311
|
-
requestedRuntime: request,
|
|
312
|
-
resolvedRuntime: "external",
|
|
313
|
-
host: "generic-terminal",
|
|
314
|
-
reason: "No Claude Code or Codex host signal; tmux is available.",
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return failResult(
|
|
319
|
-
request,
|
|
320
|
-
"host unknown: set OKSTRA_RUNTIME_HOST=claude-code|codex|external or pass --lead-runtime explicitly.",
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
- [ ] **Step 4: Run the resolver tests and verify they pass**
|
|
326
|
-
|
|
327
|
-
Run:
|
|
328
|
-
|
|
329
|
-
```bash
|
|
330
|
-
node --test tests-js/runtime-resolver.test.mjs
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Expected: PASS, 11 tests.
|
|
334
|
-
|
|
335
|
-
- [ ] **Step 5: Commit Task 1**
|
|
336
|
-
|
|
337
|
-
Run:
|
|
338
|
-
|
|
339
|
-
```bash
|
|
340
|
-
git add src/runtime-resolver.mjs tests-js/runtime-resolver.test.mjs
|
|
341
|
-
git commit -m "feat(runtime): add host runtime resolver"
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
|
-
### Task 2: Runtime Manifest v2
|
|
347
|
-
|
|
348
|
-
**Files:**
|
|
349
|
-
- Modify: `src/runtime-manifest.mjs`
|
|
350
|
-
- Modify: `tests-js/install-runtime.test.mjs`
|
|
351
|
-
|
|
352
|
-
- [ ] **Step 1: Add failing manifest v2 tests**
|
|
353
|
-
|
|
354
|
-
In `tests-js/install-runtime.test.mjs`, extend the import from `runtime-manifest.mjs`:
|
|
355
|
-
|
|
356
|
-
```js
|
|
357
|
-
import {
|
|
358
|
-
buildRuntimeManifest,
|
|
359
|
-
installedRuntimesFromManifest,
|
|
360
|
-
runtimeManifestIncludesClaudeAssets,
|
|
361
|
-
} from "../src/runtime-manifest.mjs";
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Add these tests after the existing runtime manifest tests:
|
|
365
|
-
|
|
366
|
-
```js
|
|
367
|
-
test("runtime manifest v2 separates install request from resolved runtime", () => {
|
|
368
|
-
const resolution = {
|
|
369
|
-
requestedRuntime: "auto",
|
|
370
|
-
resolvedRuntime: "external",
|
|
371
|
-
host: "generic-terminal",
|
|
372
|
-
confidence: "high",
|
|
373
|
-
reason: "No Claude Code or Codex host signal; tmux is available.",
|
|
374
|
-
fallbackFrom: null,
|
|
375
|
-
warnings: [],
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const manifest = buildRuntimeManifest("auto", "2026-06-14T00:00:00Z", resolution);
|
|
379
|
-
|
|
380
|
-
assert.equal(manifest.schemaVersion, 2);
|
|
381
|
-
assert.equal(manifest.installRequest, "auto");
|
|
382
|
-
assert.deepEqual(manifest.installedRuntimes, ["external"]);
|
|
383
|
-
assert.deepEqual(manifest.runtimeResolution, resolution);
|
|
384
|
-
assert.equal(manifest.installedAssets.sharedRuntime, true);
|
|
385
|
-
assert.equal(manifest.installedAssets.claudeSkills, false);
|
|
386
|
-
assert.equal(manifest.installedAssets.claudeAgents, false);
|
|
387
|
-
assert.equal("codexAdapter" in manifest.installedAssets, false);
|
|
388
|
-
assert.equal("externalAdapter" in manifest.installedAssets, false);
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("runtime manifest v2 records all admin install", () => {
|
|
392
|
-
const manifest = buildRuntimeManifest("all", "2026-06-14T00:00:00Z");
|
|
393
|
-
|
|
394
|
-
assert.equal(manifest.schemaVersion, 2);
|
|
395
|
-
assert.equal(manifest.installRequest, "all");
|
|
396
|
-
assert.deepEqual(manifest.installedRuntimes, ["claude-code", "codex", "external"]);
|
|
397
|
-
assert.equal(runtimeManifestIncludesClaudeAssets(manifest), true);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
test("runtime manifest readers preserve v1 compatibility", () => {
|
|
401
|
-
const v1 = {
|
|
402
|
-
installedAt: "2026-06-12T00:00:00Z",
|
|
403
|
-
runtime: "claude-code",
|
|
404
|
-
runtimes: ["claude-code"],
|
|
405
|
-
assets: {
|
|
406
|
-
sharedRuntime: true,
|
|
407
|
-
claudeSkills: true,
|
|
408
|
-
claudeAgents: true,
|
|
409
|
-
codexAdapter: false,
|
|
410
|
-
externalAdapter: false,
|
|
411
|
-
},
|
|
412
|
-
version: 1,
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
assert.equal(runtimeManifestIncludesClaudeAssets(v1), true);
|
|
416
|
-
assert.deepEqual(installedRuntimesFromManifest(v1), ["claude-code"]);
|
|
417
|
-
});
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
- [ ] **Step 2: Run the manifest tests and verify they fail**
|
|
421
|
-
|
|
422
|
-
Run:
|
|
423
|
-
|
|
424
|
-
```bash
|
|
425
|
-
node --test tests-js/install-runtime.test.mjs
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
Expected: FAIL because `installedRuntimesFromManifest` is not exported and `buildRuntimeManifest("auto", ...)` still writes v1-style fields.
|
|
429
|
-
|
|
430
|
-
- [ ] **Step 3: Implement v2 manifest writing and v1 reads**
|
|
431
|
-
|
|
432
|
-
Replace `src/runtime-manifest.mjs` with:
|
|
433
|
-
|
|
434
|
-
```js
|
|
435
|
-
export const RUNTIMES_MANIFEST_REL = "installed-runtimes.json";
|
|
436
|
-
export const RUNTIME_MANIFEST_SCHEMA_VERSION = 2;
|
|
437
|
-
export const EXPLICIT_RUNTIME_VALUES = ["claude-code", "codex", "external"];
|
|
438
|
-
|
|
439
|
-
export function runtimeListForSelection(runtime, resolution = null) {
|
|
440
|
-
if (runtime === "all") return [...EXPLICIT_RUNTIME_VALUES];
|
|
441
|
-
if (runtime === "auto") {
|
|
442
|
-
const resolved = resolution?.resolvedRuntime;
|
|
443
|
-
return resolved && resolved !== "all" ? [resolved] : [];
|
|
444
|
-
}
|
|
445
|
-
return [runtime];
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function includesClaudeRuntime(runtime, resolution = null) {
|
|
449
|
-
if (runtime === "all") return true;
|
|
450
|
-
if (runtime === "claude-code") return true;
|
|
451
|
-
return runtime === "auto" && resolution?.resolvedRuntime === "claude-code";
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function defaultResolution(runtime) {
|
|
455
|
-
if (runtime === "auto") return null;
|
|
456
|
-
return {
|
|
457
|
-
requestedRuntime: runtime,
|
|
458
|
-
resolvedRuntime: runtime,
|
|
459
|
-
host: "explicit",
|
|
460
|
-
confidence: "high",
|
|
461
|
-
reason: `Explicit runtime '${runtime}' requested.`,
|
|
462
|
-
fallbackFrom: null,
|
|
463
|
-
warnings: [],
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export function buildRuntimeManifest(runtime, installedAt = new Date().toISOString(), resolution = null) {
|
|
468
|
-
const runtimeResolution = resolution ?? defaultResolution(runtime);
|
|
469
|
-
const includesClaude = includesClaudeRuntime(runtime, runtimeResolution);
|
|
470
|
-
return {
|
|
471
|
-
schemaVersion: RUNTIME_MANIFEST_SCHEMA_VERSION,
|
|
472
|
-
installedAt,
|
|
473
|
-
installRequest: runtime,
|
|
474
|
-
runtimeResolution,
|
|
475
|
-
installedRuntimes: runtimeListForSelection(runtime, runtimeResolution),
|
|
476
|
-
installedAssets: {
|
|
477
|
-
sharedRuntime: true,
|
|
478
|
-
claudeSkills: includesClaude,
|
|
479
|
-
claudeAgents: includesClaude,
|
|
480
|
-
binWrappers: true,
|
|
481
|
-
templates: true,
|
|
482
|
-
schemas: true,
|
|
483
|
-
validators: true,
|
|
484
|
-
},
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
export function installedRuntimesFromManifest(data) {
|
|
489
|
-
if (Array.isArray(data?.installedRuntimes)) return data.installedRuntimes;
|
|
490
|
-
if (Array.isArray(data?.runtimes)) return data.runtimes;
|
|
491
|
-
if (typeof data?.runtime === "string" && data.runtime) return runtimeListForSelection(data.runtime);
|
|
492
|
-
return [];
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
export function runtimeManifestIncludesClaudeAssets(data) {
|
|
496
|
-
if (data?.schemaVersion === RUNTIME_MANIFEST_SCHEMA_VERSION) {
|
|
497
|
-
return data?.installedAssets?.claudeSkills === true || data?.installedAssets?.claudeAgents === true;
|
|
498
|
-
}
|
|
499
|
-
return data?.assets?.claudeSkills === true || data?.assets?.claudeAgents === true;
|
|
500
|
-
}
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
- [ ] **Step 4: Update old v1 expectations in `tests-js/install-runtime.test.mjs`**
|
|
504
|
-
|
|
505
|
-
Change assertions that read `manifest.runtimes` / `manifest.assets` for newly built manifests to v2 fields:
|
|
506
|
-
|
|
507
|
-
```js
|
|
508
|
-
assert.deepEqual(manifest.installedRuntimes, ["codex"]);
|
|
509
|
-
assert.equal(manifest.installedAssets.sharedRuntime, true);
|
|
510
|
-
assert.equal(runtimeManifestIncludesClaudeAssets(manifest), false);
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
For `external`:
|
|
514
|
-
|
|
515
|
-
```js
|
|
516
|
-
assert.deepEqual(manifest.installedRuntimes, ["external"]);
|
|
517
|
-
assert.equal(manifest.installedAssets.sharedRuntime, true);
|
|
518
|
-
assert.equal(runtimeManifestIncludesClaudeAssets(manifest), false);
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
For `all`:
|
|
522
|
-
|
|
523
|
-
```js
|
|
524
|
-
assert.deepEqual(manifest.installedRuntimes, ["claude-code", "codex", "external"]);
|
|
525
|
-
assert.equal(runtimeManifestIncludesClaudeAssets(manifest), true);
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
- [ ] **Step 5: Run the manifest tests and verify they pass**
|
|
529
|
-
|
|
530
|
-
Run:
|
|
531
|
-
|
|
532
|
-
```bash
|
|
533
|
-
node --test tests-js/install-runtime.test.mjs
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
Expected: PASS.
|
|
537
|
-
|
|
538
|
-
- [ ] **Step 6: Commit Task 2**
|
|
539
|
-
|
|
540
|
-
Run:
|
|
541
|
-
|
|
542
|
-
```bash
|
|
543
|
-
git add src/runtime-manifest.mjs tests-js/install-runtime.test.mjs
|
|
544
|
-
git commit -m "feat(runtime): write runtime manifest v2"
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
---
|
|
548
|
-
|
|
549
|
-
### Task 3: Install and Ensure Default to Auto
|
|
550
|
-
|
|
551
|
-
**Files:**
|
|
552
|
-
- Modify: `src/install.mjs`
|
|
553
|
-
- Modify: `tests-js/install-runtime.test.mjs`
|
|
554
|
-
|
|
555
|
-
- [ ] **Step 1: Add failing install auto tests**
|
|
556
|
-
|
|
557
|
-
In `tests-js/install-runtime.test.mjs`, extend the install argument tests:
|
|
558
|
-
|
|
559
|
-
```js
|
|
560
|
-
test("install defaults to auto runtime", () => {
|
|
561
|
-
assert.equal(parseInstallArgs([]).runtime, "auto");
|
|
562
|
-
assert.equal(runtimeIncludesClaudeAssets("auto", {
|
|
563
|
-
requestedRuntime: "auto",
|
|
564
|
-
resolvedRuntime: "external",
|
|
565
|
-
}), false);
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
test("ensure-installed defaults to auto runtime", () => {
|
|
569
|
-
assert.deepEqual(parseEnsureInstalledArgs([]), {
|
|
570
|
-
quiet: false,
|
|
571
|
-
runtime: "auto",
|
|
572
|
-
});
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
test("install accepts auto runtime explicitly", () => {
|
|
576
|
-
const opts = parseInstallArgs(["--runtime", "auto", "--dry-run"]);
|
|
577
|
-
|
|
578
|
-
assert.equal(opts.runtime, "auto");
|
|
579
|
-
assert.equal(opts.dryRun, true);
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
test("install auto external host writes manifest v2 without Claude assets", () => {
|
|
583
|
-
const { root, env } = makeInstallEnv();
|
|
584
|
-
const okstraHome = join(root, ".okstra");
|
|
585
|
-
|
|
586
|
-
const install = runCli(["install"], {
|
|
587
|
-
...env,
|
|
588
|
-
OKSTRA_RUNTIME_HOST: "external",
|
|
589
|
-
});
|
|
590
|
-
assert.equal(install.code, 0, install.stderr);
|
|
591
|
-
|
|
592
|
-
const installedRuntimes = JSON.parse(
|
|
593
|
-
readFileSync(join(okstraHome, "installed-runtimes.json"), "utf8"),
|
|
594
|
-
);
|
|
595
|
-
assert.equal(installedRuntimes.schemaVersion, 2);
|
|
596
|
-
assert.equal(installedRuntimes.installRequest, "auto");
|
|
597
|
-
assert.equal(installedRuntimes.runtimeResolution.resolvedRuntime, "external");
|
|
598
|
-
assert.deepEqual(installedRuntimes.installedRuntimes, ["external"]);
|
|
599
|
-
assert.equal(installedRuntimes.installedAssets.claudeSkills, false);
|
|
600
|
-
assert.equal(installedRuntimes.installedAssets.claudeAgents, false);
|
|
601
|
-
});
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
Remove or rewrite the old test named `install defaults to Claude Code runtime` so there is only one default-runtime assertion.
|
|
605
|
-
|
|
606
|
-
- [ ] **Step 2: Run install tests and verify they fail**
|
|
607
|
-
|
|
608
|
-
Run:
|
|
609
|
-
|
|
610
|
-
```bash
|
|
611
|
-
node --test tests-js/install-runtime.test.mjs
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
Expected: FAIL because `auto` is not accepted and defaults remain `claude-code`.
|
|
615
|
-
|
|
616
|
-
- [ ] **Step 3: Update runtime validation and Claude asset helper**
|
|
617
|
-
|
|
618
|
-
In `src/install.mjs`, import the resolver:
|
|
619
|
-
|
|
620
|
-
```js
|
|
621
|
-
import { resolveRuntime, normalizeRuntimeRequest } from "./runtime-resolver.mjs";
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
Change the runtime set and helper:
|
|
625
|
-
|
|
626
|
-
```js
|
|
627
|
-
const INSTALL_RUNTIMES = new Set(["auto", "claude-code", "codex", "external", "all"]);
|
|
628
|
-
|
|
629
|
-
export function runtimeIncludesClaudeAssets(runtime, resolution = null) {
|
|
630
|
-
if (runtime === "all" || runtime === "claude-code") return true;
|
|
631
|
-
return runtime === "auto" && resolution?.resolvedRuntime === "claude-code";
|
|
632
|
-
}
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
Change `validateInstallRuntime`:
|
|
636
|
-
|
|
637
|
-
```js
|
|
638
|
-
export function validateInstallRuntime(value) {
|
|
639
|
-
const runtime = normalizeRuntimeRequest(value);
|
|
640
|
-
if (!INSTALL_RUNTIMES.has(runtime)) {
|
|
641
|
-
throw new Error(`unknown runtime '${runtime}' (expected: auto, claude-code, codex, external, all)`);
|
|
642
|
-
}
|
|
643
|
-
return runtime;
|
|
644
|
-
}
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
Change parser defaults:
|
|
648
|
-
|
|
649
|
-
```js
|
|
650
|
-
const result = {
|
|
651
|
-
dryRun: false,
|
|
652
|
-
refresh: false,
|
|
653
|
-
quiet: false,
|
|
654
|
-
linkRepo: null,
|
|
655
|
-
runtime: "auto",
|
|
656
|
-
};
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
and:
|
|
660
|
-
|
|
661
|
-
```js
|
|
662
|
-
const result = {
|
|
663
|
-
quiet: false,
|
|
664
|
-
runtime: "auto",
|
|
665
|
-
};
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
- [ ] **Step 4: Resolve runtime inside install before asset-specific writes**
|
|
669
|
-
|
|
670
|
-
In `runInstall`, after `const paths = await resolvePaths();`, add:
|
|
671
|
-
|
|
672
|
-
```js
|
|
673
|
-
const runtimeResolution = resolveRuntime({
|
|
674
|
-
requestedRuntime: opts.runtime,
|
|
675
|
-
command: "install",
|
|
676
|
-
env: process.env,
|
|
677
|
-
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
678
|
-
});
|
|
679
|
-
if (!runtimeResolution.ok) {
|
|
680
|
-
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
681
|
-
return 2;
|
|
682
|
-
}
|
|
683
|
-
const resolvedRuntime = runtimeResolution.resolvedRuntime;
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
Replace output line:
|
|
687
|
-
|
|
688
|
-
```js
|
|
689
|
-
process.stdout.write(` runtime: ${opts.runtime} -> ${resolvedRuntime}\n`);
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
Replace Claude asset condition:
|
|
693
|
-
|
|
694
|
-
```js
|
|
695
|
-
if (runtimeIncludesClaudeAssets(opts.runtime, runtimeResolution)) {
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
Replace manifest write:
|
|
699
|
-
|
|
700
|
-
```js
|
|
701
|
-
await writeRuntimeManifest(paths.home, opts.runtime, {
|
|
702
|
-
dryRun: opts.dryRun,
|
|
703
|
-
resolution: runtimeResolution,
|
|
704
|
-
});
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
Update `writeRuntimeManifest` signature if needed:
|
|
708
|
-
|
|
709
|
-
```js
|
|
710
|
-
async function writeRuntimeManifest(home, runtime, opts = {}) {
|
|
711
|
-
const payload = buildRuntimeManifest(runtime, new Date().toISOString(), opts.resolution ?? null);
|
|
712
|
-
if (!opts.dryRun) {
|
|
713
|
-
await writeFileAtomic(join(home, RUNTIMES_MANIFEST_REL), JSON.stringify(payload, null, 2) + "\n", 0o644);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
- [ ] **Step 5: Resolve runtime in ensure-installed**
|
|
719
|
-
|
|
720
|
-
In `runEnsureInstalled`, after resolving paths, add the same resolver call with `command: "ensure-installed"`. Use `runtimeIncludesClaudeAssets(opts.runtime, runtimeResolution)` for Claude checks. When reinstalling, pass the original request:
|
|
721
|
-
|
|
722
|
-
```js
|
|
723
|
-
const installArgs = ["--runtime", opts.runtime];
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
- [ ] **Step 6: Run install tests and verify they pass**
|
|
727
|
-
|
|
728
|
-
Run:
|
|
729
|
-
|
|
730
|
-
```bash
|
|
731
|
-
node --test tests-js/install-runtime.test.mjs
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
Expected: PASS.
|
|
735
|
-
|
|
736
|
-
- [ ] **Step 7: Commit Task 3**
|
|
737
|
-
|
|
738
|
-
Run:
|
|
739
|
-
|
|
740
|
-
```bash
|
|
741
|
-
git add src/install.mjs tests-js/install-runtime.test.mjs
|
|
742
|
-
git commit -m "feat(runtime): default install to auto runtime"
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
---
|
|
746
|
-
|
|
747
|
-
### Task 4: Doctor Auto Runtime
|
|
748
|
-
|
|
749
|
-
**Files:**
|
|
750
|
-
- Modify: `src/doctor.mjs`
|
|
751
|
-
- Modify: `tests-js/doctor-runtime.test.mjs`
|
|
752
|
-
|
|
753
|
-
- [ ] **Step 1: Add failing doctor auto tests**
|
|
754
|
-
|
|
755
|
-
In `tests-js/doctor-runtime.test.mjs`, change `runCli` to accept env:
|
|
756
|
-
|
|
757
|
-
```js
|
|
758
|
-
function runCli(args, env = {}) {
|
|
759
|
-
try {
|
|
760
|
-
const stdout = execFileSync("node", [BIN, ...args], {
|
|
761
|
-
cwd: REPO,
|
|
762
|
-
env: { ...process.env, ...env },
|
|
763
|
-
encoding: "utf8",
|
|
764
|
-
});
|
|
765
|
-
return { code: 0, stdout, stderr: "" };
|
|
766
|
-
} catch (err) {
|
|
767
|
-
return {
|
|
768
|
-
code: err.status ?? 1,
|
|
769
|
-
stdout: (err.stdout || "").toString(),
|
|
770
|
-
stderr: (err.stderr || "").toString(),
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
Replace the default test with:
|
|
777
|
-
|
|
778
|
-
```js
|
|
779
|
-
test("doctor defaults to auto runtime checks", () => {
|
|
780
|
-
assert.deepEqual(parseDoctorArgs([]), {
|
|
781
|
-
jsonMode: false,
|
|
782
|
-
phase: null,
|
|
783
|
-
runtime: "auto",
|
|
784
|
-
});
|
|
785
|
-
});
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
Add:
|
|
789
|
-
|
|
790
|
-
```js
|
|
791
|
-
test("doctor JSON includes runtime resolution", () => {
|
|
792
|
-
const result = runCli(["doctor", "--json"], {
|
|
793
|
-
OKSTRA_RUNTIME_HOST: "external",
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
assert.equal(result.code, 0, result.stderr);
|
|
797
|
-
const payload = JSON.parse(result.stdout);
|
|
798
|
-
assert.equal(payload.runtime, "external");
|
|
799
|
-
assert.equal(payload.runtimeRequest, "auto");
|
|
800
|
-
assert.equal(payload.runtimeResolution.requestedRuntime, "auto");
|
|
801
|
-
assert.equal(payload.runtimeResolution.resolvedRuntime, "external");
|
|
802
|
-
});
|
|
803
|
-
```
|
|
804
|
-
|
|
805
|
-
Update the phase parse test expected runtime from `claude-code` to `auto`.
|
|
806
|
-
|
|
807
|
-
- [ ] **Step 2: Run doctor tests and verify they fail**
|
|
808
|
-
|
|
809
|
-
Run:
|
|
810
|
-
|
|
811
|
-
```bash
|
|
812
|
-
node --test tests-js/doctor-runtime.test.mjs
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
Expected: FAIL because doctor still defaults to `claude-code` and JSON lacks `runtimeResolution`.
|
|
816
|
-
|
|
817
|
-
- [ ] **Step 3: Implement doctor resolution**
|
|
818
|
-
|
|
819
|
-
In `src/doctor.mjs`, import resolver:
|
|
820
|
-
|
|
821
|
-
```js
|
|
822
|
-
import { resolveRuntime } from "./runtime-resolver.mjs";
|
|
823
|
-
```
|
|
824
|
-
|
|
825
|
-
Change parser default:
|
|
826
|
-
|
|
827
|
-
```js
|
|
828
|
-
runtime: "auto",
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
In `run`, after `const paths = await resolvePaths();`, add:
|
|
832
|
-
|
|
833
|
-
```js
|
|
834
|
-
const runtimeResolution = resolveRuntime({
|
|
835
|
-
requestedRuntime: opts.runtime,
|
|
836
|
-
command: "doctor",
|
|
837
|
-
env: process.env,
|
|
838
|
-
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
839
|
-
});
|
|
840
|
-
if (!runtimeResolution.ok) {
|
|
841
|
-
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
842
|
-
return 2;
|
|
843
|
-
}
|
|
844
|
-
const resolvedRuntime = runtimeResolution.resolvedRuntime;
|
|
845
|
-
```
|
|
846
|
-
|
|
847
|
-
Use `resolvedRuntime` when selecting required skills:
|
|
848
|
-
|
|
849
|
-
```js
|
|
850
|
-
requiredSkillNamesForRuntime(resolvedRuntime).map((name) =>
|
|
851
|
-
```
|
|
852
|
-
|
|
853
|
-
Change JSON output fields:
|
|
854
|
-
|
|
855
|
-
```js
|
|
856
|
-
runtimeRequest: opts.runtime,
|
|
857
|
-
runtime: resolvedRuntime,
|
|
858
|
-
runtimeResolution,
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
Keep non-JSON output unchanged except optionally print the resolution before checks:
|
|
862
|
-
|
|
863
|
-
```js
|
|
864
|
-
process.stdout.write(`runtime: ${opts.runtime} -> ${resolvedRuntime} (${runtimeResolution.host})\n`);
|
|
865
|
-
```
|
|
866
|
-
|
|
867
|
-
- [ ] **Step 4: Run doctor tests and verify they pass**
|
|
868
|
-
|
|
869
|
-
Run:
|
|
870
|
-
|
|
871
|
-
```bash
|
|
872
|
-
node --test tests-js/doctor-runtime.test.mjs
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
Expected: PASS.
|
|
876
|
-
|
|
877
|
-
- [ ] **Step 5: Commit Task 4**
|
|
878
|
-
|
|
879
|
-
Run:
|
|
880
|
-
|
|
881
|
-
```bash
|
|
882
|
-
git add src/doctor.mjs tests-js/doctor-runtime.test.mjs
|
|
883
|
-
git commit -m "feat(runtime): resolve doctor runtime automatically"
|
|
884
|
-
```
|
|
885
|
-
|
|
886
|
-
---
|
|
887
|
-
|
|
888
|
-
### Task 5: Render-Bundle and Run Manifest Runtime Resolution
|
|
889
|
-
|
|
890
|
-
**Files:**
|
|
891
|
-
- Modify: `src/render-bundle.mjs`
|
|
892
|
-
- Modify: `scripts/okstra_ctl/run.py`
|
|
893
|
-
- Modify: `scripts/okstra_ctl/render.py`
|
|
894
|
-
- Modify: `tests/test_lead_runtime_marker.py`
|
|
895
|
-
|
|
896
|
-
- [ ] **Step 1: Add failing Python manifest test**
|
|
897
|
-
|
|
898
|
-
In `tests/test_lead_runtime_marker.py`, add after `test_rendered_artifacts_record_external_lead_runtime`:
|
|
899
|
-
|
|
900
|
-
```python
|
|
901
|
-
def test_run_manifest_records_runtime_request_and_resolution(tmp_path: Path) -> None:
|
|
902
|
-
ctx = _ctx(tmp_path, lead_runtime="external")
|
|
903
|
-
ctx["LEAD_RUNTIME_REQUEST"] = "auto"
|
|
904
|
-
ctx["RUNTIME_RESOLUTION_JSON"] = json.dumps(
|
|
905
|
-
{
|
|
906
|
-
"requestedRuntime": "auto",
|
|
907
|
-
"resolvedRuntime": "external",
|
|
908
|
-
"host": "generic-terminal",
|
|
909
|
-
"confidence": "high",
|
|
910
|
-
"reason": "No Claude Code or Codex host signal; tmux is available.",
|
|
911
|
-
"fallbackFrom": None,
|
|
912
|
-
"warnings": [],
|
|
913
|
-
}
|
|
914
|
-
)
|
|
915
|
-
|
|
916
|
-
render_task_manifest(ctx["TASK_MANIFEST_PATH"], ctx)
|
|
917
|
-
render_run_manifest(ctx["RUN_MANIFEST_PATH"], ctx)
|
|
918
|
-
|
|
919
|
-
run_manifest = _read(ctx["RUN_MANIFEST_PATH"])
|
|
920
|
-
assert run_manifest["leadRuntime"] == "external"
|
|
921
|
-
assert run_manifest["leadRuntimeRequest"] == "auto"
|
|
922
|
-
assert run_manifest["runtimeResolution"]["requestedRuntime"] == "auto"
|
|
923
|
-
assert run_manifest["runtimeResolution"]["resolvedRuntime"] == "external"
|
|
924
|
-
assert run_manifest["runtimeResolution"]["host"] == "generic-terminal"
|
|
925
|
-
```
|
|
926
|
-
|
|
927
|
-
- [ ] **Step 2: Run the Python test and verify it fails**
|
|
928
|
-
|
|
929
|
-
Run:
|
|
930
|
-
|
|
931
|
-
```bash
|
|
932
|
-
python3 -m pytest tests/test_lead_runtime_marker.py::test_run_manifest_records_runtime_request_and_resolution -q
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
Expected: FAIL with missing `leadRuntimeRequest`.
|
|
936
|
-
|
|
937
|
-
- [ ] **Step 3: Add Python inputs and context fields**
|
|
938
|
-
|
|
939
|
-
In `scripts/okstra_ctl/run.py`, add fields to `PrepareInputs`:
|
|
940
|
-
|
|
941
|
-
```python
|
|
942
|
-
lead_runtime_request: str = ""
|
|
943
|
-
runtime_resolution_json: str = ""
|
|
944
|
-
```
|
|
945
|
-
|
|
946
|
-
In the argparse section near `--lead-runtime`, add:
|
|
947
|
-
|
|
948
|
-
```python
|
|
949
|
-
p.add_argument(
|
|
950
|
-
"--lead-runtime-request",
|
|
951
|
-
default="",
|
|
952
|
-
dest="lead_runtime_request",
|
|
953
|
-
help="Original lead runtime request before Node-side resolution, e.g. auto.",
|
|
954
|
-
)
|
|
955
|
-
p.add_argument(
|
|
956
|
-
"--runtime-resolution-json",
|
|
957
|
-
default="",
|
|
958
|
-
dest="runtime_resolution_json",
|
|
959
|
-
help="Node-side runtime resolution payload serialized as JSON.",
|
|
960
|
-
)
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
When constructing `PrepareInputs`, pass:
|
|
964
|
-
|
|
965
|
-
```python
|
|
966
|
-
lead_runtime_request=args.lead_runtime_request,
|
|
967
|
-
runtime_resolution_json=args.runtime_resolution_json,
|
|
968
|
-
```
|
|
969
|
-
|
|
970
|
-
In `prepare_task_bundle`, after `lead_runtime = _normalize_lead_runtime(inp.lead_runtime)`, add:
|
|
971
|
-
|
|
972
|
-
```python
|
|
973
|
-
lead_runtime_request = (inp.lead_runtime_request or lead_runtime).strip()
|
|
974
|
-
runtime_resolution_json = (inp.runtime_resolution_json or "{}").strip()
|
|
975
|
-
try:
|
|
976
|
-
json.loads(runtime_resolution_json or "{}")
|
|
977
|
-
except json.JSONDecodeError as exc:
|
|
978
|
-
raise PrepareError(f"invalid --runtime-resolution-json: {exc}") from exc
|
|
979
|
-
```
|
|
980
|
-
|
|
981
|
-
In the `ctx` dict, add:
|
|
982
|
-
|
|
983
|
-
```python
|
|
984
|
-
"LEAD_RUNTIME_REQUEST": lead_runtime_request,
|
|
985
|
-
"RUNTIME_RESOLUTION_JSON": runtime_resolution_json or "{}",
|
|
986
|
-
```
|
|
987
|
-
|
|
988
|
-
- [ ] **Step 4: Write runtime fields in run manifest**
|
|
989
|
-
|
|
990
|
-
In `scripts/okstra_ctl/render.py`, add a helper near the other small helpers:
|
|
991
|
-
|
|
992
|
-
```python
|
|
993
|
-
def _runtime_resolution(ctx: dict) -> dict:
|
|
994
|
-
raw = ctx.get("RUNTIME_RESOLUTION_JSON", "") or "{}"
|
|
995
|
-
try:
|
|
996
|
-
payload = json.loads(raw)
|
|
997
|
-
except Exception:
|
|
998
|
-
return {}
|
|
999
|
-
return payload if isinstance(payload, dict) else {}
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
In `render_run_manifest`, after `"leadRuntime": _lead_runtime(ctx),`, add:
|
|
1003
|
-
|
|
1004
|
-
```python
|
|
1005
|
-
"leadRuntimeRequest": ctx.get("LEAD_RUNTIME_REQUEST", "") or _lead_runtime(ctx),
|
|
1006
|
-
"runtimeResolution": _runtime_resolution(ctx),
|
|
1007
|
-
```
|
|
1008
|
-
|
|
1009
|
-
- [ ] **Step 5: Print run manifest summary line**
|
|
1010
|
-
|
|
1011
|
-
In `scripts/okstra_ctl/run.py`, after line printing `okstra instruction-set`, add:
|
|
1012
|
-
|
|
1013
|
-
```python
|
|
1014
|
-
print(f"okstra run manifest: {ctx['RUN_MANIFEST_PATH']}")
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
This line is required for `okstra run` to parse the prepared manifest without guessing paths.
|
|
1018
|
-
|
|
1019
|
-
- [ ] **Step 6: Update render-bundle to resolve `auto` before Python**
|
|
1020
|
-
|
|
1021
|
-
In `src/render-bundle.mjs`, import resolver:
|
|
1022
|
-
|
|
1023
|
-
```js
|
|
1024
|
-
import { resolveRuntime } from "./runtime-resolver.mjs";
|
|
1025
|
-
```
|
|
1026
|
-
|
|
1027
|
-
Add helpers:
|
|
1028
|
-
|
|
1029
|
-
```js
|
|
1030
|
-
function findFlagValue(args, flag) {
|
|
1031
|
-
const equals = args.find((arg) => arg.startsWith(`${flag}=`));
|
|
1032
|
-
if (equals) return equals.slice(flag.length + 1);
|
|
1033
|
-
const index = args.indexOf(flag);
|
|
1034
|
-
return index >= 0 ? args[index + 1] : "";
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
function withoutLeadRuntime(args) {
|
|
1038
|
-
const out = [];
|
|
1039
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
1040
|
-
const arg = args[i];
|
|
1041
|
-
if (arg === "--lead-runtime") {
|
|
1042
|
-
i += 1;
|
|
1043
|
-
continue;
|
|
1044
|
-
}
|
|
1045
|
-
if (arg.startsWith("--lead-runtime=")) continue;
|
|
1046
|
-
out.push(arg);
|
|
1047
|
-
}
|
|
1048
|
-
return out;
|
|
1049
|
-
}
|
|
1050
|
-
```
|
|
1051
|
-
|
|
1052
|
-
Before building `finalArgs`, add:
|
|
1053
|
-
|
|
1054
|
-
```js
|
|
1055
|
-
const requestedRuntime = findFlagValue(args, "--lead-runtime") || "auto";
|
|
1056
|
-
const runtimeResolution = resolveRuntime({
|
|
1057
|
-
requestedRuntime,
|
|
1058
|
-
command: "render",
|
|
1059
|
-
env: process.env,
|
|
1060
|
-
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
1061
|
-
});
|
|
1062
|
-
if (!runtimeResolution.ok) {
|
|
1063
|
-
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
1064
|
-
return 2;
|
|
1065
|
-
}
|
|
1066
|
-
```
|
|
1067
|
-
|
|
1068
|
-
Build final args with explicit resolved runtime and metadata:
|
|
1069
|
-
|
|
1070
|
-
```js
|
|
1071
|
-
const finalArgs = [
|
|
1072
|
-
"--workspace-root", paths.workspace,
|
|
1073
|
-
"--render-only",
|
|
1074
|
-
"--lead-runtime", runtimeResolution.resolvedRuntime,
|
|
1075
|
-
"--lead-runtime-request", requestedRuntime,
|
|
1076
|
-
"--runtime-resolution-json", JSON.stringify(runtimeResolution),
|
|
1077
|
-
...withoutLeadRuntime(args),
|
|
1078
|
-
];
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
- [ ] **Step 7: Run targeted tests and verify they pass**
|
|
1082
|
-
|
|
1083
|
-
Run:
|
|
1084
|
-
|
|
1085
|
-
```bash
|
|
1086
|
-
python3 -m pytest tests/test_lead_runtime_marker.py -q
|
|
1087
|
-
node --test tests-js/team.test.mjs tests-js/codex-run.test.mjs
|
|
1088
|
-
```
|
|
1089
|
-
|
|
1090
|
-
Expected: Python lead runtime tests PASS, Node low-level command tests PASS.
|
|
1091
|
-
|
|
1092
|
-
- [ ] **Step 8: Commit Task 5**
|
|
1093
|
-
|
|
1094
|
-
Run:
|
|
1095
|
-
|
|
1096
|
-
```bash
|
|
1097
|
-
git add src/render-bundle.mjs scripts/okstra_ctl/run.py scripts/okstra_ctl/render.py tests/test_lead_runtime_marker.py
|
|
1098
|
-
git commit -m "feat(runtime): record resolved runtime in run manifests"
|
|
1099
|
-
```
|
|
1100
|
-
|
|
1101
|
-
---
|
|
1102
|
-
|
|
1103
|
-
### Task 6: High-Level `okstra run` Driver
|
|
1104
|
-
|
|
1105
|
-
**Files:**
|
|
1106
|
-
- Create: `src/run.mjs`
|
|
1107
|
-
- Create: `tests-js/run.test.mjs`
|
|
1108
|
-
- Modify: `src/cli-registry.mjs`
|
|
1109
|
-
- Modify: `tests-js/cli-registry.test.mjs`
|
|
1110
|
-
|
|
1111
|
-
- [ ] **Step 1: Add failing run driver tests**
|
|
1112
|
-
|
|
1113
|
-
Create `tests-js/run.test.mjs`:
|
|
1114
|
-
|
|
1115
|
-
```js
|
|
1116
|
-
import test from "node:test";
|
|
1117
|
-
import assert from "node:assert/strict";
|
|
1118
|
-
|
|
1119
|
-
import {
|
|
1120
|
-
buildRunPlan,
|
|
1121
|
-
extractLabel,
|
|
1122
|
-
requestedRuntimeFromArgs,
|
|
1123
|
-
withoutLeadRuntimeFlags,
|
|
1124
|
-
} from "../src/run.mjs";
|
|
1125
|
-
|
|
1126
|
-
const PATHS = Object.freeze({ workspace: "/runtime", bin: "/home/.okstra/bin" });
|
|
1127
|
-
|
|
1128
|
-
const EXTERNAL_RESOLUTION = Object.freeze({
|
|
1129
|
-
ok: true,
|
|
1130
|
-
requestedRuntime: "auto",
|
|
1131
|
-
resolvedRuntime: "external",
|
|
1132
|
-
host: "generic-terminal",
|
|
1133
|
-
confidence: "high",
|
|
1134
|
-
reason: "No Claude Code or Codex host signal; tmux is available.",
|
|
1135
|
-
fallbackFrom: null,
|
|
1136
|
-
warnings: [],
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
const CODEX_RESOLUTION = Object.freeze({
|
|
1140
|
-
ok: true,
|
|
1141
|
-
requestedRuntime: "auto",
|
|
1142
|
-
resolvedRuntime: "codex",
|
|
1143
|
-
host: "codex",
|
|
1144
|
-
confidence: "high",
|
|
1145
|
-
reason: "OKSTRA_RUNTIME_HOST=codex selected runtime 'codex'.",
|
|
1146
|
-
fallbackFrom: null,
|
|
1147
|
-
warnings: [],
|
|
1148
|
-
});
|
|
1149
|
-
|
|
1150
|
-
const CLAUDE_RESOLUTION = Object.freeze({
|
|
1151
|
-
ok: true,
|
|
1152
|
-
requestedRuntime: "auto",
|
|
1153
|
-
resolvedRuntime: "claude-code",
|
|
1154
|
-
host: "claude-code",
|
|
1155
|
-
confidence: "high",
|
|
1156
|
-
reason: "OKSTRA_RUNTIME_HOST=claude-code selected runtime 'claude-code'.",
|
|
1157
|
-
fallbackFrom: null,
|
|
1158
|
-
warnings: [],
|
|
1159
|
-
});
|
|
1160
|
-
|
|
1161
|
-
test("requestedRuntimeFromArgs defaults to auto", () => {
|
|
1162
|
-
assert.equal(requestedRuntimeFromArgs(["--task-type", "error-analysis"]), "auto");
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
test("requestedRuntimeFromArgs reads explicit lead runtime", () => {
|
|
1166
|
-
assert.equal(requestedRuntimeFromArgs(["--lead-runtime", "external"]), "external");
|
|
1167
|
-
assert.equal(requestedRuntimeFromArgs(["--lead-runtime=codex"]), "codex");
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
test("withoutLeadRuntimeFlags removes owned runtime flags", () => {
|
|
1171
|
-
assert.deepEqual(
|
|
1172
|
-
withoutLeadRuntimeFlags(["--task-type", "error-analysis", "--lead-runtime", "external", "--workers", ""]),
|
|
1173
|
-
["--task-type", "error-analysis", "--workers", ""],
|
|
1174
|
-
);
|
|
1175
|
-
assert.deepEqual(
|
|
1176
|
-
withoutLeadRuntimeFlags(["--lead-runtime=codex", "--task-type", "error-analysis"]),
|
|
1177
|
-
["--task-type", "error-analysis"],
|
|
1178
|
-
);
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
test("extractLabel reads run manifest label", () => {
|
|
1182
|
-
const stdout = [
|
|
1183
|
-
"okstra task key: p:g:t",
|
|
1184
|
-
"okstra run manifest: /tmp/project/.okstra/tasks/g/t/runs/error-analysis/manifests/run.json",
|
|
1185
|
-
"",
|
|
1186
|
-
].join("\n");
|
|
1187
|
-
|
|
1188
|
-
assert.equal(
|
|
1189
|
-
extractLabel(stdout, "okstra run manifest"),
|
|
1190
|
-
"/tmp/project/.okstra/tasks/g/t/runs/error-analysis/manifests/run.json",
|
|
1191
|
-
);
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
test("external run plan prepares render bundle then team dispatch and await", () => {
|
|
1195
|
-
const plan = buildRunPlan({
|
|
1196
|
-
args: ["--project-root", "/project", "--task-type", "error-analysis"],
|
|
1197
|
-
paths: PATHS,
|
|
1198
|
-
resolution: EXTERNAL_RESOLUTION,
|
|
1199
|
-
runManifestPath: "/project/.okstra/run.json",
|
|
1200
|
-
});
|
|
1201
|
-
|
|
1202
|
-
assert.deepEqual(plan.commands, [
|
|
1203
|
-
{
|
|
1204
|
-
name: "render-bundle",
|
|
1205
|
-
args: [
|
|
1206
|
-
"--lead-runtime", "external",
|
|
1207
|
-
"--lead-runtime-request", "auto",
|
|
1208
|
-
"--runtime-resolution-json", JSON.stringify(EXTERNAL_RESOLUTION),
|
|
1209
|
-
"--project-root", "/project",
|
|
1210
|
-
"--task-type", "error-analysis",
|
|
1211
|
-
],
|
|
1212
|
-
capture: true,
|
|
1213
|
-
},
|
|
1214
|
-
{
|
|
1215
|
-
name: "team dispatch",
|
|
1216
|
-
args: ["dispatch", "--project-root", "/project", "--run-manifest", "/project/.okstra/run.json"],
|
|
1217
|
-
capture: false,
|
|
1218
|
-
},
|
|
1219
|
-
{
|
|
1220
|
-
name: "team await",
|
|
1221
|
-
args: ["await", "--project-root", "/project", "--run-manifest", "/project/.okstra/run.json"],
|
|
1222
|
-
capture: false,
|
|
1223
|
-
},
|
|
1224
|
-
]);
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
test("codex run plan prepares codex-run then codex-dispatch", () => {
|
|
1228
|
-
const plan = buildRunPlan({
|
|
1229
|
-
args: ["--project-root", "/project", "--task-type", "error-analysis"],
|
|
1230
|
-
paths: PATHS,
|
|
1231
|
-
resolution: CODEX_RESOLUTION,
|
|
1232
|
-
runManifestPath: "/project/.okstra/run.json",
|
|
1233
|
-
});
|
|
1234
|
-
|
|
1235
|
-
assert.deepEqual(plan.commands.map((item) => item.name), ["codex-run", "codex-dispatch"]);
|
|
1236
|
-
assert.deepEqual(plan.commands[1].args, [
|
|
1237
|
-
"--project-root", "/project",
|
|
1238
|
-
"--run-manifest", "/project/.okstra/run.json",
|
|
1239
|
-
]);
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
test("codex run plan forwards codex dispatch opt-in flags", () => {
|
|
1243
|
-
const plan = buildRunPlan({
|
|
1244
|
-
args: [
|
|
1245
|
-
"--project-root", "/project",
|
|
1246
|
-
"--task-type", "error-analysis",
|
|
1247
|
-
"--enable-codex-report-writer",
|
|
1248
|
-
"--report-writer-codex-model", "gpt-5.5",
|
|
1249
|
-
],
|
|
1250
|
-
paths: PATHS,
|
|
1251
|
-
resolution: CODEX_RESOLUTION,
|
|
1252
|
-
runManifestPath: "/project/.okstra/run.json",
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
assert.deepEqual(plan.commands[1].args, [
|
|
1256
|
-
"--project-root", "/project",
|
|
1257
|
-
"--run-manifest", "/project/.okstra/run.json",
|
|
1258
|
-
"--enable-codex-report-writer",
|
|
1259
|
-
"--report-writer-codex-model", "gpt-5.5",
|
|
1260
|
-
]);
|
|
1261
|
-
});
|
|
1262
|
-
|
|
1263
|
-
test("bare Node run cannot execute Claude Code native teams", () => {
|
|
1264
|
-
const plan = buildRunPlan({
|
|
1265
|
-
args: ["--project-root", "/project", "--task-type", "error-analysis"],
|
|
1266
|
-
paths: PATHS,
|
|
1267
|
-
resolution: CLAUDE_RESOLUTION,
|
|
1268
|
-
runManifestPath: "",
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1271
|
-
assert.equal(plan.ok, false);
|
|
1272
|
-
assert.match(plan.reason, /\/okstra-run/);
|
|
1273
|
-
assert.match(plan.reason, /TeamCreate/);
|
|
1274
|
-
});
|
|
1275
|
-
```
|
|
1276
|
-
|
|
1277
|
-
- [ ] **Step 2: Run run driver tests and verify they fail**
|
|
1278
|
-
|
|
1279
|
-
Run:
|
|
1280
|
-
|
|
1281
|
-
```bash
|
|
1282
|
-
node --test tests-js/run.test.mjs
|
|
1283
|
-
```
|
|
1284
|
-
|
|
1285
|
-
Expected: FAIL because `src/run.mjs` does not exist.
|
|
1286
|
-
|
|
1287
|
-
- [ ] **Step 3: Implement pure planning helpers**
|
|
1288
|
-
|
|
1289
|
-
Create `src/run.mjs` with the helper exports first:
|
|
1290
|
-
|
|
1291
|
-
```js
|
|
1292
|
-
import { runEnsureInstalled } from "./install.mjs";
|
|
1293
|
-
import { runPythonModule } from "./_python-helper.mjs";
|
|
1294
|
-
import { resolvePaths } from "./paths.mjs";
|
|
1295
|
-
import { resolveRuntime } from "./runtime-resolver.mjs";
|
|
1296
|
-
const USAGE = `okstra run — host-aware okstra execution front door
|
|
1297
|
-
|
|
1298
|
-
Usage:
|
|
1299
|
-
okstra run --project-root <dir> --project-id <id> --task-group <tg> \
|
|
1300
|
-
--task-id <tid> --task-type <type> [--lead-runtime auto|claude-code|codex|external]
|
|
1301
|
-
|
|
1302
|
-
Claude Code native execution remains the /okstra-run skill front door. Bare
|
|
1303
|
-
'okstra run' orchestrates Codex and external/tmux CLI execution.
|
|
1304
|
-
`;
|
|
1305
|
-
|
|
1306
|
-
function flagValue(args, flag) {
|
|
1307
|
-
const equals = args.find((arg) => arg.startsWith(`${flag}=`));
|
|
1308
|
-
if (equals) return equals.slice(flag.length + 1);
|
|
1309
|
-
const index = args.indexOf(flag);
|
|
1310
|
-
return index >= 0 ? args[index + 1] : "";
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
export function requestedRuntimeFromArgs(args) {
|
|
1314
|
-
return flagValue(args, "--lead-runtime") || "auto";
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
export function withoutLeadRuntimeFlags(args) {
|
|
1318
|
-
const out = [];
|
|
1319
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
1320
|
-
const arg = args[i];
|
|
1321
|
-
if (arg === "--lead-runtime") {
|
|
1322
|
-
i += 1;
|
|
1323
|
-
continue;
|
|
1324
|
-
}
|
|
1325
|
-
if (arg.startsWith("--lead-runtime=")) continue;
|
|
1326
|
-
out.push(arg);
|
|
1327
|
-
}
|
|
1328
|
-
return out;
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
export function extractLabel(stdout, label) {
|
|
1332
|
-
const prefix = `${label}:`;
|
|
1333
|
-
const line = String(stdout || "").split("\n").find((item) => item.startsWith(prefix));
|
|
1334
|
-
return line ? line.slice(prefix.length).trim() : "";
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
function projectRootFromArgs(args) {
|
|
1338
|
-
return flagValue(args, "--project-root");
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
function renderArgs(args, resolution) {
|
|
1342
|
-
return [
|
|
1343
|
-
"--lead-runtime", resolution.resolvedRuntime,
|
|
1344
|
-
"--lead-runtime-request", resolution.requestedRuntime,
|
|
1345
|
-
"--runtime-resolution-json", JSON.stringify(resolution),
|
|
1346
|
-
...withoutLeadRuntimeFlags(args),
|
|
1347
|
-
];
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
function codexDispatchArgs(args, projectRoot, runManifestPath) {
|
|
1351
|
-
const out = ["--project-root", projectRoot, "--run-manifest", runManifestPath];
|
|
1352
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
1353
|
-
const arg = args[i];
|
|
1354
|
-
if (arg === "--enable-codex-report-writer") {
|
|
1355
|
-
out.push(arg);
|
|
1356
|
-
continue;
|
|
1357
|
-
}
|
|
1358
|
-
if (arg === "--report-writer-codex-model") {
|
|
1359
|
-
out.push(arg, args[i + 1] ?? "");
|
|
1360
|
-
i += 1;
|
|
1361
|
-
continue;
|
|
1362
|
-
}
|
|
1363
|
-
if (arg.startsWith("--report-writer-codex-model=")) out.push(arg);
|
|
1364
|
-
}
|
|
1365
|
-
return out;
|
|
1366
|
-
}
|
|
1367
|
-
export function buildRunPlan({ args, paths, resolution, runManifestPath = "" }) {
|
|
1368
|
-
void paths;
|
|
1369
|
-
const projectRoot = projectRootFromArgs(args);
|
|
1370
|
-
if (resolution.resolvedRuntime === "claude-code") {
|
|
1371
|
-
return {
|
|
1372
|
-
ok: false,
|
|
1373
|
-
reason: "Bare okstra run cannot invoke Claude Code TeamCreate / Agent(...). Use /okstra-run in Claude Code or pass --lead-runtime external|codex.",
|
|
1374
|
-
commands: [],
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
if (resolution.resolvedRuntime === "external") {
|
|
1379
|
-
return {
|
|
1380
|
-
ok: true,
|
|
1381
|
-
reason: resolution.reason,
|
|
1382
|
-
commands: [
|
|
1383
|
-
{ name: "render-bundle", args: renderArgs(args, resolution), capture: true },
|
|
1384
|
-
{ name: "team dispatch", args: ["dispatch", "--project-root", projectRoot, "--run-manifest", runManifestPath], capture: false },
|
|
1385
|
-
{ name: "team await", args: ["await", "--project-root", projectRoot, "--run-manifest", runManifestPath], capture: false },
|
|
1386
|
-
],
|
|
1387
|
-
};
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
if (resolution.resolvedRuntime === "codex") {
|
|
1391
|
-
return {
|
|
1392
|
-
ok: true,
|
|
1393
|
-
reason: resolution.reason,
|
|
1394
|
-
commands: [
|
|
1395
|
-
{ name: "codex-run", args: renderArgs(args, resolution), capture: true },
|
|
1396
|
-
{ name: "codex-dispatch", args: codexDispatchArgs(args, projectRoot, runManifestPath), capture: false },
|
|
1397
|
-
],
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
return { ok: false, reason: `unsupported resolved runtime: ${resolution.resolvedRuntime}`, commands: [] };
|
|
1402
|
-
}
|
|
1403
|
-
```
|
|
1404
|
-
|
|
1405
|
-
- [ ] **Step 4: Run run driver tests and verify helper tests pass**
|
|
1406
|
-
|
|
1407
|
-
Run:
|
|
1408
|
-
|
|
1409
|
-
```bash
|
|
1410
|
-
node --test tests-js/run.test.mjs
|
|
1411
|
-
```
|
|
1412
|
-
|
|
1413
|
-
Expected: PASS for helper tests.
|
|
1414
|
-
|
|
1415
|
-
- [ ] **Step 5: Implement CLI execution path**
|
|
1416
|
-
|
|
1417
|
-
Append to `src/run.mjs`:
|
|
1418
|
-
|
|
1419
|
-
```js
|
|
1420
|
-
async function ensureRuntime(requestedRuntime) {
|
|
1421
|
-
return await runEnsureInstalled(["--runtime", requestedRuntime, "-q"]);
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
async function runRenderCommand(command, paths) {
|
|
1425
|
-
const result = await runPythonModule({
|
|
1426
|
-
module: "okstra_ctl.run",
|
|
1427
|
-
args: ["--workspace-root", paths.workspace, "--render-only", ...command.args],
|
|
1428
|
-
stdio: "capture",
|
|
1429
|
-
});
|
|
1430
|
-
process.stdout.write(result.stdout);
|
|
1431
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
1432
|
-
return result;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
async function runTeam(args) {
|
|
1436
|
-
const { run: runTeamCommand } = await import("./team.mjs");
|
|
1437
|
-
return await runTeamCommand(args);
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
async function runCodexDispatch(args) {
|
|
1441
|
-
const { run: runCodexDispatchCommand } = await import("./codex-dispatch.mjs");
|
|
1442
|
-
return await runCodexDispatchCommand(args);
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
export async function run(args) {
|
|
1446
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
1447
|
-
process.stdout.write(USAGE);
|
|
1448
|
-
return 0;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
const paths = await resolvePaths();
|
|
1452
|
-
const requestedRuntime = requestedRuntimeFromArgs(args);
|
|
1453
|
-
const resolution = resolveRuntime({
|
|
1454
|
-
requestedRuntime,
|
|
1455
|
-
command: "run",
|
|
1456
|
-
env: process.env,
|
|
1457
|
-
capabilities: { tmuxAvailable: Boolean(process.env.TMUX) },
|
|
1458
|
-
});
|
|
1459
|
-
if (!resolution.ok) {
|
|
1460
|
-
process.stderr.write(`error: ${resolution.reason}\n`);
|
|
1461
|
-
return 2;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
const ensureCode = await ensureRuntime(requestedRuntime);
|
|
1465
|
-
if (ensureCode !== 0) return ensureCode;
|
|
1466
|
-
|
|
1467
|
-
const firstPlan = buildRunPlan({ args, paths, resolution });
|
|
1468
|
-
if (!firstPlan.ok) {
|
|
1469
|
-
process.stderr.write(`error: ${firstPlan.reason}\n`);
|
|
1470
|
-
return 2;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
const render = firstPlan.commands[0];
|
|
1474
|
-
const prepared = await runRenderCommand(render, paths);
|
|
1475
|
-
if (prepared.code !== 0) return prepared.code ?? 1;
|
|
1476
|
-
|
|
1477
|
-
const runManifestPath = extractLabel(prepared.stdout, "okstra run manifest");
|
|
1478
|
-
if (!runManifestPath) {
|
|
1479
|
-
process.stderr.write("error: render output did not include 'okstra run manifest:'\n");
|
|
1480
|
-
return 1;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
const plan = buildRunPlan({ args, paths, resolution, runManifestPath });
|
|
1484
|
-
for (const command of plan.commands.slice(1)) {
|
|
1485
|
-
const code = command.name.startsWith("team ")
|
|
1486
|
-
? await runTeam(command.args)
|
|
1487
|
-
: await runCodexDispatch(command.args);
|
|
1488
|
-
if (code !== 0) return code;
|
|
1489
|
-
}
|
|
1490
|
-
return 0;
|
|
1491
|
-
}
|
|
1492
|
-
```
|
|
1493
|
-
|
|
1494
|
-
- [ ] **Step 6: Register CLI command and update registry test**
|
|
1495
|
-
|
|
1496
|
-
In `src/cli-registry.mjs`, add after `render-bundle`:
|
|
1497
|
-
|
|
1498
|
-
```js
|
|
1499
|
-
{
|
|
1500
|
-
name: "run",
|
|
1501
|
-
module: "./run.mjs",
|
|
1502
|
-
export: "run",
|
|
1503
|
-
category: "introspection",
|
|
1504
|
-
summary: ["Host-aware auto execution front door"],
|
|
1505
|
-
},
|
|
1506
|
-
```
|
|
1507
|
-
|
|
1508
|
-
In `tests-js/cli-registry.test.mjs`:
|
|
1509
|
-
|
|
1510
|
-
```js
|
|
1511
|
-
assert.equal(registryNames.length, 29);
|
|
1512
|
-
assert.equal(COMMANDS.size, 29);
|
|
1513
|
-
assert.equal(introspection.length, 18);
|
|
1514
|
-
```
|
|
1515
|
-
|
|
1516
|
-
Add assertion:
|
|
1517
|
-
|
|
1518
|
-
```js
|
|
1519
|
-
assert.match(USAGE, /run\s+Host-aware auto execution front door/);
|
|
1520
|
-
```
|
|
1521
|
-
|
|
1522
|
-
- [ ] **Step 7: Run run and registry tests**
|
|
1523
|
-
|
|
1524
|
-
Run:
|
|
1525
|
-
|
|
1526
|
-
```bash
|
|
1527
|
-
node --test tests-js/run.test.mjs tests-js/cli-registry.test.mjs
|
|
1528
|
-
```
|
|
1529
|
-
|
|
1530
|
-
Expected: PASS.
|
|
1531
|
-
|
|
1532
|
-
- [ ] **Step 8: Commit Task 6**
|
|
1533
|
-
|
|
1534
|
-
Run:
|
|
1535
|
-
|
|
1536
|
-
```bash
|
|
1537
|
-
git add src/run.mjs tests-js/run.test.mjs src/cli-registry.mjs tests-js/cli-registry.test.mjs
|
|
1538
|
-
git commit -m "feat(runtime): add host-aware run driver"
|
|
1539
|
-
```
|
|
1540
|
-
|
|
1541
|
-
---
|
|
1542
|
-
|
|
1543
|
-
### Task 7: Claude Skill Host Marker and Docs Contract
|
|
1544
|
-
|
|
1545
|
-
**Files:**
|
|
1546
|
-
- Modify: `skills/okstra-run/SKILL.md`
|
|
1547
|
-
- Modify: `docs/project-structure-overview.md`
|
|
1548
|
-
- Modify: `docs/kr/cli.md`
|
|
1549
|
-
- Modify: `tests/test_docs_runtime_contract.py`
|
|
1550
|
-
|
|
1551
|
-
- [ ] **Step 1: Add failing docs contract checks**
|
|
1552
|
-
|
|
1553
|
-
In `tests/test_docs_runtime_contract.py`, add:
|
|
1554
|
-
|
|
1555
|
-
```python
|
|
1556
|
-
def test_docs_state_runtime_auto_detection_contract():
|
|
1557
|
-
cli_text = _read_doc("docs/kr/cli.md")
|
|
1558
|
-
structure_text = _read_doc("docs/project-structure-overview.md")
|
|
1559
|
-
skill_text = _read_doc("skills/okstra-run/SKILL.md")
|
|
1560
|
-
|
|
1561
|
-
cli_fragments = [
|
|
1562
|
-
"기본 runtime은 `auto`",
|
|
1563
|
-
"host 기반으로 `claude-code`, `codex`, `external` 중 하나로 resolve",
|
|
1564
|
-
"bare `okstra run` cannot invoke Claude Code `TeamCreate` / `Agent(...)`",
|
|
1565
|
-
"Claude Code에서는 `/okstra-run`이 front door",
|
|
1566
|
-
"`installed-runtimes.json` schemaVersion 2",
|
|
1567
|
-
"`codexAdapter` / `externalAdapter`는 v1 compatibility read path",
|
|
1568
|
-
]
|
|
1569
|
-
for fragment in cli_fragments:
|
|
1570
|
-
assert fragment in cli_text
|
|
1571
|
-
|
|
1572
|
-
structure_fragments = [
|
|
1573
|
-
"`src/runtime-resolver.mjs` is the single reference point",
|
|
1574
|
-
"`okstra install` defaults to `--runtime auto`",
|
|
1575
|
-
"Dynamic capabilities such as `tmux` and `codex` CLI availability are checked by `doctor` and `run`",
|
|
1576
|
-
]
|
|
1577
|
-
for fragment in structure_fragments:
|
|
1578
|
-
assert fragment in structure_text
|
|
1579
|
-
|
|
1580
|
-
assert "OKSTRA_RUNTIME_HOST=claude-code okstra ensure-installed" in skill_text
|
|
1581
|
-
assert "OKSTRA_RUNTIME_HOST=claude-code okstra render-bundle" in skill_text
|
|
1582
|
-
```
|
|
1583
|
-
|
|
1584
|
-
- [ ] **Step 2: Run docs contract test and verify it fails**
|
|
1585
|
-
|
|
1586
|
-
Run:
|
|
1587
|
-
|
|
1588
|
-
```bash
|
|
1589
|
-
python3 -m pytest tests/test_docs_runtime_contract.py::test_docs_state_runtime_auto_detection_contract -q
|
|
1590
|
-
```
|
|
1591
|
-
|
|
1592
|
-
Expected: FAIL with missing fragments.
|
|
1593
|
-
|
|
1594
|
-
- [ ] **Step 3: Update `skills/okstra-run/SKILL.md`**
|
|
1595
|
-
|
|
1596
|
-
Change Step 1 command from:
|
|
1597
|
-
|
|
1598
|
-
```markdown
|
|
1599
|
-
1. `okstra ensure-installed`
|
|
1600
|
-
```
|
|
1601
|
-
|
|
1602
|
-
to:
|
|
1603
|
-
|
|
1604
|
-
```markdown
|
|
1605
|
-
1. `OKSTRA_RUNTIME_HOST=claude-code okstra ensure-installed`
|
|
1606
|
-
```
|
|
1607
|
-
|
|
1608
|
-
Change the render command block from:
|
|
1609
|
-
|
|
1610
|
-
```bash
|
|
1611
|
-
okstra render-bundle \
|
|
1612
|
-
```
|
|
1613
|
-
|
|
1614
|
-
to:
|
|
1615
|
-
|
|
1616
|
-
```bash
|
|
1617
|
-
OKSTRA_RUNTIME_HOST=claude-code okstra render-bundle \
|
|
1618
|
-
```
|
|
1619
|
-
|
|
1620
|
-
Add one sentence near the render command:
|
|
1621
|
-
|
|
1622
|
-
```markdown
|
|
1623
|
-
The Claude Code skill is the Claude execution front door, so it marks the host explicitly with `OKSTRA_RUNTIME_HOST=claude-code`; bare `okstra run` cannot invoke native `TeamCreate` / `Agent(...)` from a terminal.
|
|
1624
|
-
```
|
|
1625
|
-
|
|
1626
|
-
- [ ] **Step 4: Update CLI docs**
|
|
1627
|
-
|
|
1628
|
-
In `docs/kr/cli.md`, add a concise runtime auto section under the runtime docs:
|
|
1629
|
-
|
|
1630
|
-
```markdown
|
|
1631
|
-
기본 runtime은 `auto` 입니다. `auto` 는 host 기반으로 `claude-code`, `codex`, `external` 중 하나로 resolve 됩니다.
|
|
1632
|
-
|
|
1633
|
-
- Claude Code에서는 `/okstra-run`이 front door 입니다. bare `okstra run` cannot invoke Claude Code `TeamCreate` / `Agent(...)` from a terminal.
|
|
1634
|
-
- Codex host에서는 `okstra run`이 `codex-run` → `codex-dispatch`를 orchestration 합니다.
|
|
1635
|
-
- generic terminal + tmux에서는 `okstra run`이 `render-bundle --lead-runtime external` → `okstra team dispatch` → `okstra team await`를 orchestration 합니다.
|
|
1636
|
-
|
|
1637
|
-
`installed-runtimes.json` schemaVersion 2는 `installRequest`, `runtimeResolution`, `installedRuntimes`, `installedAssets`를 분리합니다. `codexAdapter` / `externalAdapter`는 v1 compatibility read path 에서만 해석됩니다.
|
|
1638
|
-
```
|
|
1639
|
-
|
|
1640
|
-
- [ ] **Step 5: Update project structure docs**
|
|
1641
|
-
|
|
1642
|
-
In `docs/project-structure-overview.md`, add under the install output section:
|
|
1643
|
-
|
|
1644
|
-
```markdown
|
|
1645
|
-
`src/runtime-resolver.mjs` is the single reference point for runtime auto-detection. `okstra install` defaults to `--runtime auto`, records the request and resolution in `installed-runtimes.json` schemaVersion 2, and still copies the shared runtime payload from the installed package `runtime/` tree.
|
|
1646
|
-
|
|
1647
|
-
Dynamic capabilities such as `tmux` and `codex` CLI availability are checked by `doctor` and `run`, not frozen into the install manifest.
|
|
1648
|
-
```
|
|
1649
|
-
|
|
1650
|
-
- [ ] **Step 6: Run docs tests and verify they pass**
|
|
1651
|
-
|
|
1652
|
-
Run:
|
|
1653
|
-
|
|
1654
|
-
```bash
|
|
1655
|
-
python3 -m pytest tests/test_docs_runtime_contract.py -q
|
|
1656
|
-
```
|
|
1657
|
-
|
|
1658
|
-
Expected: PASS.
|
|
1659
|
-
|
|
1660
|
-
- [ ] **Step 7: Commit Task 7**
|
|
1661
|
-
|
|
1662
|
-
Run:
|
|
1663
|
-
|
|
1664
|
-
```bash
|
|
1665
|
-
git add skills/okstra-run/SKILL.md docs/project-structure-overview.md docs/kr/cli.md tests/test_docs_runtime_contract.py
|
|
1666
|
-
git commit -m "docs(runtime): document host runtime auto execution"
|
|
1667
|
-
```
|
|
1668
|
-
|
|
1669
|
-
---
|
|
1670
|
-
|
|
1671
|
-
### Task 8: Full Verification and Cleanup
|
|
1672
|
-
|
|
1673
|
-
**Files:**
|
|
1674
|
-
- Review all changed files from Tasks 1-7.
|
|
1675
|
-
- Modify only files with failing tests or stale docs.
|
|
1676
|
-
|
|
1677
|
-
- [ ] **Step 1: Run targeted Node tests**
|
|
1678
|
-
|
|
1679
|
-
Run:
|
|
1680
|
-
|
|
1681
|
-
```bash
|
|
1682
|
-
node --test \
|
|
1683
|
-
tests-js/runtime-resolver.test.mjs \
|
|
1684
|
-
tests-js/install-runtime.test.mjs \
|
|
1685
|
-
tests-js/doctor-runtime.test.mjs \
|
|
1686
|
-
tests-js/run.test.mjs \
|
|
1687
|
-
tests-js/cli-registry.test.mjs \
|
|
1688
|
-
tests-js/codex-run.test.mjs \
|
|
1689
|
-
tests-js/team.test.mjs
|
|
1690
|
-
```
|
|
1691
|
-
|
|
1692
|
-
Expected: PASS.
|
|
1693
|
-
|
|
1694
|
-
- [ ] **Step 2: Run targeted Python tests**
|
|
1695
|
-
|
|
1696
|
-
Run:
|
|
1697
|
-
|
|
1698
|
-
```bash
|
|
1699
|
-
python3 -m pytest \
|
|
1700
|
-
tests/test_lead_runtime_marker.py \
|
|
1701
|
-
tests/test_docs_runtime_contract.py
|
|
1702
|
-
```
|
|
1703
|
-
|
|
1704
|
-
Expected: PASS.
|
|
1705
|
-
|
|
1706
|
-
- [ ] **Step 3: Run install round-trip tests**
|
|
1707
|
-
|
|
1708
|
-
Run:
|
|
1709
|
-
|
|
1710
|
-
```bash
|
|
1711
|
-
node --test tests-js/install-runtime.test.mjs tests-js/runtime-build-sync.test.mjs
|
|
1712
|
-
```
|
|
1713
|
-
|
|
1714
|
-
Expected: PASS.
|
|
1715
|
-
|
|
1716
|
-
- [ ] **Step 4: Run project check**
|
|
1717
|
-
|
|
1718
|
-
Run:
|
|
1719
|
-
|
|
1720
|
-
```bash
|
|
1721
|
-
npm run check
|
|
1722
|
-
```
|
|
1723
|
-
|
|
1724
|
-
Expected: exit code 0.
|
|
1725
|
-
|
|
1726
|
-
- [ ] **Step 5: Run graphify update**
|
|
1727
|
-
|
|
1728
|
-
Run:
|
|
1729
|
-
|
|
1730
|
-
```bash
|
|
1731
|
-
graphify update .
|
|
1732
|
-
```
|
|
1733
|
-
|
|
1734
|
-
Expected: exit code 0. If it fails with the existing HTML graph node-count limit, record the exact failure in the final report and do not edit graph outputs by hand.
|
|
1735
|
-
|
|
1736
|
-
- [ ] **Step 6: Grep new identifiers for consistency**
|
|
1737
|
-
|
|
1738
|
-
Use the search tool, not shell grep, for these identifiers:
|
|
1739
|
-
|
|
1740
|
-
```text
|
|
1741
|
-
OKSTRA_RUNTIME_HOST
|
|
1742
|
-
runtimeResolution
|
|
1743
|
-
leadRuntimeRequest
|
|
1744
|
-
installedAssets
|
|
1745
|
-
installedRuntimes
|
|
1746
|
-
schemaVersion": 2
|
|
1747
|
-
```
|
|
1748
|
-
|
|
1749
|
-
Expected:
|
|
1750
|
-
|
|
1751
|
-
- `OKSTRA_RUNTIME_HOST` appears in resolver tests, docs, and `skills/okstra-run/SKILL.md`.
|
|
1752
|
-
- `runtimeResolution` appears in resolver, manifest, doctor JSON, run manifest, tests, and docs.
|
|
1753
|
-
- `leadRuntimeRequest` appears in Python run/render code and run manifest tests.
|
|
1754
|
-
- `codexAdapter` / `externalAdapter` appear only in v1 compatibility tests/docs or old manifest read compatibility.
|
|
1755
|
-
|
|
1756
|
-
- [ ] **Step 7: Commit verification fixes if any**
|
|
1757
|
-
|
|
1758
|
-
If any command in Steps 1-6 required code/doc fixes, commit them:
|
|
1759
|
-
|
|
1760
|
-
```bash
|
|
1761
|
-
git add <fixed-files>
|
|
1762
|
-
git commit -m "fix(runtime): align auto runtime verification"
|
|
1763
|
-
```
|
|
1764
|
-
|
|
1765
|
-
If no fixes were needed, do not create an empty commit.
|
|
1766
|
-
|
|
1767
|
-
- [ ] **Step 8: Final status**
|
|
1768
|
-
|
|
1769
|
-
Run:
|
|
1770
|
-
|
|
1771
|
-
```bash
|
|
1772
|
-
git status --short
|
|
1773
|
-
```
|
|
1774
|
-
|
|
1775
|
-
Expected: no unstaged implementation changes. Test artifacts under `.okstra` or temp directories must not be committed.
|