oh-my-codex 0.18.1 → 0.18.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 (310) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +4 -2
  4. package/dist/agents/__tests__/definitions.test.js +23 -0
  5. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  6. package/dist/agents/__tests__/native-config.test.js +20 -0
  7. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  8. package/dist/agents/definitions.d.ts.map +1 -1
  9. package/dist/agents/definitions.js +40 -0
  10. package/dist/agents/definitions.js.map +1 -1
  11. package/dist/agents/native-config.d.ts +1 -0
  12. package/dist/agents/native-config.d.ts.map +1 -1
  13. package/dist/agents/native-config.js +4 -0
  14. package/dist/agents/native-config.js.map +1 -1
  15. package/dist/auth/__tests__/config-sessions.test.d.ts +2 -0
  16. package/dist/auth/__tests__/config-sessions.test.d.ts.map +1 -0
  17. package/dist/auth/__tests__/config-sessions.test.js +48 -0
  18. package/dist/auth/__tests__/config-sessions.test.js.map +1 -0
  19. package/dist/auth/__tests__/quota-rotation.test.d.ts +2 -0
  20. package/dist/auth/__tests__/quota-rotation.test.d.ts.map +1 -0
  21. package/dist/auth/__tests__/quota-rotation.test.js +33 -0
  22. package/dist/auth/__tests__/quota-rotation.test.js.map +1 -0
  23. package/dist/auth/__tests__/redact.test.d.ts +2 -0
  24. package/dist/auth/__tests__/redact.test.d.ts.map +1 -0
  25. package/dist/auth/__tests__/redact.test.js +20 -0
  26. package/dist/auth/__tests__/redact.test.js.map +1 -0
  27. package/dist/auth/__tests__/storage.test.d.ts +2 -0
  28. package/dist/auth/__tests__/storage.test.d.ts.map +1 -0
  29. package/dist/auth/__tests__/storage.test.js +108 -0
  30. package/dist/auth/__tests__/storage.test.js.map +1 -0
  31. package/dist/auth/config.d.ts +9 -0
  32. package/dist/auth/config.d.ts.map +1 -0
  33. package/dist/auth/config.js +77 -0
  34. package/dist/auth/config.js.map +1 -0
  35. package/dist/auth/hotswap.d.ts +36 -0
  36. package/dist/auth/hotswap.d.ts.map +1 -0
  37. package/dist/auth/hotswap.js +159 -0
  38. package/dist/auth/hotswap.js.map +1 -0
  39. package/dist/auth/index.d.ts +8 -0
  40. package/dist/auth/index.d.ts.map +1 -0
  41. package/dist/auth/index.js +8 -0
  42. package/dist/auth/index.js.map +1 -0
  43. package/dist/auth/paths.d.ts +12 -0
  44. package/dist/auth/paths.d.ts.map +1 -0
  45. package/dist/auth/paths.js +78 -0
  46. package/dist/auth/paths.js.map +1 -0
  47. package/dist/auth/quota-detector.d.ts +10 -0
  48. package/dist/auth/quota-detector.d.ts.map +1 -0
  49. package/dist/auth/quota-detector.js +40 -0
  50. package/dist/auth/quota-detector.js.map +1 -0
  51. package/dist/auth/redact.d.ts +2 -0
  52. package/dist/auth/redact.d.ts.map +1 -0
  53. package/dist/auth/redact.js +26 -0
  54. package/dist/auth/redact.js.map +1 -0
  55. package/dist/auth/rotation.d.ts +9 -0
  56. package/dist/auth/rotation.d.ts.map +1 -0
  57. package/dist/auth/rotation.js +26 -0
  58. package/dist/auth/rotation.js.map +1 -0
  59. package/dist/auth/sessions.d.ts +15 -0
  60. package/dist/auth/sessions.d.ts.map +1 -0
  61. package/dist/auth/sessions.js +62 -0
  62. package/dist/auth/sessions.js.map +1 -0
  63. package/dist/auth/storage.d.ts +27 -0
  64. package/dist/auth/storage.d.ts.map +1 -0
  65. package/dist/auth/storage.js +111 -0
  66. package/dist/auth/storage.js.map +1 -0
  67. package/dist/catalog/__tests__/generator.test.js +4 -0
  68. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  69. package/dist/cli/__tests__/auth.test.d.ts +2 -0
  70. package/dist/cli/__tests__/auth.test.d.ts.map +1 -0
  71. package/dist/cli/__tests__/auth.test.js +168 -0
  72. package/dist/cli/__tests__/auth.test.js.map +1 -0
  73. package/dist/cli/__tests__/doctor-warning-copy.test.js +112 -5
  74. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  75. package/dist/cli/__tests__/explore.test.js +20 -0
  76. package/dist/cli/__tests__/explore.test.js.map +1 -1
  77. package/dist/cli/__tests__/index.test.js +171 -21
  78. package/dist/cli/__tests__/index.test.js.map +1 -1
  79. package/dist/cli/__tests__/launch-fallback.test.js +51 -3
  80. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  81. package/dist/cli/__tests__/nested-help-routing.test.js +1 -0
  82. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  83. package/dist/cli/__tests__/question.test.js +2 -2
  84. package/dist/cli/__tests__/question.test.js.map +1 -1
  85. package/dist/cli/__tests__/setup-agents-overwrite.test.js +30 -1
  86. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  87. package/dist/cli/__tests__/setup-install-mode.test.js +47 -0
  88. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  89. package/dist/cli/auth.d.ts +4 -0
  90. package/dist/cli/auth.d.ts.map +1 -0
  91. package/dist/cli/auth.js +89 -0
  92. package/dist/cli/auth.js.map +1 -0
  93. package/dist/cli/doctor.d.ts.map +1 -1
  94. package/dist/cli/doctor.js +190 -7
  95. package/dist/cli/doctor.js.map +1 -1
  96. package/dist/cli/explore.d.ts.map +1 -1
  97. package/dist/cli/explore.js +12 -0
  98. package/dist/cli/explore.js.map +1 -1
  99. package/dist/cli/index.d.ts +27 -3
  100. package/dist/cli/index.d.ts.map +1 -1
  101. package/dist/cli/index.js +245 -47
  102. package/dist/cli/index.js.map +1 -1
  103. package/dist/cli/setup.d.ts.map +1 -1
  104. package/dist/cli/setup.js +11 -3
  105. package/dist/cli/setup.js.map +1 -1
  106. package/dist/config/__tests__/codex-hooks.test.js +3 -3
  107. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  108. package/dist/config/__tests__/deep-interview.test.d.ts +2 -0
  109. package/dist/config/__tests__/deep-interview.test.d.ts.map +1 -0
  110. package/dist/config/__tests__/deep-interview.test.js +239 -0
  111. package/dist/config/__tests__/deep-interview.test.js.map +1 -0
  112. package/dist/config/__tests__/generator-idempotent.test.js +123 -0
  113. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  114. package/dist/config/codex-hooks.d.ts +1 -0
  115. package/dist/config/codex-hooks.d.ts.map +1 -1
  116. package/dist/config/codex-hooks.js +2 -4
  117. package/dist/config/codex-hooks.js.map +1 -1
  118. package/dist/config/deep-interview.d.ts +22 -0
  119. package/dist/config/deep-interview.d.ts.map +1 -0
  120. package/dist/config/deep-interview.js +151 -0
  121. package/dist/config/deep-interview.js.map +1 -0
  122. package/dist/config/generator.d.ts +19 -2
  123. package/dist/config/generator.d.ts.map +1 -1
  124. package/dist/config/generator.js +198 -29
  125. package/dist/config/generator.js.map +1 -1
  126. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +21 -0
  127. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  128. package/dist/goal-workflows/codex-goal-snapshot.d.ts +3 -0
  129. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  130. package/dist/goal-workflows/codex-goal-snapshot.js +45 -2
  131. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  132. package/dist/hooks/__tests__/agents-overlay.test.js +2 -0
  133. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  134. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +17 -0
  135. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  136. package/dist/hooks/__tests__/explore-routing.test.js +1 -0
  137. package/dist/hooks/__tests__/explore-routing.test.js.map +1 -1
  138. package/dist/hooks/__tests__/keyword-detector.test.js +471 -15
  139. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  140. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +2 -0
  141. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +1 -0
  142. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +320 -0
  143. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -0
  144. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +12 -0
  145. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  146. package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts +2 -0
  147. package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts.map +1 -0
  148. package/dist/hooks/__tests__/research-workflow-boundaries.test.js +35 -0
  149. package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -0
  150. package/dist/hooks/deep-interview-config-instruction.d.ts +3 -0
  151. package/dist/hooks/deep-interview-config-instruction.d.ts.map +1 -0
  152. package/dist/hooks/deep-interview-config-instruction.js +47 -0
  153. package/dist/hooks/deep-interview-config-instruction.js.map +1 -0
  154. package/dist/hooks/explore-routing.d.ts.map +1 -1
  155. package/dist/hooks/explore-routing.js +1 -0
  156. package/dist/hooks/explore-routing.js.map +1 -1
  157. package/dist/hooks/keyword-detector.d.ts +6 -1
  158. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  159. package/dist/hooks/keyword-detector.js +80 -14
  160. package/dist/hooks/keyword-detector.js.map +1 -1
  161. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  162. package/dist/hooks/keyword-registry.js +1 -0
  163. package/dist/hooks/keyword-registry.js.map +1 -1
  164. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  165. package/dist/hooks/prompt-guidance-contract.js +11 -0
  166. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  167. package/dist/hud/__tests__/hud-tmux-injection.test.js +22 -0
  168. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  169. package/dist/hud/__tests__/reconcile.test.js +213 -17
  170. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  171. package/dist/hud/__tests__/render.test.js +84 -0
  172. package/dist/hud/__tests__/render.test.js.map +1 -1
  173. package/dist/hud/__tests__/state.test.js +51 -1
  174. package/dist/hud/__tests__/state.test.js.map +1 -1
  175. package/dist/hud/__tests__/tmux.test.js +171 -23
  176. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  177. package/dist/hud/index.d.ts +1 -1
  178. package/dist/hud/index.d.ts.map +1 -1
  179. package/dist/hud/index.js +8 -3
  180. package/dist/hud/index.js.map +1 -1
  181. package/dist/hud/reconcile.d.ts +1 -1
  182. package/dist/hud/reconcile.d.ts.map +1 -1
  183. package/dist/hud/reconcile.js +14 -3
  184. package/dist/hud/reconcile.js.map +1 -1
  185. package/dist/hud/render.d.ts.map +1 -1
  186. package/dist/hud/render.js +26 -0
  187. package/dist/hud/render.js.map +1 -1
  188. package/dist/hud/state.d.ts +2 -1
  189. package/dist/hud/state.d.ts.map +1 -1
  190. package/dist/hud/state.js +62 -1
  191. package/dist/hud/state.js.map +1 -1
  192. package/dist/hud/tmux.d.ts +17 -3
  193. package/dist/hud/tmux.d.ts.map +1 -1
  194. package/dist/hud/tmux.js +96 -10
  195. package/dist/hud/tmux.js.map +1 -1
  196. package/dist/hud/types.d.ts +22 -0
  197. package/dist/hud/types.d.ts.map +1 -1
  198. package/dist/hud/types.js.map +1 -1
  199. package/dist/pipeline/__tests__/orchestrator.test.js +63 -1
  200. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  201. package/dist/pipeline/__tests__/stages.test.js +410 -4
  202. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  203. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  204. package/dist/pipeline/orchestrator.js +29 -2
  205. package/dist/pipeline/orchestrator.js.map +1 -1
  206. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  207. package/dist/pipeline/stages/ralplan.js +41 -6
  208. package/dist/pipeline/stages/ralplan.js.map +1 -1
  209. package/dist/question/__tests__/ui.test.js +43 -10
  210. package/dist/question/__tests__/ui.test.js.map +1 -1
  211. package/dist/question/deep-interview.d.ts +2 -0
  212. package/dist/question/deep-interview.d.ts.map +1 -1
  213. package/dist/question/deep-interview.js.map +1 -1
  214. package/dist/question/ui.d.ts +12 -0
  215. package/dist/question/ui.d.ts.map +1 -1
  216. package/dist/question/ui.js +83 -46
  217. package/dist/question/ui.js.map +1 -1
  218. package/dist/ralplan/__tests__/runtime.test.js +200 -10
  219. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  220. package/dist/ralplan/consensus-gate.d.ts +23 -0
  221. package/dist/ralplan/consensus-gate.d.ts.map +1 -0
  222. package/dist/ralplan/consensus-gate.js +212 -0
  223. package/dist/ralplan/consensus-gate.js.map +1 -0
  224. package/dist/ralplan/runtime.d.ts +25 -0
  225. package/dist/ralplan/runtime.d.ts.map +1 -1
  226. package/dist/ralplan/runtime.js +144 -8
  227. package/dist/ralplan/runtime.js.map +1 -1
  228. package/dist/scripts/__tests__/codex-native-hook.test.js +1034 -28
  229. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  230. package/dist/scripts/__tests__/docs-site-contract.test.d.ts +2 -0
  231. package/dist/scripts/__tests__/docs-site-contract.test.d.ts.map +1 -0
  232. package/dist/scripts/__tests__/docs-site-contract.test.js +42 -0
  233. package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -0
  234. package/dist/scripts/__tests__/notify-dispatcher.test.js +115 -2
  235. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  236. package/dist/scripts/__tests__/run-test-files.test.js +57 -0
  237. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  238. package/dist/scripts/__tests__/verify-native-agents.test.js +2 -2
  239. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  240. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  241. package/dist/scripts/codex-native-hook.js +238 -36
  242. package/dist/scripts/codex-native-hook.js.map +1 -1
  243. package/dist/scripts/notify-dispatcher.js +188 -4
  244. package/dist/scripts/notify-dispatcher.js.map +1 -1
  245. package/dist/scripts/run-test-files.js +13 -0
  246. package/dist/scripts/run-test-files.js.map +1 -1
  247. package/dist/state/__tests__/planning-gate.test.d.ts +2 -0
  248. package/dist/state/__tests__/planning-gate.test.d.ts.map +1 -0
  249. package/dist/state/__tests__/planning-gate.test.js +219 -0
  250. package/dist/state/__tests__/planning-gate.test.js.map +1 -0
  251. package/dist/state/__tests__/workflow-transition.test.js +6 -0
  252. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  253. package/dist/state/workflow-transition.d.ts +24 -1
  254. package/dist/state/workflow-transition.d.ts.map +1 -1
  255. package/dist/state/workflow-transition.js +70 -0
  256. package/dist/state/workflow-transition.js.map +1 -1
  257. package/dist/subagents/tracker.d.ts.map +1 -1
  258. package/dist/subagents/tracker.js +4 -3
  259. package/dist/subagents/tracker.js.map +1 -1
  260. package/dist/team/__tests__/runtime.test.js +36 -44
  261. package/dist/team/__tests__/runtime.test.js.map +1 -1
  262. package/dist/team/__tests__/tmux-session.test.js +144 -18
  263. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  264. package/dist/team/runtime.d.ts.map +1 -1
  265. package/dist/team/runtime.js +10 -20
  266. package/dist/team/runtime.js.map +1 -1
  267. package/dist/team/tmux-session.d.ts.map +1 -1
  268. package/dist/team/tmux-session.js +22 -6
  269. package/dist/team/tmux-session.js.map +1 -1
  270. package/dist/ultragoal/__tests__/artifacts.test.js +50 -0
  271. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  272. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  273. package/dist/ultragoal/artifacts.js +28 -2
  274. package/dist/ultragoal/artifacts.js.map +1 -1
  275. package/package.json +1 -1
  276. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  277. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +16 -4
  278. package/plugins/oh-my-codex/skills/autoresearch/SKILL.md +4 -0
  279. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +1 -1
  280. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
  281. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +10 -0
  282. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +1 -1
  283. package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
  284. package/plugins/oh-my-codex/skills/prometheus-strict/README.md +35 -0
  285. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +219 -0
  286. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +24 -5
  287. package/prompts/prometheus-strict-metis.md +274 -0
  288. package/prompts/prometheus-strict-momus.md +82 -0
  289. package/prompts/prometheus-strict-oracle.md +107 -0
  290. package/prompts/researcher.md +22 -3
  291. package/prompts/scholastic.md +11 -0
  292. package/skills/autopilot/SKILL.md +16 -4
  293. package/skills/autoresearch/SKILL.md +4 -0
  294. package/skills/autoresearch-goal/SKILL.md +1 -1
  295. package/skills/best-practice-research/SKILL.md +1 -1
  296. package/skills/deep-interview/SKILL.md +10 -0
  297. package/skills/pipeline/SKILL.md +1 -1
  298. package/skills/plan/SKILL.md +1 -1
  299. package/skills/prometheus-strict/README.md +35 -0
  300. package/skills/prometheus-strict/SKILL.md +219 -0
  301. package/skills/ralplan/SKILL.md +24 -5
  302. package/src/scripts/__tests__/codex-native-hook.test.ts +1307 -61
  303. package/src/scripts/__tests__/docs-site-contract.test.ts +47 -0
  304. package/src/scripts/__tests__/notify-dispatcher.test.ts +132 -3
  305. package/src/scripts/__tests__/run-test-files.test.ts +67 -0
  306. package/src/scripts/__tests__/verify-native-agents.test.ts +2 -2
  307. package/src/scripts/codex-native-hook.ts +260 -31
  308. package/src/scripts/notify-dispatcher.ts +202 -4
  309. package/src/scripts/run-test-files.ts +13 -0
  310. package/templates/catalog-manifest.json +27 -0
