oh-my-codex 0.18.6 → 0.18.7

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 (278) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +56 -7
  4. package/dist/agents/__tests__/definitions.test.js +11 -0
  5. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  6. package/dist/agents/__tests__/native-config.test.js +14 -5
  7. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  8. package/dist/agents/definitions.d.ts +2 -0
  9. package/dist/agents/definitions.d.ts.map +1 -1
  10. package/dist/agents/definitions.js +4 -1
  11. package/dist/agents/definitions.js.map +1 -1
  12. package/dist/agents/native-config.js +2 -2
  13. package/dist/agents/native-config.js.map +1 -1
  14. package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
  15. package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
  16. package/dist/autopilot/__tests__/fsm.test.js +75 -0
  17. package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
  18. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
  19. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
  20. package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
  21. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
  22. package/dist/autopilot/deep-interview-gate.d.ts +18 -0
  23. package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
  24. package/dist/autopilot/deep-interview-gate.js +256 -0
  25. package/dist/autopilot/deep-interview-gate.js.map +1 -0
  26. package/dist/autopilot/fsm.d.ts +13 -0
  27. package/dist/autopilot/fsm.d.ts.map +1 -0
  28. package/dist/autopilot/fsm.js +70 -0
  29. package/dist/autopilot/fsm.js.map +1 -0
  30. package/dist/autopilot/ralplan-gate.d.ts +17 -0
  31. package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
  32. package/dist/autopilot/ralplan-gate.js +61 -0
  33. package/dist/autopilot/ralplan-gate.js.map +1 -0
  34. package/dist/cli/__tests__/index.test.js +24 -4
  35. package/dist/cli/__tests__/index.test.js.map +1 -1
  36. package/dist/cli/__tests__/launch-fallback.test.js +175 -6
  37. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  38. package/dist/cli/__tests__/question.test.js +100 -0
  39. package/dist/cli/__tests__/question.test.js.map +1 -1
  40. package/dist/cli/__tests__/setup-refresh.test.js +18 -0
  41. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  42. package/dist/cli/__tests__/team.test.js +2 -2
  43. package/dist/cli/__tests__/team.test.js.map +1 -1
  44. package/dist/cli/index.d.ts +3 -1
  45. package/dist/cli/index.d.ts.map +1 -1
  46. package/dist/cli/index.js +191 -36
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/cli/question.d.ts.map +1 -1
  49. package/dist/cli/question.js +36 -5
  50. package/dist/cli/question.js.map +1 -1
  51. package/dist/config/__tests__/deep-interview.test.js +7 -6
  52. package/dist/config/__tests__/deep-interview.test.js.map +1 -1
  53. package/dist/config/deep-interview.d.ts.map +1 -1
  54. package/dist/config/deep-interview.js +14 -4
  55. package/dist/config/deep-interview.js.map +1 -1
  56. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
  57. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  58. package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
  59. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  60. package/dist/hooks/__tests__/keyword-detector.test.js +649 -11
  61. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  62. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +63 -0
  63. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  64. package/dist/hooks/__tests__/session.test.js +25 -0
  65. package/dist/hooks/__tests__/session.test.js.map +1 -1
  66. package/dist/hooks/deep-interview-config-instruction.js +1 -1
  67. package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
  68. package/dist/hooks/keyword-detector.d.ts +1 -0
  69. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  70. package/dist/hooks/keyword-detector.js +171 -21
  71. package/dist/hooks/keyword-detector.js.map +1 -1
  72. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  73. package/dist/hooks/keyword-registry.js +1 -0
  74. package/dist/hooks/keyword-registry.js.map +1 -1
  75. package/dist/hooks/session.d.ts +2 -0
  76. package/dist/hooks/session.d.ts.map +1 -1
  77. package/dist/hooks/session.js +13 -5
  78. package/dist/hooks/session.js.map +1 -1
  79. package/dist/hud/__tests__/authority.test.js +35 -0
  80. package/dist/hud/__tests__/authority.test.js.map +1 -1
  81. package/dist/hud/__tests__/index.test.js +168 -2
  82. package/dist/hud/__tests__/index.test.js.map +1 -1
  83. package/dist/hud/__tests__/reconcile.test.js +67 -13
  84. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  85. package/dist/hud/__tests__/state.test.js +80 -0
  86. package/dist/hud/__tests__/state.test.js.map +1 -1
  87. package/dist/hud/__tests__/tmux.test.js +134 -1
  88. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  89. package/dist/hud/authority.d.ts.map +1 -1
  90. package/dist/hud/authority.js +13 -2
  91. package/dist/hud/authority.js.map +1 -1
  92. package/dist/hud/index.d.ts +17 -0
  93. package/dist/hud/index.d.ts.map +1 -1
  94. package/dist/hud/index.js +64 -10
  95. package/dist/hud/index.js.map +1 -1
  96. package/dist/hud/reconcile.js +1 -1
  97. package/dist/hud/reconcile.js.map +1 -1
  98. package/dist/hud/state.d.ts.map +1 -1
  99. package/dist/hud/state.js +16 -1
  100. package/dist/hud/state.js.map +1 -1
  101. package/dist/hud/tmux.d.ts +2 -0
  102. package/dist/hud/tmux.d.ts.map +1 -1
  103. package/dist/hud/tmux.js +39 -2
  104. package/dist/hud/tmux.js.map +1 -1
  105. package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
  106. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  107. package/dist/mcp/__tests__/state-server.test.js +13 -1
  108. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  109. package/dist/mcp/hermes-bridge.d.ts +12 -2
  110. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  111. package/dist/mcp/hermes-bridge.js +83 -9
  112. package/dist/mcp/hermes-bridge.js.map +1 -1
  113. package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
  114. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  115. package/dist/pipeline/__tests__/stages.test.js +130 -0
  116. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  117. package/dist/pipeline/orchestrator.js +1 -1
  118. package/dist/pipeline/orchestrator.js.map +1 -1
  119. package/dist/pipeline/stages/ralplan.d.ts +1 -0
  120. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  121. package/dist/pipeline/stages/ralplan.js +14 -5
  122. package/dist/pipeline/stages/ralplan.js.map +1 -1
  123. package/dist/question/__tests__/deep-interview.test.js +160 -2
  124. package/dist/question/__tests__/deep-interview.test.js.map +1 -1
  125. package/dist/question/__tests__/policy.test.js +63 -3
  126. package/dist/question/__tests__/policy.test.js.map +1 -1
  127. package/dist/question/__tests__/renderer.test.js +191 -2
  128. package/dist/question/__tests__/renderer.test.js.map +1 -1
  129. package/dist/question/__tests__/state.test.js +94 -3
  130. package/dist/question/__tests__/state.test.js.map +1 -1
  131. package/dist/question/__tests__/ui.test.js +4 -0
  132. package/dist/question/__tests__/ui.test.js.map +1 -1
  133. package/dist/question/autopilot-wait.d.ts +12 -2
  134. package/dist/question/autopilot-wait.d.ts.map +1 -1
  135. package/dist/question/autopilot-wait.js +158 -47
  136. package/dist/question/autopilot-wait.js.map +1 -1
  137. package/dist/question/deep-interview.d.ts.map +1 -1
  138. package/dist/question/deep-interview.js +22 -6
  139. package/dist/question/deep-interview.js.map +1 -1
  140. package/dist/question/policy.d.ts.map +1 -1
  141. package/dist/question/policy.js +2 -5
  142. package/dist/question/policy.js.map +1 -1
  143. package/dist/question/renderer.d.ts +12 -0
  144. package/dist/question/renderer.d.ts.map +1 -1
  145. package/dist/question/renderer.js +87 -3
  146. package/dist/question/renderer.js.map +1 -1
  147. package/dist/question/state.d.ts +8 -1
  148. package/dist/question/state.d.ts.map +1 -1
  149. package/dist/question/state.js +54 -14
  150. package/dist/question/state.js.map +1 -1
  151. package/dist/question/types.d.ts +1 -1
  152. package/dist/question/types.d.ts.map +1 -1
  153. package/dist/question/ui.d.ts +1 -0
  154. package/dist/question/ui.d.ts.map +1 -1
  155. package/dist/question/ui.js +1 -0
  156. package/dist/question/ui.js.map +1 -1
  157. package/dist/ralplan/__tests__/runtime.test.js +191 -0
  158. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  159. package/dist/ralplan/consensus-gate.d.ts +9 -1
  160. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  161. package/dist/ralplan/consensus-gate.js +84 -2
  162. package/dist/ralplan/consensus-gate.js.map +1 -1
  163. package/dist/ralplan/runtime.d.ts +9 -0
  164. package/dist/ralplan/runtime.d.ts.map +1 -1
  165. package/dist/ralplan/runtime.js +32 -11
  166. package/dist/ralplan/runtime.js.map +1 -1
  167. package/dist/scripts/__tests__/codex-native-hook.test.js +1487 -34
  168. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  169. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  170. package/dist/scripts/codex-native-hook.js +356 -38
  171. package/dist/scripts/codex-native-hook.js.map +1 -1
  172. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  173. package/dist/scripts/codex-native-pre-post.js +79 -1
  174. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  175. package/dist/scripts/hook-payload-guard.d.ts +9 -0
  176. package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
  177. package/dist/scripts/hook-payload-guard.js +111 -0
  178. package/dist/scripts/hook-payload-guard.js.map +1 -0
  179. package/dist/scripts/notify-fallback-watcher.js +8 -1
  180. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  181. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
  182. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
  183. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
  184. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
  185. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  186. package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
  187. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  188. package/dist/scripts/notify-hook.js +11 -2
  189. package/dist/scripts/notify-hook.js.map +1 -1
  190. package/dist/state/__tests__/operations.test.js +1012 -1
  191. package/dist/state/__tests__/operations.test.js.map +1 -1
  192. package/dist/state/__tests__/skill-active.test.js +59 -1
  193. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  194. package/dist/state/__tests__/workflow-transition.test.js +73 -7
  195. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  196. package/dist/state/operations.d.ts.map +1 -1
  197. package/dist/state/operations.js +102 -0
  198. package/dist/state/operations.js.map +1 -1
  199. package/dist/state/skill-active.d.ts.map +1 -1
  200. package/dist/state/skill-active.js +33 -3
  201. package/dist/state/skill-active.js.map +1 -1
  202. package/dist/state/workflow-transition-reconcile.d.ts +6 -0
  203. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  204. package/dist/state/workflow-transition-reconcile.js +28 -1
  205. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  206. package/dist/state/workflow-transition.d.ts.map +1 -1
  207. package/dist/state/workflow-transition.js +10 -3
  208. package/dist/state/workflow-transition.js.map +1 -1
  209. package/dist/subagents/__tests__/tracker.test.js +139 -0
  210. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  211. package/dist/subagents/tracker.d.ts +3 -0
  212. package/dist/subagents/tracker.d.ts.map +1 -1
  213. package/dist/subagents/tracker.js +41 -4
  214. package/dist/subagents/tracker.js.map +1 -1
  215. package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
  216. package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
  217. package/dist/team/__tests__/coordination-protocol.test.js +173 -0
  218. package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
  219. package/dist/team/__tests__/runtime.test.js +51 -2
  220. package/dist/team/__tests__/runtime.test.js.map +1 -1
  221. package/dist/team/__tests__/state.test.js +83 -0
  222. package/dist/team/__tests__/state.test.js.map +1 -1
  223. package/dist/team/__tests__/tmux-session.test.js +45 -0
  224. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  225. package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
  226. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  227. package/dist/team/coordination-protocol.d.ts +14 -0
  228. package/dist/team/coordination-protocol.d.ts.map +1 -0
  229. package/dist/team/coordination-protocol.js +244 -0
  230. package/dist/team/coordination-protocol.js.map +1 -0
  231. package/dist/team/runtime.d.ts +1 -0
  232. package/dist/team/runtime.d.ts.map +1 -1
  233. package/dist/team/runtime.js +19 -3
  234. package/dist/team/runtime.js.map +1 -1
  235. package/dist/team/state/tasks.d.ts.map +1 -1
  236. package/dist/team/state/tasks.js +24 -0
  237. package/dist/team/state/tasks.js.map +1 -1
  238. package/dist/team/state/types.d.ts +21 -1
  239. package/dist/team/state/types.d.ts.map +1 -1
  240. package/dist/team/state/types.js.map +1 -1
  241. package/dist/team/state.d.ts +17 -1
  242. package/dist/team/state.d.ts.map +1 -1
  243. package/dist/team/state.js +12 -5
  244. package/dist/team/state.js.map +1 -1
  245. package/dist/team/team-ops.d.ts +1 -1
  246. package/dist/team/team-ops.d.ts.map +1 -1
  247. package/dist/team/team-ops.js.map +1 -1
  248. package/dist/team/tmux-session.d.ts.map +1 -1
  249. package/dist/team/tmux-session.js +19 -1
  250. package/dist/team/tmux-session.js.map +1 -1
  251. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  252. package/dist/team/worker-bootstrap.js +63 -0
  253. package/dist/team/worker-bootstrap.js.map +1 -1
  254. package/dist/utils/__tests__/agents-model-table.test.js +4 -2
  255. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  256. package/dist/utils/agents-model-table.d.ts.map +1 -1
  257. package/dist/utils/agents-model-table.js +3 -0
  258. package/dist/utils/agents-model-table.js.map +1 -1
  259. package/package.json +1 -1
  260. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  261. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +10 -5
  262. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
  263. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
  264. package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
  265. package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
  266. package/skills/autopilot/SKILL.md +10 -5
  267. package/skills/deep-interview/SKILL.md +9 -4
  268. package/skills/ralplan/SKILL.md +12 -0
  269. package/skills/team/SKILL.md +16 -0
  270. package/skills/worker/SKILL.md +14 -0
  271. package/src/scripts/__tests__/codex-native-hook.test.ts +2202 -523
  272. package/src/scripts/codex-native-hook.ts +444 -36
  273. package/src/scripts/codex-native-pre-post.ts +80 -0
  274. package/src/scripts/hook-payload-guard.ts +113 -0
  275. package/src/scripts/notify-fallback-watcher.ts +8 -1
  276. package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
  277. package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
  278. package/src/scripts/notify-hook.ts +14 -2
