okstra 0.102.2 → 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.
Files changed (161) hide show
  1. package/README.kr.md +5 -5
  2. package/README.md +5 -5
  3. package/docs/kr/architecture/storage-model.md +1 -1
  4. package/docs/kr/architecture.md +7 -11
  5. package/docs/kr/cli.md +4 -4
  6. package/docs/kr/container.md +1 -1
  7. package/docs/kr/performance-improvement-plan-v2.md +2 -2
  8. package/docs/project-structure-overview.md +2 -2
  9. package/package.json +7 -2
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/bin/okstra-antigravity-exec.sh +1 -2
  12. package/runtime/bin/okstra-codex-exec.sh +1 -2
  13. package/runtime/bin/okstra-render-final-report.py +2 -9
  14. package/runtime/bin/okstra-spawn-followups.py +4 -6
  15. package/runtime/bin/okstra-trace-cleanup.sh +24 -4
  16. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  17. package/runtime/prompts/lead/report-writer.md +1 -1
  18. package/runtime/prompts/profiles/_common-contract.md +9 -11
  19. package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
  20. package/runtime/python/okstra_ctl/consumers.py +35 -0
  21. package/runtime/python/okstra_ctl/container.py +4 -6
  22. package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
  23. package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
  24. package/runtime/python/okstra_ctl/handoff.py +2 -1
  25. package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
  26. package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
  27. package/runtime/python/okstra_ctl/report_views.py +7 -4
  28. package/runtime/python/okstra_ctl/run.py +3 -10
  29. package/runtime/python/okstra_ctl/stage_targets.py +18 -0
  30. package/runtime/python/okstra_ctl/wizard.py +24 -25
  31. package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
  32. package/runtime/python/okstra_token_usage/report.py +2 -5
  33. package/runtime/validators/validate-report-views.py +2 -1
  34. package/runtime/validators/validate-run.py +1 -11
  35. package/src/commands/lifecycle/install.mjs +5 -5
  36. package/src/commands/lifecycle/uninstall.mjs +4 -4
  37. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
  38. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
  39. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
  40. package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
  41. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
  42. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
  43. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
  44. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
  45. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
  46. package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
  47. package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
  48. package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
  49. package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
  50. package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
  51. package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
  52. package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
  53. package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
  54. package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
  55. package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
  56. package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
  57. package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
  58. package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
  59. package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
  60. package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
  61. package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
  62. package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
  63. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
  64. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
  65. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
  66. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
  67. package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
  68. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
  69. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
  70. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
  71. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
  72. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
  73. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
  74. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
  75. package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
  76. package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
  77. package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
  78. package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
  79. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
  80. package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
  81. package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
  82. package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
  83. package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
  84. package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
  85. package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
  86. package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
  87. package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
  88. package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
  89. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
  90. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
  91. package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
  92. package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
  93. package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
  94. package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
  95. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
  96. package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
  97. package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
  98. package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
  99. package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
  100. package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
  101. package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
  102. package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
  103. package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
  104. package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
  105. package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
  106. package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
  107. package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
  108. package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
  109. package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
  110. package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
  111. package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
  112. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
  113. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
  114. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
  115. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
  116. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
  117. package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
  118. package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
  119. package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
  120. package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
  121. package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
  122. package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
  123. package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
  124. package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
  125. package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
  126. package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
  127. package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
  128. package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
  129. package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
  130. package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
  131. package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
  132. package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
  133. package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
  134. package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
  135. package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
  136. package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
  137. package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
  138. package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
  139. package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
  140. package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
  141. package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
  142. package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
  143. package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
  144. package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
  145. package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
  146. package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
  147. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
  148. package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
  149. package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
  150. package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
  151. package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
  152. package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
  153. package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
  154. package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
  155. package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
  156. package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
  157. package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
  158. package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
  159. package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
  160. package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
  161. 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.