@@ -8,6 +8,21 @@ import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, DEEP_INTER
8
8
  import { SKILL_ACTIVE_STATE_FILE } from '../../state/skill-active.js';
9
9
  import { isUnderspecifiedForExecution, applyRalplanGate } from '../keyword-detector.js';
10
10
  import { KEYWORD_TRIGGER_DEFINITIONS } from '../keyword-registry.js';
11
+ async function withIsolatedHome(prefix, run) {
12
+ const homeDir = await mkdtemp(join(tmpdir(), `omx-keyword-home-${prefix}-`));
13
+ const previousHome = process.env.HOME;
14
+ try {
15
+ process.env.HOME = homeDir;
16
+ return await run(homeDir);
17
+ }
18
+ finally {
19
+ if (typeof previousHome === 'string')
20
+ process.env.HOME = previousHome;
21
+ else
22
+ delete process.env.HOME;
23
+ await rm(homeDir, { recursive: true, force: true });
24
+ }
25
+ }
11
26
  describe('keyword detector team compatibility', () => {
12
27
  it('keeps explicit $skill order in detectKeywords results (left-to-right)', () => {
13
28
  const matches = detectKeywords('$analyze $ultraqa $code-review now');
@@ -194,6 +209,11 @@ describe('keyword detector team compatibility', () => {
194
209
  assert.equal(detectPrimaryKeyword('cleanup stale deep-interview state after session clear'), null);
195
210
  assert.equal(detectPrimaryKeyword('remove the stale deep interview lock from .omx/state'), null);
196
211
  });
212
+ it('does not trigger deep-interview from casual discussion mentions', () => {
213
+ assert.equal(detectPrimaryKeyword('the deep interview report is useful context for the next plan'), null);
214
+ assert.equal(detectPrimaryKeyword('we already did a deep interview and should not reactivate it'), null);
215
+ assert.equal(detectPrimaryKeyword('this interview transcript says implementation is ready'), null);
216
+ });
197
217
  it('maps "gather requirements" to deep-interview skill', () => {
198
218
  const match = detectPrimaryKeyword('let us gather requirements first');
199
219
  assert.ok(match);
@@ -285,6 +305,13 @@ describe('explicit skill-name invocation requirement', () => {
285
305
  it('does not trigger ralplan from bare skill-name usage', () => {
286
306
  assert.equal(detectPrimaryKeyword('please do ralplan first'), null);
287
307
  });
308
+ it('detects explicit prometheus-strict invocation only', () => {
309
+ const match = detectPrimaryKeyword('please run $prometheus-strict before implementation');
310
+ assert.ok(match);
311
+ assert.equal(match.skill, 'prometheus-strict');
312
+ assert.equal(match.keyword.toLowerCase(), '$prometheus-strict');
313
+ assert.equal(detectPrimaryKeyword('please use prometheus-strict planning here'), null);
314
+ });
288
315
  });
289
316
  describe('keyword registry coverage', () => {
290
317
  it('includes key team aliases in runtime keyword registry', () => {
@@ -304,6 +331,7 @@ describe('keyword registry coverage', () => {
304
331
  assert.ok(registryKeywords.has('wiki lint'));
305
332
  assert.ok(registryKeywords.has('$autoresearch'));
306
333
  assert.ok(registryKeywords.has('$ultragoal'));
334
+ assert.ok(registryKeywords.has('$prometheus-strict'));
307
335
  assert.ok(registryKeywords.has('ultragoal'));
308
336
  });
309
337
  });
@@ -398,7 +426,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
398
426
  await rm(root, { recursive: true, force: true });
399
427
  }
400
428
  });
401
- it('writes skill-active-state.json with ralplan phase when autopilot keyword activates', async () => {
429
+ it('writes skill-active-state.json with deep-interview phase when autopilot keyword activates', async () => {
402
430
  const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-'));
403
431
  const stateDir = join(cwd, '.omx', 'state');
404
432
  try {
@@ -413,11 +441,11 @@ describe('keyword detector skill-active-state lifecycle', () => {
413
441
  });
414
442
  assert.ok(result);
415
443
  assert.equal(result.skill, 'autopilot');
416
- assert.equal(result.phase, 'ralplan');
444
+ assert.equal(result.phase, 'deep-interview');
417
445
  assert.equal(result.active, true);
418
446
  assert.deepEqual(result.active_skills, [{
419
447
  skill: 'autopilot',
420
- phase: 'ralplan',
448
+ phase: 'deep-interview',
421
449
  active: true,
422
450
  activated_at: '2026-02-25T00:00:00.000Z',
423
451
  updated_at: '2026-02-25T00:00:00.000Z',
@@ -439,7 +467,27 @@ describe('keyword detector skill-active-state lifecycle', () => {
439
467
  assert.equal(modeState.review_cycle, 0);
440
468
  assert.equal(modeState.max_iterations, 10);
441
469
  assert.deepEqual(modeState.state.phase_cycle, ['deep-interview', 'ralplan', 'ultragoal', 'code-review', 'ultraqa']);
442
- assert.deepEqual(modeState.state.handoff_artifacts, { deep_interview: null, ralplan: null, ultragoal: null, code_review: null, ultraqa: null });
470
+ assert.deepEqual(modeState.state.deep_interview_gate, {
471
+ status: 'required',
472
+ skip_reason: null,
473
+ rationale: 'Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.',
474
+ });
475
+ assert.deepEqual(modeState.state.handoff_artifacts, {
476
+ deep_interview: null,
477
+ ralplan: null,
478
+ ralplan_consensus_gate: {
479
+ required: true,
480
+ sequence: ['architect-review', 'critic-review'],
481
+ planning_artifacts_are_not_consensus: true,
482
+ required_review_roles: ['architect', 'critic'],
483
+ ralplan_architect_review: null,
484
+ ralplan_critic_review: null,
485
+ complete: false,
486
+ },
487
+ ultragoal: null,
488
+ code_review: null,
489
+ ultraqa: null,
490
+ });
443
491
  assert.equal(modeState.state.review_verdict, null);
444
492
  assert.equal(modeState.state.qa_verdict, null);
445
493
  assert.equal(modeState.state.return_to_ralplan_reason, null);
@@ -792,18 +840,56 @@ describe('keyword detector skill-active-state lifecycle', () => {
792
840
  }, null, 2));
793
841
  const result = await recordSkillActivation({
794
842
  stateDir,
795
- text: '$ralplan implement the approved contract',
843
+ text: '$ultragoal turn the clarified spec into goals',
796
844
  sessionId: 'sess-handoff',
797
845
  nowIso: '2026-04-10T00:00:00.000Z',
798
846
  });
799
847
  assert.equal(result?.transition_error, undefined);
800
- assert.equal(result?.transition_message, 'mode transiting: deep-interview -> ralplan');
848
+ assert.equal(result?.skill, 'ultragoal');
849
+ assert.equal(result?.initialized_mode, 'ultragoal');
850
+ assert.equal(result?.initialized_state_path, '.omx/state/sessions/sess-handoff/ultragoal-state.json');
851
+ assert.equal(result?.transition_message, 'mode transiting: deep-interview -> ultragoal');
801
852
  const completed = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-handoff', 'deep-interview-state.json'), 'utf-8'));
802
853
  assert.equal(completed.active, false);
803
854
  assert.equal(completed.current_phase, 'completed');
804
855
  assert.equal(completed.question_enforcement?.status, 'cleared');
805
856
  assert.equal(completed.question_enforcement?.clear_reason, 'handoff');
806
857
  assert.ok(completed.question_enforcement?.cleared_at);
858
+ const ultragoal = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-handoff', 'ultragoal-state.json'), 'utf-8'));
859
+ assert.equal(ultragoal.active, true);
860
+ assert.equal(ultragoal.mode, 'ultragoal');
861
+ assert.equal(ultragoal.current_phase, 'planning');
862
+ }
863
+ finally {
864
+ await rm(cwd, { recursive: true, force: true });
865
+ }
866
+ });
867
+ it('keeps ralplan as an allowlisted deep-interview forward handoff', async () => {
868
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ralplan-handoff-'));
869
+ const stateDir = join(cwd, '.omx', 'state');
870
+ try {
871
+ await mkdir(join(stateDir, 'sessions', 'sess-ralplan-handoff'), { recursive: true });
872
+ await writeFile(join(stateDir, 'sessions', 'sess-ralplan-handoff', SKILL_ACTIVE_STATE_FILE), JSON.stringify({
873
+ version: 1,
874
+ active: true,
875
+ skill: 'deep-interview',
876
+ phase: 'planning',
877
+ session_id: 'sess-ralplan-handoff',
878
+ active_skills: [{ skill: 'deep-interview', phase: 'planning', active: true, session_id: 'sess-ralplan-handoff' }],
879
+ }, null, 2));
880
+ await writeFile(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'deep-interview-state.json'), JSON.stringify({ active: true, mode: 'deep-interview', current_phase: 'intent-first' }, null, 2));
881
+ const result = await recordSkillActivation({
882
+ stateDir,
883
+ text: '$ralplan implement the approved contract',
884
+ sessionId: 'sess-ralplan-handoff',
885
+ nowIso: '2026-04-10T00:00:00.000Z',
886
+ });
887
+ assert.equal(result?.transition_error, undefined);
888
+ assert.equal(result?.skill, 'ralplan');
889
+ assert.equal(result?.transition_message, 'mode transiting: deep-interview -> ralplan');
890
+ const completed = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralplan-handoff', 'deep-interview-state.json'), 'utf-8'));
891
+ assert.equal(completed.active, false);
892
+ assert.equal(completed.current_phase, 'completed');
807
893
  }
808
894
  finally {
809
895
  await rm(cwd, { recursive: true, force: true });
@@ -1008,6 +1094,292 @@ describe('keyword detector skill-active-state lifecycle', () => {
1008
1094
  await rm(cwd, { recursive: true, force: true });
1009
1095
  }
1010
1096
  });
1097
+ it('persists repo-local deep-interview config values into activation and mode state', async () => {
1098
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-config-'));
1099
+ const stateDir = join(cwd, '.omx', 'state');
1100
+ try {
1101
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1102
+ await mkdir(stateDir, { recursive: true });
1103
+ await writeFile(join(cwd, '.omx', 'config.toml'), `[omx.deepInterview]
1104
+ defaultProfile = "standard"
1105
+ standardThreshold = 0.05
1106
+ standardMaxRounds = 15
1107
+ enableChallengeModes = false
1108
+ `);
1109
+ const result = await recordSkillActivation({
1110
+ stateDir,
1111
+ sourceCwd: cwd,
1112
+ text: '$deep-interview clarify runtime config',
1113
+ sessionId: 'sess-deep-interview-config',
1114
+ nowIso: '2026-02-25T00:00:00.000Z',
1115
+ });
1116
+ assert.ok(result);
1117
+ assert.equal(result.skill, 'deep-interview');
1118
+ assert.equal(result.deep_interview_config?.profile, 'standard');
1119
+ assert.equal(result.deep_interview_config?.threshold, 0.05);
1120
+ assert.equal(result.deep_interview_config?.maxRounds, 15);
1121
+ assert.equal(result.initialized_state_path, '.omx/state/sessions/sess-deep-interview-config/deep-interview-state.json');
1122
+ const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-deep-interview-config', DEEP_INTERVIEW_STATE_FILE), 'utf-8'));
1123
+ assert.equal(modeState.profile, 'standard');
1124
+ assert.equal(modeState.threshold, 0.05);
1125
+ assert.equal(modeState.max_rounds, 15);
1126
+ assert.equal(modeState.enable_challenge_modes, false);
1127
+ assert.equal(modeState.config_source, join(cwd, '.omx', 'config.toml'));
1128
+ assert.equal(modeState.deep_interview_config?.sourcePath, join(cwd, '.omx', 'config.toml'));
1129
+ }
1130
+ finally {
1131
+ await rm(cwd, { recursive: true, force: true });
1132
+ }
1133
+ });
1134
+ it('persists deep-interview config when mixed workflow prompts defer execution modes', async () => {
1135
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-config-mixed-'));
1136
+ const stateDir = join(cwd, '.omx', 'state');
1137
+ const sessionId = 'sess-deep-interview-config-mixed';
1138
+ try {
1139
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1140
+ await mkdir(stateDir, { recursive: true });
1141
+ await writeFile(join(cwd, '.omx', 'config.toml'), `[omx.deepInterview]
1142
+ defaultProfile = "deep"
1143
+ deepThreshold = 0.13
1144
+ deepMaxRounds = 21
1145
+ enableChallengeModes = false
1146
+ `);
1147
+ const result = await recordSkillActivation({
1148
+ stateDir,
1149
+ sourceCwd: cwd,
1150
+ text: '$autopilot $deep-interview prove mixed workflow config',
1151
+ sessionId,
1152
+ nowIso: '2026-02-25T00:00:00.000Z',
1153
+ });
1154
+ assert.ok(result);
1155
+ assert.equal(result.skill, 'deep-interview');
1156
+ assert.deepEqual(result.deferred_skills, ['autopilot']);
1157
+ assert.equal(result.input_lock?.active, true);
1158
+ assert.equal(result.deep_interview_config?.profile, 'deep');
1159
+ assert.equal(result.deep_interview_config?.threshold, 0.13);
1160
+ assert.equal(result.deep_interview_config?.maxRounds, 21);
1161
+ assert.equal(result.deep_interview_config?.enableChallengeModes, false);
1162
+ const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, DEEP_INTERVIEW_STATE_FILE), 'utf-8'));
1163
+ assert.equal(modeState.profile, 'deep');
1164
+ assert.equal(modeState.threshold, 0.13);
1165
+ assert.equal(modeState.max_rounds, 21);
1166
+ assert.equal(modeState.enable_challenge_modes, false);
1167
+ assert.equal(modeState.config_source, join(cwd, '.omx', 'config.toml'));
1168
+ assert.equal(modeState.deep_interview_config?.profile, 'deep');
1169
+ assert.equal(modeState.input_lock?.active, true);
1170
+ }
1171
+ finally {
1172
+ await rm(cwd, { recursive: true, force: true });
1173
+ }
1174
+ });
1175
+ it('shows before-after state change when deep-interview config is added at runtime', async () => {
1176
+ await withIsolatedHome('deep-interview-config-before-after', async () => {
1177
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-config-before-after-'));
1178
+ const stateDir = join(cwd, '.omx', 'state');
1179
+ const sessionId = 'sess-deep-interview-config-before-after';
1180
+ const statePath = join(stateDir, 'sessions', sessionId, DEEP_INTERVIEW_STATE_FILE);
1181
+ try {
1182
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1183
+ await mkdir(stateDir, { recursive: true });
1184
+ const before = await recordSkillActivation({
1185
+ stateDir,
1186
+ sourceCwd: cwd,
1187
+ text: '$deep-interview prove config before state',
1188
+ sessionId,
1189
+ nowIso: '2026-02-25T00:00:00.000Z',
1190
+ });
1191
+ const beforeModeState = JSON.parse(await readFile(statePath, 'utf-8'));
1192
+ assert.ok(before);
1193
+ assert.equal(before.deep_interview_config, undefined);
1194
+ assert.equal(beforeModeState.deep_interview_config, undefined);
1195
+ assert.equal(beforeModeState.profile, undefined);
1196
+ assert.equal(beforeModeState.threshold, undefined);
1197
+ assert.equal(beforeModeState.max_rounds, undefined);
1198
+ assert.equal(beforeModeState.config_source, undefined);
1199
+ await writeFile(join(cwd, '.omx', 'config.toml'), `[omx.deepInterview]
1200
+ defaultProfile = "standard"
1201
+ standardThreshold = 0.05
1202
+ standardMaxRounds = 15
1203
+ `);
1204
+ const after = await recordSkillActivation({
1205
+ stateDir,
1206
+ sourceCwd: cwd,
1207
+ text: '$deep-interview prove config after state',
1208
+ sessionId,
1209
+ nowIso: '2026-02-25T00:00:01.000Z',
1210
+ });
1211
+ const afterModeState = JSON.parse(await readFile(statePath, 'utf-8'));
1212
+ assert.ok(after);
1213
+ assert.equal(after.deep_interview_config?.profile, 'standard');
1214
+ assert.equal(after.deep_interview_config?.threshold, 0.05);
1215
+ assert.equal(after.deep_interview_config?.maxRounds, 15);
1216
+ assert.equal(afterModeState.deep_interview_config?.profile, 'standard');
1217
+ assert.equal(afterModeState.profile, 'standard');
1218
+ assert.equal(afterModeState.threshold, 0.05);
1219
+ assert.equal(afterModeState.max_rounds, 15);
1220
+ assert.equal(afterModeState.config_source, join(cwd, '.omx', 'config.toml'));
1221
+ }
1222
+ finally {
1223
+ await rm(cwd, { recursive: true, force: true });
1224
+ }
1225
+ });
1226
+ });
1227
+ it('preserves deep-interview config values during continuation prompts', async () => {
1228
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-config-continuation-'));
1229
+ const stateDir = join(cwd, '.omx', 'state');
1230
+ const sessionId = 'sess-deep-interview-config-continuation';
1231
+ const statePath = join(stateDir, 'sessions', sessionId, DEEP_INTERVIEW_STATE_FILE);
1232
+ try {
1233
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1234
+ await mkdir(stateDir, { recursive: true });
1235
+ await writeFile(join(cwd, '.omx', 'config.toml'), `[omx.deepInterview]
1236
+ defaultProfile = "standard"
1237
+ standardThreshold = 0.05
1238
+ standardMaxRounds = 15
1239
+ `);
1240
+ await recordSkillActivation({
1241
+ stateDir,
1242
+ sourceCwd: cwd,
1243
+ text: '$deep-interview prove config continuation',
1244
+ sessionId,
1245
+ nowIso: '2026-02-25T00:00:00.000Z',
1246
+ });
1247
+ const continued = await recordSkillActivation({
1248
+ stateDir,
1249
+ sourceCwd: cwd,
1250
+ text: 'continue',
1251
+ sessionId,
1252
+ nowIso: '2026-02-25T00:00:01.000Z',
1253
+ });
1254
+ const modeState = JSON.parse(await readFile(statePath, 'utf-8'));
1255
+ assert.equal(continued?.skill, 'deep-interview');
1256
+ assert.equal(continued?.deep_interview_config?.profile, 'standard');
1257
+ assert.equal(continued?.deep_interview_config?.threshold, 0.05);
1258
+ assert.equal(continued?.deep_interview_config?.maxRounds, 15);
1259
+ assert.equal(modeState.deep_interview_config?.profile, 'standard');
1260
+ assert.equal(modeState.profile, 'standard');
1261
+ assert.equal(modeState.threshold, 0.05);
1262
+ assert.equal(modeState.max_rounds, 15);
1263
+ }
1264
+ finally {
1265
+ await rm(cwd, { recursive: true, force: true });
1266
+ }
1267
+ });
1268
+ it('preserves explicit deep-interview profile flags during continuation prompts', async () => {
1269
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-config-profile-continuation-'));
1270
+ const stateDir = join(cwd, '.omx', 'state');
1271
+ const sessionId = 'sess-deep-interview-config-profile-continuation';
1272
+ const statePath = join(stateDir, 'sessions', sessionId, DEEP_INTERVIEW_STATE_FILE);
1273
+ try {
1274
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1275
+ await mkdir(stateDir, { recursive: true });
1276
+ await writeFile(join(cwd, '.omx', 'config.toml'), `[omx.deepInterview]
1277
+ defaultProfile = "standard"
1278
+ standardThreshold = 0.22
1279
+ standardMaxRounds = 13
1280
+ deepThreshold = 0.13
1281
+ deepMaxRounds = 21
1282
+ `);
1283
+ const started = await recordSkillActivation({
1284
+ stateDir,
1285
+ sourceCwd: cwd,
1286
+ text: '$deep-interview --deep prove explicit profile continuation',
1287
+ sessionId,
1288
+ nowIso: '2026-02-25T00:00:00.000Z',
1289
+ });
1290
+ const continued = await recordSkillActivation({
1291
+ stateDir,
1292
+ sourceCwd: cwd,
1293
+ text: 'continue',
1294
+ sessionId,
1295
+ nowIso: '2026-02-25T00:00:01.000Z',
1296
+ });
1297
+ const modeState = JSON.parse(await readFile(statePath, 'utf-8'));
1298
+ assert.equal(started?.deep_interview_config?.profile, 'deep');
1299
+ assert.equal(continued?.deep_interview_config?.profile, 'deep');
1300
+ assert.equal(continued?.deep_interview_config?.threshold, 0.13);
1301
+ assert.equal(continued?.deep_interview_config?.maxRounds, 21);
1302
+ assert.equal(modeState.deep_interview_config?.profile, 'deep');
1303
+ assert.equal(modeState.profile, 'deep');
1304
+ assert.equal(modeState.threshold, 0.13);
1305
+ assert.equal(modeState.max_rounds, 21);
1306
+ }
1307
+ finally {
1308
+ await rm(cwd, { recursive: true, force: true });
1309
+ }
1310
+ });
1311
+ it('keeps the documented deep-interview Suggested Config executable through activation state', async () => {
1312
+ const skillDoc = await readFile(join(process.cwd(), 'skills', 'deep-interview', 'SKILL.md'), 'utf-8');
1313
+ const markerIndex = skillDoc.indexOf('## Suggested Config (optional)');
1314
+ assert.notEqual(markerIndex, -1);
1315
+ const configMatch = skillDoc.slice(markerIndex).match(/```toml\n([\s\S]*?)\n```/);
1316
+ assert.ok(configMatch);
1317
+ const documentedConfig = configMatch[1]?.trimEnd();
1318
+ assert.ok(documentedConfig);
1319
+ assert.match(documentedConfig, /standardThreshold = 0\.20/);
1320
+ assert.match(documentedConfig, /standardMaxRounds = 12/);
1321
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-doc-config-'));
1322
+ const stateDir = join(cwd, '.omx', 'state');
1323
+ const sessionId = 'sess-deep-interview-doc-config';
1324
+ const statePath = join(stateDir, 'sessions', sessionId, DEEP_INTERVIEW_STATE_FILE);
1325
+ try {
1326
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1327
+ await mkdir(stateDir, { recursive: true });
1328
+ await writeFile(join(cwd, '.omx', 'config.toml'), `${documentedConfig}\n`);
1329
+ const result = await recordSkillActivation({
1330
+ stateDir,
1331
+ sourceCwd: cwd,
1332
+ text: '$deep-interview prove documented config runtime contract',
1333
+ sessionId,
1334
+ nowIso: '2026-02-25T00:00:00.000Z',
1335
+ });
1336
+ const modeState = JSON.parse(await readFile(statePath, 'utf-8'));
1337
+ assert.ok(result);
1338
+ assert.equal(result.deep_interview_config?.profile, 'standard');
1339
+ assert.equal(result.deep_interview_config?.threshold, 0.2);
1340
+ assert.equal(result.deep_interview_config?.maxRounds, 12);
1341
+ assert.equal(modeState.deep_interview_config?.profile, 'standard');
1342
+ assert.equal(modeState.profile, 'standard');
1343
+ assert.equal(modeState.threshold, 0.2);
1344
+ assert.equal(modeState.max_rounds, 12);
1345
+ assert.equal(modeState.config_source, join(cwd, '.omx', 'config.toml'));
1346
+ }
1347
+ finally {
1348
+ await rm(cwd, { recursive: true, force: true });
1349
+ }
1350
+ });
1351
+ it('keeps deep-interview activation alive when repo config TOML is malformed', async () => {
1352
+ await withIsolatedHome('deep-interview-malformed-config', async () => {
1353
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-malformed-config-'));
1354
+ const stateDir = join(cwd, '.omx', 'state');
1355
+ const originalWarn = console.warn;
1356
+ try {
1357
+ console.warn = () => { };
1358
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1359
+ await mkdir(stateDir, { recursive: true });
1360
+ await writeFile(join(cwd, '.omx', 'config.toml'), '[omx.deepInterview\nstandardThreshold = 0.05\n');
1361
+ const result = await recordSkillActivation({
1362
+ stateDir,
1363
+ sourceCwd: cwd,
1364
+ text: '$deep-interview clarify despite malformed config',
1365
+ sessionId: 'sess-deep-interview-malformed-config',
1366
+ nowIso: '2026-02-25T00:00:00.000Z',
1367
+ });
1368
+ assert.ok(result);
1369
+ assert.equal(result.skill, 'deep-interview');
1370
+ assert.equal(result.active, true);
1371
+ assert.equal(result.deep_interview_config, undefined);
1372
+ const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-deep-interview-malformed-config', DEEP_INTERVIEW_STATE_FILE), 'utf-8'));
1373
+ assert.equal(modeState.mode, 'deep-interview');
1374
+ assert.equal(modeState.active, true);
1375
+ assert.equal(modeState.deep_interview_config, undefined);
1376
+ }
1377
+ finally {
1378
+ console.warn = originalWarn;
1379
+ await rm(cwd, { recursive: true, force: true });
1380
+ }
1381
+ });
1382
+ });
1011
1383
  it('creates the session-scoped deep-interview state directory before persisting mode state', async () => {
1012
1384
  const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-session-dir-'));
1013
1385
  const stateDir = join(cwd, '.omx', 'state');
@@ -1151,7 +1523,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
1151
1523
  await rm(cwd, { recursive: true, force: true });
1152
1524
  }
1153
1525
  });
1154
- it('records ultragoal as a prompt skill without seeding unrelated mode state', async () => {
1526
+ it('records ultragoal as a prompt skill with first-class mode state', async () => {
1155
1527
  const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-ultragoal-'));
1156
1528
  const stateDir = join(cwd, '.omx', 'state');
1157
1529
  try {
@@ -1163,9 +1535,12 @@ describe('keyword detector skill-active-state lifecycle', () => {
1163
1535
  assert.ok(result);
1164
1536
  assert.equal(result.skill, 'ultragoal');
1165
1537
  assert.equal(result.keyword, '$ultragoal');
1166
- assert.equal(result.initialized_mode, undefined);
1167
- assert.equal(result.initialized_state_path, undefined);
1168
- assert.equal(existsSync(join(stateDir, 'ultragoal-state.json')), false);
1538
+ assert.equal(result.initialized_mode, 'ultragoal');
1539
+ assert.equal(result.initialized_state_path, '.omx/state/ultragoal-state.json');
1540
+ const modeState = JSON.parse(await readFile(join(stateDir, 'ultragoal-state.json'), 'utf-8'));
1541
+ assert.equal(modeState.active, true);
1542
+ assert.equal(modeState.mode, 'ultragoal');
1543
+ assert.equal(modeState.current_phase, 'planning');
1169
1544
  }
1170
1545
  finally {
1171
1546
  await rm(cwd, { recursive: true, force: true });
@@ -1696,7 +2071,7 @@ describe('isUnderspecifiedForExecution', () => {
1696
2071
  });
1697
2072
  });
1698
2073
  describe('applyRalplanGate', () => {
1699
- it('does not re-enter ralplan for a short approved team follow-up', async () => {
2074
+ it('gates short team follow-up when only PRD/test-spec artifacts exist', async () => {
1700
2075
  const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-followup-'));
1701
2076
  try {
1702
2077
  const plansDir = join(cwd, '.omx', 'plans');
@@ -1704,20 +2079,32 @@ describe('applyRalplanGate', () => {
1704
2079
  await writeFile(join(plansDir, 'prd-issue-831.md'), '# Approved plan\n\nLaunch hint: omx team 3:executor "Execute approved issue 831 plan"\n');
1705
2080
  await writeFile(join(plansDir, 'test-spec-issue-831.md'), '# Test spec\n');
1706
2081
  const result = applyRalplanGate(['team'], 'team', { cwd });
1707
- assert.equal(result.gateApplied, false);
1708
- assert.deepEqual(result.keywords, ['team']);
2082
+ assert.equal(result.gateApplied, true);
2083
+ assert.deepEqual(result.keywords, ['ralplan']);
1709
2084
  }
1710
2085
  finally {
1711
2086
  await rm(cwd, { recursive: true, force: true });
1712
2087
  }
1713
2088
  });
1714
- it('does not re-enter ralplan for a short approved Korean team follow-up', async () => {
2089
+ it('does not re-enter ralplan for a short approved team follow-up with durable consensus', async () => {
1715
2090
  const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-followup-ko-'));
1716
2091
  try {
1717
2092
  const plansDir = join(cwd, '.omx', 'plans');
2093
+ const stateDir = join(cwd, '.omx', 'state');
1718
2094
  await mkdir(plansDir, { recursive: true });
2095
+ await mkdir(stateDir, { recursive: true });
1719
2096
  await writeFile(join(plansDir, 'prd-issue-831.md'), '# Approved plan\n\nLaunch hint: omx team 3:executor "Execute approved issue 831 plan"\n');
1720
2097
  await writeFile(join(plansDir, 'test-spec-issue-831.md'), '# Test spec\n');
2098
+ await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
2099
+ current_phase: 'complete',
2100
+ planning_complete: true,
2101
+ ralplan_consensus_gate: {
2102
+ complete: true,
2103
+ sequence: ['architect-review', 'critic-review'],
2104
+ ralplan_architect_review: { agent_role: 'architect', verdict: 'approve', iteration: 1 },
2105
+ ralplan_critic_review: { agent_role: 'critic', verdict: 'approve', iteration: 1 },
2106
+ },
2107
+ }));
1721
2108
  const result = applyRalplanGate(['team'], 'team으로 해줘', { cwd });
1722
2109
  assert.equal(result.gateApplied, false);
1723
2110
  assert.deepEqual(result.keywords, ['team']);
@@ -1726,13 +2113,25 @@ describe('applyRalplanGate', () => {
1726
2113
  await rm(cwd, { recursive: true, force: true });
1727
2114
  }
1728
2115
  });
1729
- it('does not re-enter ralplan for a short approved ralph follow-up', async () => {
2116
+ it('does not re-enter ralplan for a short approved ralph follow-up with durable consensus', async () => {
1730
2117
  const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-followup-ralph-'));
1731
2118
  try {
1732
2119
  const plansDir = join(cwd, '.omx', 'plans');
2120
+ const stateDir = join(cwd, '.omx', 'state');
1733
2121
  await mkdir(plansDir, { recursive: true });
2122
+ await mkdir(stateDir, { recursive: true });
1734
2123
  await writeFile(join(plansDir, 'prd-issue-832.md'), '# Approved plan\n\nLaunch hint: omx ralph "Execute approved issue 832 plan"\n');
1735
2124
  await writeFile(join(plansDir, 'test-spec-issue-832.md'), '# Test spec\n');
2125
+ await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
2126
+ current_phase: 'complete',
2127
+ planning_complete: true,
2128
+ ralplan_consensus_gate: {
2129
+ complete: true,
2130
+ sequence: ['architect-review', 'critic-review'],
2131
+ ralplan_architect_review: { agent_role: 'architect', verdict: 'approve', iteration: 1 },
2132
+ ralplan_critic_review: { agent_role: 'critic', verdict: 'approve', iteration: 1 },
2133
+ },
2134
+ }));
1736
2135
  const result = applyRalplanGate(['ralph'], 'ralph please', { cwd, priorSkill: 'ralplan' });
1737
2136
  assert.equal(result.gateApplied, false);
1738
2137
  assert.deepEqual(result.keywords, ['ralph']);
@@ -1741,6 +2140,63 @@ describe('applyRalplanGate', () => {
1741
2140
  await rm(cwd, { recursive: true, force: true });
1742
2141
  }
1743
2142
  });
2143
+ it('ignores ambient OMX_ROOT consensus state for local PRD/test-spec-only follow-up gating', async () => {
2144
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-local-'));
2145
+ const ambientRoot = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-ambient-'));
2146
+ const previousOmxRoot = process.env.OMX_ROOT;
2147
+ try {
2148
+ const plansDir = join(cwd, '.omx', 'plans');
2149
+ await mkdir(plansDir, { recursive: true });
2150
+ await writeFile(join(plansDir, 'prd-local.md'), '# Plan\n');
2151
+ await writeFile(join(plansDir, 'test-spec-local.md'), '# Test spec\n');
2152
+ const ambientStateDir = join(ambientRoot, '.omx', 'state');
2153
+ await mkdir(ambientStateDir, { recursive: true });
2154
+ await writeFile(join(ambientStateDir, 'ralplan-state.json'), JSON.stringify({
2155
+ current_phase: 'complete',
2156
+ planning_complete: true,
2157
+ ralplan_consensus_gate: {
2158
+ complete: true,
2159
+ ralplan_architect_review: { agent_role: 'architect', verdict: 'approve', iteration: 1 },
2160
+ ralplan_critic_review: { agent_role: 'critic', verdict: 'approve', iteration: 1 },
2161
+ },
2162
+ }));
2163
+ process.env.OMX_ROOT = ambientRoot;
2164
+ const result = applyRalplanGate(['team'], 'team', { cwd });
2165
+ assert.equal(result.gateApplied, true);
2166
+ assert.deepEqual(result.keywords, ['ralplan']);
2167
+ }
2168
+ finally {
2169
+ if (previousOmxRoot === undefined)
2170
+ delete process.env.OMX_ROOT;
2171
+ else
2172
+ process.env.OMX_ROOT = previousOmxRoot;
2173
+ await rm(cwd, { recursive: true, force: true });
2174
+ await rm(ambientRoot, { recursive: true, force: true });
2175
+ }
2176
+ });
2177
+ it('gates short follow-up when local state only has latest verdict fields', async () => {
2178
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-gate-latest-only-'));
2179
+ try {
2180
+ const plansDir = join(cwd, '.omx', 'plans');
2181
+ const stateDir = join(cwd, '.omx', 'state');
2182
+ await mkdir(plansDir, { recursive: true });
2183
+ await mkdir(stateDir, { recursive: true });
2184
+ await writeFile(join(plansDir, 'prd-local.md'), '# Plan\n\nLaunch hint: omx team 3:executor "Execute approved local plan"\n');
2185
+ await writeFile(join(plansDir, 'test-spec-local.md'), '# Test spec\n');
2186
+ await writeFile(join(stateDir, 'ralplan-state.json'), JSON.stringify({
2187
+ current_phase: 'complete',
2188
+ planning_complete: true,
2189
+ latest_architect_verdict: 'approve',
2190
+ latest_critic_verdict: 'approve',
2191
+ }));
2192
+ const result = applyRalplanGate(['team'], 'team', { cwd });
2193
+ assert.equal(result.gateApplied, true);
2194
+ assert.deepEqual(result.keywords, ['ralplan']);
2195
+ }
2196
+ finally {
2197
+ await rm(cwd, { recursive: true, force: true });
2198
+ }
2199
+ });
1744
2200
  it('redirects underspecified execution keywords to ralplan', () => {
1745
2201
  const result = applyRalplanGate(['ralph'], 'ralph fix this');
1746
2202
  assert.equal(result.gateApplied, true);