@@ -2,9 +2,10 @@ import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
4
  import { existsSync } from 'node:fs';
5
- import { join } from 'node:path';
5
+ import { dirname, join } from 'node:path';
6
6
  import { tmpdir } from 'node:os';
7
7
  import { executeStateOperation } from '../operations.js';
8
+ import { subagentTrackingPath } from '../../subagents/tracker.js';
8
9
  async function withAmbientTmuxEnv(env, run) {
9
10
  const previousTmux = process.env.TMUX;
10
11
  const previousTmuxPane = process.env.TMUX_PANE;
@@ -39,6 +40,80 @@ async function withAmbientTmuxEnv(env, run) {
39
40
  delete process.env.PATH;
40
41
  }
41
42
  }
43
+ async function withOmxRootEnv(root, run) {
44
+ const previousOmxRoot = process.env.OMX_ROOT;
45
+ const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
46
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
47
+ process.env.OMX_ROOT = root;
48
+ delete process.env.OMX_STATE_ROOT;
49
+ delete process.env.OMX_TEAM_STATE_ROOT;
50
+ try {
51
+ return await run();
52
+ }
53
+ finally {
54
+ if (typeof previousOmxRoot === 'string')
55
+ process.env.OMX_ROOT = previousOmxRoot;
56
+ else
57
+ delete process.env.OMX_ROOT;
58
+ if (typeof previousOmxStateRoot === 'string')
59
+ process.env.OMX_STATE_ROOT = previousOmxStateRoot;
60
+ else
61
+ delete process.env.OMX_STATE_ROOT;
62
+ if (typeof previousTeamStateRoot === 'string')
63
+ process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
64
+ else
65
+ delete process.env.OMX_TEAM_STATE_ROOT;
66
+ }
67
+ }
68
+ async function writeNativeSubagentTracking(cwd, sessionId) {
69
+ const trackingPath = subagentTrackingPath(cwd);
70
+ const now = '2026-05-28T00:00:00.000Z';
71
+ await mkdir(dirname(trackingPath), { recursive: true });
72
+ await writeFile(trackingPath, JSON.stringify({
73
+ schemaVersion: 1,
74
+ sessions: {
75
+ [sessionId]: {
76
+ session_id: sessionId,
77
+ leader_thread_id: 'thread-leader',
78
+ updated_at: now,
79
+ threads: {
80
+ 'thread-leader': { thread_id: 'thread-leader', kind: 'leader', first_seen_at: now, last_seen_at: now, turn_count: 1 },
81
+ 'thread-architect': { thread_id: 'thread-architect', kind: 'subagent', first_seen_at: now, last_seen_at: now, completed_at: now, turn_count: 1 },
82
+ 'thread-critic': { thread_id: 'thread-critic', kind: 'subagent', first_seen_at: now, last_seen_at: now, completed_at: now, turn_count: 1 },
83
+ },
84
+ },
85
+ },
86
+ }, null, 2));
87
+ }
88
+ function ralplanConsensusGate(sessionId, provenanceKind, threadOverrides = {}) {
89
+ const architectThread = threadOverrides.architect ?? (provenanceKind === 'native_subagent' ? 'thread-architect' : 'exec-architect');
90
+ const criticThread = threadOverrides.critic ?? (provenanceKind === 'native_subagent' ? 'thread-critic' : 'exec-critic');
91
+ return {
92
+ required: true,
93
+ complete: true,
94
+ sequence: ['architect-review', 'critic-review'],
95
+ planning_artifacts_are_not_consensus: true,
96
+ required_review_roles: ['architect', 'critic'],
97
+ ralplan_architect_review: {
98
+ agent_role: 'architect',
99
+ verdict: 'approve',
100
+ provenance_kind: provenanceKind,
101
+ session_id: sessionId,
102
+ thread_id: architectThread,
103
+ artifact_path: '.omx/artifacts/architect.md',
104
+ tracker_path: '.omx/state/subagent-tracking.json',
105
+ },
106
+ ralplan_critic_review: {
107
+ agent_role: 'critic',
108
+ verdict: 'approve',
109
+ provenance_kind: provenanceKind,
110
+ session_id: sessionId,
111
+ thread_id: criticThread,
112
+ artifact_path: '.omx/artifacts/critic.md',
113
+ tracker_path: '.omx/state/subagent-tracking.json',
114
+ },
115
+ };
116
+ }
42
117
  async function createFakeTmuxBin(wd) {
43
118
  const fakeBin = join(wd, 'bin');
44
119
  await mkdir(fakeBin, { recursive: true });
@@ -123,6 +198,69 @@ describe('state operations directory initialization', () => {
123
198
  await rm(wd, { recursive: true, force: true });
124
199
  }
125
200
  });
201
+ it('surfaces active ultragoal artifacts in list-active without mode state files', async () => {
202
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ultragoal-artifact-'));
203
+ try {
204
+ await mkdir(join(wd, '.omx', 'ultragoal'), { recursive: true });
205
+ await writeFile(join(wd, '.omx', 'ultragoal', 'goals.json'), JSON.stringify({
206
+ activeGoalId: 'G001',
207
+ goals: [{
208
+ id: 'G001',
209
+ title: 'Fix duplicate HUD panes',
210
+ objective: 'Keep one HUD renderer per leader.',
211
+ status: 'in_progress',
212
+ }],
213
+ }, null, 2));
214
+ const activeResponse = await executeStateOperation('state_list_active', {
215
+ workingDirectory: wd,
216
+ });
217
+ assert.deepEqual(activeResponse.payload, { active_modes: ['ultragoal'] });
218
+ const statusResponse = await executeStateOperation('state_get_status', {
219
+ workingDirectory: wd,
220
+ mode: 'ultragoal',
221
+ });
222
+ const statuses = statusResponse.payload.statuses || {};
223
+ assert.equal(statuses.ultragoal?.active, true);
224
+ assert.equal(statuses.ultragoal?.phase, 'in_progress');
225
+ assert.equal(statuses.ultragoal?.path, join(wd, '.omx', 'ultragoal', 'goals.json'));
226
+ assert.equal(statuses.ultragoal?.source, 'ultragoal-artifacts');
227
+ }
228
+ finally {
229
+ await rm(wd, { recursive: true, force: true });
230
+ }
231
+ });
232
+ it('prefers active ultragoal artifacts over stale inactive mode state', async () => {
233
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ultragoal-stale-state-'));
234
+ try {
235
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
236
+ await mkdir(join(wd, '.omx', 'ultragoal'), { recursive: true });
237
+ await writeFile(join(wd, '.omx', 'state', 'ultragoal-state.json'), JSON.stringify({ active: false, current_phase: 'cleared' }, null, 2));
238
+ await writeFile(join(wd, '.omx', 'ultragoal', 'goals.json'), JSON.stringify({
239
+ activeGoalId: 'G001',
240
+ goals: [{
241
+ id: 'G001',
242
+ title: 'Fix duplicate HUD panes',
243
+ objective: 'Keep one HUD renderer per leader.',
244
+ status: 'in_progress',
245
+ }],
246
+ }, null, 2));
247
+ const activeResponse = await executeStateOperation('state_list_active', {
248
+ workingDirectory: wd,
249
+ });
250
+ assert.deepEqual(activeResponse.payload, { active_modes: ['ultragoal'] });
251
+ const statusResponse = await executeStateOperation('state_get_status', {
252
+ workingDirectory: wd,
253
+ mode: 'ultragoal',
254
+ });
255
+ const statuses = statusResponse.payload.statuses || {};
256
+ assert.equal(statuses.ultragoal?.active, true);
257
+ assert.equal(statuses.ultragoal?.phase, 'in_progress');
258
+ assert.equal(statuses.ultragoal?.source, 'ultragoal-artifacts');
259
+ }
260
+ finally {
261
+ await rm(wd, { recursive: true, force: true });
262
+ }
263
+ });
126
264
  it('does not treat root fallback as active for explicit session list-active decisions', async () => {
127
265
  const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-active-scope-'));
128
266
  try {
@@ -478,6 +616,879 @@ describe('state operations directory initialization', () => {
478
616
  await rm(wd, { recursive: true, force: true });
479
617
  }
480
618
  });
619
+ it('rejects standalone ralplan writes while preserving active Autopilot supervisor state', async () => {
620
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-child-'));
621
+ try {
622
+ await withOmxRootEnv(wd, async () => {
623
+ const sessionId = 'sess-autopilot-ralplan-child';
624
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
625
+ await mkdir(sessionDir, { recursive: true });
626
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
627
+ active: true,
628
+ mode: 'autopilot',
629
+ current_phase: 'deep-interview',
630
+ state: {
631
+ deep_interview_gate: {
632
+ status: 'required',
633
+ skip_reason: null,
634
+ },
635
+ },
636
+ }, null, 2));
637
+ const denied = await executeStateOperation('state_write', {
638
+ workingDirectory: wd,
639
+ session_id: sessionId,
640
+ mode: 'ralplan',
641
+ active: true,
642
+ current_phase: 'planning',
643
+ });
644
+ assert.equal(denied.isError, true);
645
+ assert.match(String(denied.payload.error || ''), /Execution-to-planning rollback auto-complete is not allowed\./);
646
+ assert.equal(existsSync(join(sessionDir, 'ralplan-state.json')), false);
647
+ const autopilotState = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
648
+ assert.equal(autopilotState.active, true);
649
+ assert.equal(autopilotState.mode, 'autopilot');
650
+ assert.equal(autopilotState.current_phase, 'deep-interview');
651
+ assert.equal(autopilotState.auto_completed_reason, undefined);
652
+ });
653
+ }
654
+ finally {
655
+ await rm(wd, { recursive: true, force: true });
656
+ }
657
+ });
658
+ it('allows Autopilot itself to enter the supervised ralplan child phase', async () => {
659
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-child-phase-'));
660
+ try {
661
+ await withOmxRootEnv(wd, async () => {
662
+ const sessionId = 'sess-autopilot-child-phase';
663
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
664
+ await mkdir(sessionDir, { recursive: true });
665
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
666
+ active: true,
667
+ mode: 'autopilot',
668
+ current_phase: 'deep-interview',
669
+ state: {
670
+ deep_interview_gate: {
671
+ status: 'complete',
672
+ rationale: 'Requirements clarified and ready for consensus planning.',
673
+ },
674
+ handoff_artifacts: {
675
+ deep_interview: {
676
+ summary: 'Autopilot may proceed to ralplan.',
677
+ },
678
+ },
679
+ },
680
+ }, null, 2));
681
+ const response = await executeStateOperation('state_write', {
682
+ workingDirectory: wd,
683
+ session_id: sessionId,
684
+ mode: 'autopilot',
685
+ active: true,
686
+ current_phase: 'ralplan',
687
+ });
688
+ assert.equal(response.isError, undefined);
689
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
690
+ assert.equal(state.active, true);
691
+ assert.equal(state.mode, 'autopilot');
692
+ assert.equal(state.current_phase, 'ralplan');
693
+ });
694
+ }
695
+ finally {
696
+ await rm(wd, { recursive: true, force: true });
697
+ }
698
+ });
699
+ it('denies Autopilot direct deep-interview to ultragoal skip without deep-interview evidence', async () => {
700
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-direct-di-skip-deny-'));
701
+ try {
702
+ await withOmxRootEnv(wd, async () => {
703
+ const sessionId = 'sess-autopilot-direct-di-skip-deny';
704
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
705
+ await mkdir(sessionDir, { recursive: true });
706
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
707
+ active: true,
708
+ mode: 'autopilot',
709
+ current_phase: 'deep-interview',
710
+ state: {
711
+ deep_interview_gate: { status: 'required' },
712
+ },
713
+ }, null, 2));
714
+ const response = await executeStateOperation('state_write', {
715
+ workingDirectory: wd,
716
+ session_id: sessionId,
717
+ mode: 'autopilot',
718
+ active: true,
719
+ current_phase: 'ultragoal',
720
+ });
721
+ assert.equal(response.isError, true);
722
+ assert.match(String(response.payload.error || ''), /Cannot transition ralplan -> ultragoal|Unsupported|cannot/i);
723
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
724
+ assert.equal(state.current_phase, 'deep-interview');
725
+ });
726
+ }
727
+ finally {
728
+ await rm(wd, { recursive: true, force: true });
729
+ }
730
+ });
731
+ it('denies Autopilot deep-interview completion before the ralplan gate', async () => {
732
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-di-complete-deny-'));
733
+ try {
734
+ await withOmxRootEnv(wd, async () => {
735
+ const sessionId = 'sess-autopilot-di-complete-deny';
736
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
737
+ await mkdir(sessionDir, { recursive: true });
738
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
739
+ active: true,
740
+ mode: 'autopilot',
741
+ current_phase: 'deep-interview',
742
+ state: {
743
+ deep_interview_gate: { status: 'required' },
744
+ },
745
+ }, null, 2));
746
+ const response = await executeStateOperation('state_write', {
747
+ workingDirectory: wd,
748
+ session_id: sessionId,
749
+ mode: 'autopilot',
750
+ active: false,
751
+ current_phase: 'complete',
752
+ });
753
+ assert.equal(response.isError, true);
754
+ assert.match(String(response.payload.error || ''), /Cannot complete Autopilot before ralplan gate/i);
755
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
756
+ assert.equal(state.current_phase, 'deep-interview');
757
+ assert.equal(state.active, true);
758
+ });
759
+ }
760
+ finally {
761
+ await rm(wd, { recursive: true, force: true });
762
+ }
763
+ });
764
+ it('denies Autopilot direct deep-interview to ultragoal skip even with deep-interview evidence', async () => {
765
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-direct-di-complete-deny-'));
766
+ try {
767
+ await withOmxRootEnv(wd, async () => {
768
+ const sessionId = 'sess-autopilot-direct-di-complete-deny';
769
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
770
+ await mkdir(sessionDir, { recursive: true });
771
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
772
+ active: true,
773
+ mode: 'autopilot',
774
+ current_phase: 'deep-interview',
775
+ state: {
776
+ deep_interview_gate: {
777
+ status: 'complete',
778
+ rationale: 'Deep-interview is complete, but ralplan consensus is not.',
779
+ },
780
+ handoff_artifacts: {
781
+ deep_interview: { summary: 'Ready for ralplan only.' },
782
+ },
783
+ },
784
+ }, null, 2));
785
+ const response = await executeStateOperation('state_write', {
786
+ workingDirectory: wd,
787
+ session_id: sessionId,
788
+ mode: 'autopilot',
789
+ active: true,
790
+ current_phase: 'ultragoal',
791
+ });
792
+ assert.equal(response.isError, true);
793
+ assert.match(String(response.payload.error || ''), /Cannot transition ralplan -> ultragoal|Unsupported|cannot/i);
794
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
795
+ assert.equal(state.current_phase, 'deep-interview');
796
+ });
797
+ }
798
+ finally {
799
+ await rm(wd, { recursive: true, force: true });
800
+ }
801
+ });
802
+ it('denies Autopilot deep-interview to ralplan self-write when only a satisfied question exists', async () => {
803
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-child-phase-deny-'));
804
+ try {
805
+ await withOmxRootEnv(wd, async () => {
806
+ const sessionId = 'sess-autopilot-child-phase-deny';
807
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
808
+ await mkdir(sessionDir, { recursive: true });
809
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
810
+ active: true,
811
+ mode: 'autopilot',
812
+ current_phase: 'deep-interview',
813
+ question_enforcement: {
814
+ obligation_id: 'obligation-answered',
815
+ source: 'omx-question',
816
+ status: 'satisfied',
817
+ lifecycle_outcome: 'askuserQuestion',
818
+ requested_at: '2026-05-28T00:00:00.000Z',
819
+ question_id: 'question-answered',
820
+ satisfied_at: '2026-05-28T00:01:00.000Z',
821
+ },
822
+ }, null, 2));
823
+ const response = await executeStateOperation('state_write', {
824
+ workingDirectory: wd,
825
+ session_id: sessionId,
826
+ mode: 'autopilot',
827
+ active: true,
828
+ current_phase: 'ralplan',
829
+ });
830
+ assert.equal(response.isError, true);
831
+ assert.match(String(response.payload.error || ''), /missing deep-interview completion\/skip gate/i);
832
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
833
+ assert.equal(state.current_phase, 'deep-interview');
834
+ });
835
+ }
836
+ finally {
837
+ await rm(wd, { recursive: true, force: true });
838
+ }
839
+ });
840
+ it('denies Autopilot waiting-for-user to ralplan self-write while the deep-interview question is unresolved', async () => {
841
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-waiting-question-deny-'));
842
+ try {
843
+ await withOmxRootEnv(wd, async () => {
844
+ const sessionId = 'sess-autopilot-waiting-question-deny';
845
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
846
+ await mkdir(sessionDir, { recursive: true });
847
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
848
+ active: true,
849
+ mode: 'autopilot',
850
+ current_phase: 'waiting-for-user',
851
+ run_outcome: 'blocked_on_user',
852
+ lifecycle_outcome: 'askuserQuestion',
853
+ state: {
854
+ deep_interview_question: {
855
+ status: 'waiting_for_user',
856
+ source: 'omx-question',
857
+ obligation_id: 'obligation-waiting',
858
+ previous_phase: 'deep-interview',
859
+ requested_at: '2026-05-28T00:00:00.000Z',
860
+ },
861
+ deep_interview_gate: {
862
+ status: 'complete',
863
+ rationale: 'Stale completion gate must not bypass an unresolved question.',
864
+ },
865
+ },
866
+ }, null, 2));
867
+ const response = await executeStateOperation('state_write', {
868
+ workingDirectory: wd,
869
+ session_id: sessionId,
870
+ mode: 'autopilot',
871
+ active: true,
872
+ current_phase: 'ralplan',
873
+ });
874
+ assert.equal(response.isError, true);
875
+ assert.match(String(response.payload.error || ''), /question obligation is still pending/i);
876
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
877
+ assert.equal(state.current_phase, 'waiting-for-user');
878
+ });
879
+ }
880
+ finally {
881
+ await rm(wd, { recursive: true, force: true });
882
+ }
883
+ });
884
+ it('denies Autopilot handoff when the next state omits a still-pending deep-interview question', async () => {
885
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-omitted-question-deny-'));
886
+ try {
887
+ await withOmxRootEnv(wd, async () => {
888
+ const sessionId = 'sess-autopilot-omitted-question-deny';
889
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
890
+ await mkdir(sessionDir, { recursive: true });
891
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
892
+ active: true,
893
+ mode: 'autopilot',
894
+ current_phase: 'waiting-for-user',
895
+ run_outcome: 'blocked_on_user',
896
+ lifecycle_outcome: 'askuserQuestion',
897
+ state: {
898
+ deep_interview_question: {
899
+ status: 'waiting_for_user',
900
+ source: 'omx-question',
901
+ obligation_id: 'obligation-omitted',
902
+ previous_phase: 'deep-interview',
903
+ requested_at: '2026-05-28T00:00:00.000Z',
904
+ },
905
+ deep_interview_gate: {
906
+ status: 'required',
907
+ rationale: 'Question still needs an answer.',
908
+ },
909
+ },
910
+ }, null, 2));
911
+ const response = await executeStateOperation('state_write', {
912
+ workingDirectory: wd,
913
+ session_id: sessionId,
914
+ mode: 'autopilot',
915
+ active: true,
916
+ current_phase: 'ralplan',
917
+ state: {
918
+ deep_interview_gate: {
919
+ status: 'complete',
920
+ rationale: 'Replacement state must not erase an unanswered question obligation.',
921
+ },
922
+ handoff_artifacts: {
923
+ deep_interview: { summary: 'Ready for planning.' },
924
+ },
925
+ },
926
+ });
927
+ assert.equal(response.isError, true);
928
+ assert.match(String(response.payload.error || ''), /question obligation is still pending/i);
929
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
930
+ assert.equal(state.current_phase, 'waiting-for-user');
931
+ });
932
+ }
933
+ finally {
934
+ await rm(wd, { recursive: true, force: true });
935
+ }
936
+ });
937
+ it('ignores stale standalone deep-interview question state for Autopilot supervisor handoff', async () => {
938
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ignore-standalone-di-'));
939
+ try {
940
+ await withOmxRootEnv(wd, async () => {
941
+ const sessionId = 'sess-autopilot-ignore-standalone-di';
942
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
943
+ await mkdir(sessionDir, { recursive: true });
944
+ await writeFile(join(sessionDir, 'deep-interview-state.json'), JSON.stringify({
945
+ active: false,
946
+ mode: 'deep-interview',
947
+ current_phase: 'completed',
948
+ question_enforcement: {
949
+ obligation_id: 'stale-obligation',
950
+ source: 'omx-question',
951
+ status: 'pending',
952
+ lifecycle_outcome: 'askuserQuestion',
953
+ requested_at: '2026-05-28T00:00:00.000Z',
954
+ },
955
+ }, null, 2));
956
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
957
+ active: true,
958
+ mode: 'autopilot',
959
+ current_phase: 'deep-interview',
960
+ state: {
961
+ deep_interview_gate: {
962
+ status: 'complete',
963
+ rationale: 'Autopilot-owned gate is complete.',
964
+ },
965
+ handoff_artifacts: {
966
+ deep_interview: { summary: 'Autopilot-owned handoff is ready.' },
967
+ },
968
+ },
969
+ }, null, 2));
970
+ const response = await executeStateOperation('state_write', {
971
+ workingDirectory: wd,
972
+ session_id: sessionId,
973
+ mode: 'autopilot',
974
+ active: true,
975
+ current_phase: 'ralplan',
976
+ });
977
+ assert.equal(response.isError, undefined);
978
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
979
+ assert.equal(state.current_phase, 'ralplan');
980
+ });
981
+ }
982
+ finally {
983
+ await rm(wd, { recursive: true, force: true });
984
+ }
985
+ });
986
+ it('denies Autopilot satisfied nested question handoff without a record-backed question id', async () => {
987
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-satisfied-question-deny-'));
988
+ try {
989
+ await withOmxRootEnv(wd, async () => {
990
+ const sessionId = 'sess-autopilot-satisfied-question-deny';
991
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
992
+ await mkdir(sessionDir, { recursive: true });
993
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
994
+ active: true,
995
+ mode: 'autopilot',
996
+ current_phase: 'deep-interview',
997
+ state: {
998
+ deep_interview_question: {
999
+ status: 'satisfied',
1000
+ source: 'omx-question',
1001
+ obligation_id: 'obligation-no-record',
1002
+ previous_phase: 'deep-interview',
1003
+ requested_at: '2026-05-28T00:00:00.000Z',
1004
+ satisfied_at: '2026-05-28T00:01:00.000Z',
1005
+ },
1006
+ deep_interview_gate: {
1007
+ status: 'complete',
1008
+ rationale: 'Question satisfaction must be backed by an answered record.',
1009
+ },
1010
+ },
1011
+ }, null, 2));
1012
+ const response = await executeStateOperation('state_write', {
1013
+ workingDirectory: wd,
1014
+ session_id: sessionId,
1015
+ mode: 'autopilot',
1016
+ active: true,
1017
+ current_phase: 'ralplan',
1018
+ });
1019
+ assert.equal(response.isError, true);
1020
+ assert.match(String(response.payload.error || ''), /lacks same-session answered omx question record/i);
1021
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1022
+ assert.equal(state.current_phase, 'deep-interview');
1023
+ });
1024
+ }
1025
+ finally {
1026
+ await rm(wd, { recursive: true, force: true });
1027
+ }
1028
+ });
1029
+ it('allows Autopilot handoff when next state satisfies a previously pending question', async () => {
1030
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-next-question-satisfied-'));
1031
+ try {
1032
+ await withOmxRootEnv(wd, async () => {
1033
+ const sessionId = 'sess-autopilot-next-question-satisfied';
1034
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1035
+ const questionId = 'question-next-satisfied';
1036
+ await mkdir(join(sessionDir, 'questions'), { recursive: true });
1037
+ await writeFile(join(sessionDir, 'questions', `${questionId}.json`), JSON.stringify({
1038
+ kind: 'omx.question/v1',
1039
+ question_id: questionId,
1040
+ session_id: sessionId,
1041
+ source: 'deep-interview',
1042
+ status: 'answered',
1043
+ answer: 'lowercase ascii slug',
1044
+ answers: [{ question_id: 'q-1', index: 0, answer: 'lowercase ascii slug' }],
1045
+ }, null, 2));
1046
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1047
+ active: true,
1048
+ mode: 'autopilot',
1049
+ current_phase: 'deep-interview',
1050
+ state: {
1051
+ deep_interview_question: {
1052
+ obligation_id: 'obligation-next-satisfied',
1053
+ source: 'omx-question',
1054
+ status: 'waiting_for_user',
1055
+ requested_at: '2026-05-28T00:00:00.000Z',
1056
+ },
1057
+ deep_interview_gate: { status: 'required' },
1058
+ },
1059
+ }, null, 2));
1060
+ const response = await executeStateOperation('state_write', {
1061
+ workingDirectory: wd,
1062
+ session_id: sessionId,
1063
+ mode: 'autopilot',
1064
+ active: true,
1065
+ current_phase: 'ralplan',
1066
+ state: {
1067
+ deep_interview_question: {
1068
+ obligation_id: 'obligation-next-satisfied',
1069
+ source: 'omx-question',
1070
+ status: 'satisfied',
1071
+ requested_at: '2026-05-28T00:00:00.000Z',
1072
+ question_id: questionId,
1073
+ satisfied_at: '2026-05-28T00:01:00.000Z',
1074
+ },
1075
+ deep_interview_gate: {
1076
+ status: 'complete',
1077
+ rationale: 'The answered question resolves the CLI output policy.',
1078
+ },
1079
+ handoff_artifacts: {
1080
+ deep_interview: { summary: 'Ready for ralplan after answered question.' },
1081
+ },
1082
+ },
1083
+ });
1084
+ assert.equal(response.isError, undefined);
1085
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1086
+ assert.equal(state.current_phase, 'ralplan');
1087
+ });
1088
+ }
1089
+ finally {
1090
+ await rm(wd, { recursive: true, force: true });
1091
+ }
1092
+ });
1093
+ it('allows Autopilot deep-interview to ralplan self-write with explicit user-authorized skip evidence', async () => {
1094
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-child-phase-skip-'));
1095
+ try {
1096
+ await withOmxRootEnv(wd, async () => {
1097
+ const sessionId = 'sess-autopilot-child-phase-skip';
1098
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1099
+ await mkdir(sessionDir, { recursive: true });
1100
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1101
+ active: true,
1102
+ mode: 'autopilot',
1103
+ current_phase: 'deep-interview',
1104
+ state: {
1105
+ deep_interview_gate: {
1106
+ status: 'skipped',
1107
+ skip_authorized_by_user: true,
1108
+ skip_reason: 'User explicitly authorized skipping deep-interview for this bounded follow-up.',
1109
+ skipped_at: '2026-05-28T00:02:00.000Z',
1110
+ source: 'user',
1111
+ session_id: sessionId,
1112
+ },
1113
+ },
1114
+ }, null, 2));
1115
+ const response = await executeStateOperation('state_write', {
1116
+ workingDirectory: wd,
1117
+ session_id: sessionId,
1118
+ mode: 'autopilot',
1119
+ active: true,
1120
+ current_phase: 'ralplan',
1121
+ });
1122
+ assert.equal(response.isError, undefined);
1123
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1124
+ assert.equal(state.current_phase, 'ralplan');
1125
+ });
1126
+ }
1127
+ finally {
1128
+ await rm(wd, { recursive: true, force: true });
1129
+ }
1130
+ });
1131
+ it('resolves Autopilot satisfied question evidence under OMX_TEAM_STATE_ROOT', async () => {
1132
+ const root = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-team-question-'));
1133
+ const previousOmxRoot = process.env.OMX_ROOT;
1134
+ const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
1135
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
1136
+ try {
1137
+ const wd = join(root, 'source');
1138
+ const teamStateRoot = join(root, 'team-state');
1139
+ const sessionId = 'sess-autopilot-team-question';
1140
+ const sessionDir = join(teamStateRoot, 'sessions', sessionId);
1141
+ const questionId = 'question-team-satisfied';
1142
+ await mkdir(join(sessionDir, 'questions'), { recursive: true });
1143
+ await writeFile(join(sessionDir, 'questions', `${questionId}.json`), JSON.stringify({
1144
+ kind: 'omx.question/v1',
1145
+ question_id: questionId,
1146
+ session_id: sessionId,
1147
+ source: 'deep-interview',
1148
+ status: 'answered',
1149
+ answer: 'clarified scope',
1150
+ answers: [{ question_id: 'q-1', index: 0, answer: 'clarified scope' }],
1151
+ }, null, 2));
1152
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1153
+ active: true,
1154
+ mode: 'autopilot',
1155
+ current_phase: 'deep-interview',
1156
+ question_enforcement: {
1157
+ obligation_id: 'obligation-team-question',
1158
+ source: 'omx-question',
1159
+ status: 'satisfied',
1160
+ lifecycle_outcome: 'askuserQuestion',
1161
+ requested_at: '2026-05-28T00:00:00.000Z',
1162
+ question_id: questionId,
1163
+ satisfied_at: '2026-05-28T00:01:00.000Z',
1164
+ },
1165
+ state: {
1166
+ deep_interview_gate: {
1167
+ status: 'complete',
1168
+ rationale: 'The answered question resolves the execution boundary.',
1169
+ },
1170
+ },
1171
+ }, null, 2));
1172
+ delete process.env.OMX_ROOT;
1173
+ delete process.env.OMX_STATE_ROOT;
1174
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
1175
+ const response = await executeStateOperation('state_write', {
1176
+ workingDirectory: wd,
1177
+ session_id: sessionId,
1178
+ mode: 'autopilot',
1179
+ active: true,
1180
+ current_phase: 'ralplan',
1181
+ });
1182
+ assert.equal(response.isError, undefined);
1183
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1184
+ assert.equal(state.current_phase, 'ralplan');
1185
+ assert.equal(existsSync(join(wd, '.omx', 'state', 'sessions', sessionId, 'questions', `${questionId}.json`)), false);
1186
+ }
1187
+ finally {
1188
+ if (typeof previousOmxRoot === 'string')
1189
+ process.env.OMX_ROOT = previousOmxRoot;
1190
+ else
1191
+ delete process.env.OMX_ROOT;
1192
+ if (typeof previousOmxStateRoot === 'string')
1193
+ process.env.OMX_STATE_ROOT = previousOmxStateRoot;
1194
+ else
1195
+ delete process.env.OMX_STATE_ROOT;
1196
+ if (typeof previousTeamStateRoot === 'string')
1197
+ process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
1198
+ else
1199
+ delete process.env.OMX_TEAM_STATE_ROOT;
1200
+ await rm(root, { recursive: true, force: true });
1201
+ }
1202
+ });
1203
+ it('denies Autopilot direct ralplan to code-review skip without native consensus evidence', async () => {
1204
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-direct-ralplan-skip-deny-'));
1205
+ try {
1206
+ await withOmxRootEnv(wd, async () => {
1207
+ const sessionId = 'sess-autopilot-direct-ralplan-skip-deny';
1208
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1209
+ await mkdir(sessionDir, { recursive: true });
1210
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1211
+ active: true,
1212
+ mode: 'autopilot',
1213
+ current_phase: 'ralplan',
1214
+ }, null, 2));
1215
+ const response = await executeStateOperation('state_write', {
1216
+ workingDirectory: wd,
1217
+ session_id: sessionId,
1218
+ mode: 'autopilot',
1219
+ active: true,
1220
+ current_phase: 'code-review',
1221
+ });
1222
+ assert.equal(response.isError, true);
1223
+ assert.match(String(response.payload.error || ''), /Cannot skip Autopilot ultragoal gate/i);
1224
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1225
+ assert.equal(state.current_phase, 'ralplan');
1226
+ });
1227
+ }
1228
+ finally {
1229
+ await rm(wd, { recursive: true, force: true });
1230
+ }
1231
+ });
1232
+ it('denies Autopilot ralplan completion before the ultragoal gate', async () => {
1233
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-complete-deny-'));
1234
+ try {
1235
+ await withOmxRootEnv(wd, async () => {
1236
+ const sessionId = 'sess-autopilot-ralplan-complete-deny';
1237
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1238
+ await mkdir(sessionDir, { recursive: true });
1239
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1240
+ active: true,
1241
+ mode: 'autopilot',
1242
+ current_phase: 'ralplan',
1243
+ state: {
1244
+ handoff_artifacts: {
1245
+ ralplan: {
1246
+ plan_path: '.omx/plans/prd.md',
1247
+ test_spec_path: '.omx/plans/test-spec.md',
1248
+ },
1249
+ },
1250
+ },
1251
+ }, null, 2));
1252
+ const response = await executeStateOperation('state_write', {
1253
+ workingDirectory: wd,
1254
+ session_id: sessionId,
1255
+ mode: 'autopilot',
1256
+ active: false,
1257
+ current_phase: 'complete',
1258
+ });
1259
+ assert.equal(response.isError, true);
1260
+ assert.match(String(response.payload.error || ''), /Cannot complete Autopilot before ultragoal gate/i);
1261
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1262
+ assert.equal(state.current_phase, 'ralplan');
1263
+ assert.equal(state.active, true);
1264
+ });
1265
+ }
1266
+ finally {
1267
+ await rm(wd, { recursive: true, force: true });
1268
+ }
1269
+ });
1270
+ it('denies Autopilot ralplan to ultragoal self-write with codex_exec consensus evidence', async () => {
1271
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-native-deny-'));
1272
+ try {
1273
+ await withOmxRootEnv(wd, async () => {
1274
+ const sessionId = 'sess-autopilot-ralplan-native-deny';
1275
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1276
+ await mkdir(sessionDir, { recursive: true });
1277
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1278
+ active: true,
1279
+ mode: 'autopilot',
1280
+ current_phase: 'ralplan',
1281
+ state: {
1282
+ handoff_artifacts: {
1283
+ ralplan: {
1284
+ plan_path: '.omx/plans/prd.md',
1285
+ test_spec_path: '.omx/plans/test-spec.md',
1286
+ },
1287
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'codex_exec'),
1288
+ },
1289
+ },
1290
+ }, null, 2));
1291
+ const response = await executeStateOperation('state_write', {
1292
+ workingDirectory: wd,
1293
+ session_id: sessionId,
1294
+ mode: 'autopilot',
1295
+ active: true,
1296
+ current_phase: 'ultragoal',
1297
+ });
1298
+ assert.equal(response.isError, true);
1299
+ assert.match(String(response.payload.error || ''), /tracker-backed native architect and critic lanes/i);
1300
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1301
+ assert.equal(state.current_phase, 'ralplan');
1302
+ });
1303
+ }
1304
+ finally {
1305
+ await rm(wd, { recursive: true, force: true });
1306
+ }
1307
+ });
1308
+ it('explains when native ralplan reviews are not present in subagent tracking', async () => {
1309
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-native-missing-tracker-'));
1310
+ try {
1311
+ await withOmxRootEnv(wd, async () => {
1312
+ const sessionId = 'sess-autopilot-ralplan-native-missing-tracker';
1313
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1314
+ await mkdir(sessionDir, { recursive: true });
1315
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1316
+ active: true,
1317
+ mode: 'autopilot',
1318
+ current_phase: 'ralplan',
1319
+ state: {
1320
+ handoff_artifacts: {
1321
+ ralplan: {
1322
+ plan_path: '.omx/plans/prd.md',
1323
+ test_spec_path: '.omx/plans/test-spec.md',
1324
+ },
1325
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
1326
+ },
1327
+ },
1328
+ }, null, 2));
1329
+ const response = await executeStateOperation('state_write', {
1330
+ workingDirectory: wd,
1331
+ session_id: sessionId,
1332
+ mode: 'autopilot',
1333
+ active: true,
1334
+ current_phase: 'ultragoal',
1335
+ });
1336
+ assert.equal(response.isError, true);
1337
+ const error = String(response.payload.error || '');
1338
+ assert.match(error, /subagent-tracking\.json/);
1339
+ assert.match(error, /only reviews recorded in OMX subagent-tracking\.json count as native lanes/i);
1340
+ });
1341
+ }
1342
+ finally {
1343
+ await rm(wd, { recursive: true, force: true });
1344
+ }
1345
+ });
1346
+ it('denies Autopilot ralplan to ultragoal self-write when native reviews reuse one subagent thread', async () => {
1347
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-same-thread-deny-'));
1348
+ try {
1349
+ await withOmxRootEnv(wd, async () => {
1350
+ const sessionId = 'sess-autopilot-ralplan-same-thread-deny';
1351
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1352
+ await mkdir(sessionDir, { recursive: true });
1353
+ await writeNativeSubagentTracking(wd, sessionId);
1354
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1355
+ active: true,
1356
+ mode: 'autopilot',
1357
+ current_phase: 'ralplan',
1358
+ state: {
1359
+ handoff_artifacts: {
1360
+ ralplan: {
1361
+ plan_path: '.omx/plans/prd.md',
1362
+ test_spec_path: '.omx/plans/test-spec.md',
1363
+ },
1364
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent', {
1365
+ critic: 'thread-architect',
1366
+ }),
1367
+ },
1368
+ },
1369
+ }, null, 2));
1370
+ const response = await executeStateOperation('state_write', {
1371
+ workingDirectory: wd,
1372
+ session_id: sessionId,
1373
+ mode: 'autopilot',
1374
+ active: true,
1375
+ current_phase: 'ultragoal',
1376
+ });
1377
+ assert.equal(response.isError, true);
1378
+ assert.match(String(response.payload.error || ''), /tracker-backed native architect and critic lanes/i);
1379
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1380
+ assert.equal(state.current_phase, 'ralplan');
1381
+ });
1382
+ }
1383
+ finally {
1384
+ await rm(wd, { recursive: true, force: true });
1385
+ }
1386
+ });
1387
+ it('denies legacy Autopilot planning to ultragoal without ralplan consensus evidence', async () => {
1388
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-legacy-planning-gate-'));
1389
+ try {
1390
+ await withOmxRootEnv(wd, async () => {
1391
+ const sessionId = 'sess-autopilot-legacy-planning-gate';
1392
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1393
+ await mkdir(sessionDir, { recursive: true });
1394
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1395
+ active: true,
1396
+ mode: 'autopilot',
1397
+ current_phase: 'planning',
1398
+ }, null, 2));
1399
+ const response = await executeStateOperation('state_write', {
1400
+ workingDirectory: wd,
1401
+ session_id: sessionId,
1402
+ mode: 'autopilot',
1403
+ active: true,
1404
+ current_phase: 'ultragoal',
1405
+ });
1406
+ assert.equal(response.isError, true);
1407
+ assert.match(String(response.payload.error || ''), /ralplan consensus/i);
1408
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1409
+ assert.equal(state.current_phase, 'planning');
1410
+ });
1411
+ }
1412
+ finally {
1413
+ await rm(wd, { recursive: true, force: true });
1414
+ }
1415
+ });
1416
+ it('allows Autopilot ralplan to ultragoal self-write with tracker-backed native consensus evidence', async () => {
1417
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-native-allow-'));
1418
+ try {
1419
+ await withOmxRootEnv(wd, async () => {
1420
+ const sessionId = 'sess-autopilot-ralplan-native-allow';
1421
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1422
+ await mkdir(sessionDir, { recursive: true });
1423
+ await writeNativeSubagentTracking(wd, sessionId);
1424
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
1425
+ active: true,
1426
+ mode: 'autopilot',
1427
+ current_phase: 'ralplan',
1428
+ state: {
1429
+ handoff_artifacts: {
1430
+ ralplan: {
1431
+ plan_path: '.omx/plans/prd.md',
1432
+ test_spec_path: '.omx/plans/test-spec.md',
1433
+ },
1434
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
1435
+ },
1436
+ },
1437
+ }, null, 2));
1438
+ const response = await executeStateOperation('state_write', {
1439
+ workingDirectory: wd,
1440
+ session_id: sessionId,
1441
+ mode: 'autopilot',
1442
+ active: true,
1443
+ current_phase: 'ultragoal',
1444
+ });
1445
+ assert.equal(response.isError, undefined);
1446
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
1447
+ assert.equal(state.current_phase, 'ultragoal');
1448
+ });
1449
+ }
1450
+ finally {
1451
+ await rm(wd, { recursive: true, force: true });
1452
+ }
1453
+ });
1454
+ it('fails closed when canonical deep-interview is active but mode state is missing', async () => {
1455
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-missing-deep-interview-state-'));
1456
+ try {
1457
+ await withOmxRootEnv(wd, async () => {
1458
+ const sessionId = 'sess-missing-deep-interview-state';
1459
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
1460
+ await mkdir(sessionDir, { recursive: true });
1461
+ await writeFile(join(sessionDir, 'skill-active-state.json'), JSON.stringify({
1462
+ version: 1,
1463
+ active: true,
1464
+ skill: 'deep-interview',
1465
+ session_id: sessionId,
1466
+ active_skills: [{
1467
+ skill: 'deep-interview',
1468
+ active: true,
1469
+ phase: 'intent-first',
1470
+ session_id: sessionId,
1471
+ }],
1472
+ }, null, 2));
1473
+ const response = await executeStateOperation('state_write', {
1474
+ workingDirectory: wd,
1475
+ session_id: sessionId,
1476
+ mode: 'ralplan',
1477
+ active: true,
1478
+ current_phase: 'planning',
1479
+ });
1480
+ assert.equal(response.isError, true);
1481
+ assert.match(String(response.payload.error || ''), /missing deep-interview completion\/skip gate/i);
1482
+ assert.equal(existsSync(join(sessionDir, 'ralplan-state.json')), false);
1483
+ const canonical = JSON.parse(await readFile(join(sessionDir, 'skill-active-state.json'), 'utf-8'));
1484
+ assert.equal(canonical.active_skills?.[0]?.skill, 'deep-interview');
1485
+ assert.equal(canonical.active_skills?.[0]?.active, true);
1486
+ });
1487
+ }
1488
+ finally {
1489
+ await rm(wd, { recursive: true, force: true });
1490
+ }
1491
+ });
481
1492
  it('does not auto-complete existing workflow state when tracked write validation fails', async () => {
482
1493
  const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-validate-before-transition-'));
483
1494
  try {