oh-my-codex 0.18.0 → 0.18.2

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 (410) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +45 -19
  4. package/crates/omx-api/src/lib.rs +66 -9
  5. package/crates/omx-sparkshell/src/exec.rs +125 -3
  6. package/crates/omx-sparkshell/src/main.rs +126 -36
  7. package/crates/omx-sparkshell/tests/execution.rs +225 -1
  8. package/dist/agents/__tests__/definitions.test.js +14 -0
  9. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  10. package/dist/agents/__tests__/native-config.test.js +19 -0
  11. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  12. package/dist/agents/definitions.d.ts.map +1 -1
  13. package/dist/agents/definitions.js +30 -0
  14. package/dist/agents/definitions.js.map +1 -1
  15. package/dist/agents/native-config.d.ts +1 -0
  16. package/dist/agents/native-config.d.ts.map +1 -1
  17. package/dist/agents/native-config.js +4 -0
  18. package/dist/agents/native-config.js.map +1 -1
  19. package/dist/catalog/__tests__/generator.test.js +4 -0
  20. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  21. package/dist/cli/__tests__/codex-plugin-layout.test.js +15 -7
  22. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  23. package/dist/cli/__tests__/doctor-warning-copy.test.js +137 -8
  24. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  25. package/dist/cli/__tests__/index.test.js +203 -15
  26. package/dist/cli/__tests__/index.test.js.map +1 -1
  27. package/dist/cli/__tests__/install-docs-contract.test.d.ts +2 -0
  28. package/dist/cli/__tests__/install-docs-contract.test.d.ts.map +1 -0
  29. package/dist/cli/__tests__/install-docs-contract.test.js +55 -0
  30. package/dist/cli/__tests__/install-docs-contract.test.js.map +1 -0
  31. package/dist/cli/__tests__/launch-fallback.test.js +163 -0
  32. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  33. package/dist/cli/__tests__/question.test.js +29 -43
  34. package/dist/cli/__tests__/question.test.js.map +1 -1
  35. package/dist/cli/__tests__/setup-install-mode.test.js +94 -35
  36. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  37. package/dist/cli/__tests__/sparkshell-cli.test.js +20 -1
  38. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  39. package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -0
  40. package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
  41. package/dist/cli/__tests__/ultragoal.test.js +227 -4
  42. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  43. package/dist/cli/__tests__/update.test.js +72 -1
  44. package/dist/cli/__tests__/update.test.js.map +1 -1
  45. package/dist/cli/codex-feature-probe.d.ts +5 -0
  46. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  47. package/dist/cli/codex-feature-probe.js +13 -7
  48. package/dist/cli/codex-feature-probe.js.map +1 -1
  49. package/dist/cli/doctor.d.ts +7 -0
  50. package/dist/cli/doctor.d.ts.map +1 -1
  51. package/dist/cli/doctor.js +297 -17
  52. package/dist/cli/doctor.js.map +1 -1
  53. package/dist/cli/index.d.ts +9 -1
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +465 -110
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/plugin-marketplace.d.ts +2 -0
  58. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  59. package/dist/cli/plugin-marketplace.js +15 -1
  60. package/dist/cli/plugin-marketplace.js.map +1 -1
  61. package/dist/cli/setup.d.ts.map +1 -1
  62. package/dist/cli/setup.js +71 -11
  63. package/dist/cli/setup.js.map +1 -1
  64. package/dist/cli/sparkshell.d.ts +7 -1
  65. package/dist/cli/sparkshell.d.ts.map +1 -1
  66. package/dist/cli/sparkshell.js +13 -3
  67. package/dist/cli/sparkshell.js.map +1 -1
  68. package/dist/cli/ultragoal.d.ts +1 -1
  69. package/dist/cli/ultragoal.d.ts.map +1 -1
  70. package/dist/cli/ultragoal.js +184 -10
  71. package/dist/cli/ultragoal.js.map +1 -1
  72. package/dist/cli/update.d.ts +2 -0
  73. package/dist/cli/update.d.ts.map +1 -1
  74. package/dist/cli/update.js +14 -3
  75. package/dist/cli/update.js.map +1 -1
  76. package/dist/compat/__tests__/doctor-contract.test.js +3 -0
  77. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  78. package/dist/config/__tests__/codex-feature-flags.test.js +11 -1
  79. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
  80. package/dist/config/__tests__/codex-hooks.test.js +22 -11
  81. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  82. package/dist/config/__tests__/commit-lore-guard.test.d.ts +2 -0
  83. package/dist/config/__tests__/commit-lore-guard.test.d.ts.map +1 -0
  84. package/dist/config/__tests__/commit-lore-guard.test.js +20 -0
  85. package/dist/config/__tests__/commit-lore-guard.test.js.map +1 -0
  86. package/dist/config/codex-feature-flags.d.ts +4 -0
  87. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  88. package/dist/config/codex-feature-flags.js +4 -0
  89. package/dist/config/codex-feature-flags.js.map +1 -1
  90. package/dist/config/codex-hooks.d.ts +1 -0
  91. package/dist/config/codex-hooks.d.ts.map +1 -1
  92. package/dist/config/codex-hooks.js +8 -10
  93. package/dist/config/codex-hooks.js.map +1 -1
  94. package/dist/config/commit-lore-guard.d.ts +1 -0
  95. package/dist/config/commit-lore-guard.d.ts.map +1 -1
  96. package/dist/config/commit-lore-guard.js +29 -3
  97. package/dist/config/commit-lore-guard.js.map +1 -1
  98. package/dist/config/generator.d.ts +17 -1
  99. package/dist/config/generator.d.ts.map +1 -1
  100. package/dist/config/generator.js +124 -11
  101. package/dist/config/generator.js.map +1 -1
  102. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +21 -0
  103. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  104. package/dist/goal-workflows/codex-goal-snapshot.d.ts +4 -0
  105. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  106. package/dist/goal-workflows/codex-goal-snapshot.js +50 -3
  107. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  108. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -6
  109. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  110. package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +1 -1
  111. package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -11
  112. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  113. package/dist/hooks/__tests__/deep-interview-contract.test.js +4 -3
  114. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  115. package/dist/hooks/__tests__/keyword-detector.test.js +173 -17
  116. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +33 -0
  118. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  119. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +2 -0
  120. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +1 -0
  121. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +320 -0
  122. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -0
  123. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +12 -0
  124. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  125. package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts +2 -0
  126. package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts.map +1 -0
  127. package/dist/hooks/__tests__/research-workflow-boundaries.test.js +35 -0
  128. package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -0
  129. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +26 -3
  130. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  131. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  132. package/dist/hooks/extensibility/dispatcher.js +29 -14
  133. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  134. package/dist/hooks/keyword-detector.d.ts +1 -1
  135. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  136. package/dist/hooks/keyword-detector.js +36 -9
  137. package/dist/hooks/keyword-detector.js.map +1 -1
  138. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  139. package/dist/hooks/keyword-registry.js +1 -0
  140. package/dist/hooks/keyword-registry.js.map +1 -1
  141. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  142. package/dist/hooks/prompt-guidance-contract.js +14 -2
  143. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  144. package/dist/hud/__tests__/hud-tmux-injection.test.js +36 -8
  145. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  146. package/dist/hud/__tests__/reconcile.test.js +122 -11
  147. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  148. package/dist/hud/__tests__/render.test.js +84 -0
  149. package/dist/hud/__tests__/render.test.js.map +1 -1
  150. package/dist/hud/__tests__/resource-leak-watch.test.d.ts +2 -0
  151. package/dist/hud/__tests__/resource-leak-watch.test.d.ts.map +1 -0
  152. package/dist/hud/__tests__/resource-leak-watch.test.js +28 -0
  153. package/dist/hud/__tests__/resource-leak-watch.test.js.map +1 -0
  154. package/dist/hud/__tests__/state.test.js +51 -1
  155. package/dist/hud/__tests__/state.test.js.map +1 -1
  156. package/dist/hud/__tests__/tmux.test.js +69 -23
  157. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  158. package/dist/hud/index.d.ts +2 -2
  159. package/dist/hud/index.d.ts.map +1 -1
  160. package/dist/hud/index.js +17 -6
  161. package/dist/hud/index.js.map +1 -1
  162. package/dist/hud/reconcile.d.ts.map +1 -1
  163. package/dist/hud/reconcile.js +6 -3
  164. package/dist/hud/reconcile.js.map +1 -1
  165. package/dist/hud/render.d.ts.map +1 -1
  166. package/dist/hud/render.js +26 -0
  167. package/dist/hud/render.js.map +1 -1
  168. package/dist/hud/state.d.ts +2 -1
  169. package/dist/hud/state.d.ts.map +1 -1
  170. package/dist/hud/state.js +62 -1
  171. package/dist/hud/state.js.map +1 -1
  172. package/dist/hud/tmux.d.ts +10 -3
  173. package/dist/hud/tmux.d.ts.map +1 -1
  174. package/dist/hud/tmux.js +60 -11
  175. package/dist/hud/tmux.js.map +1 -1
  176. package/dist/hud/types.d.ts +22 -0
  177. package/dist/hud/types.d.ts.map +1 -1
  178. package/dist/hud/types.js.map +1 -1
  179. package/dist/notifications/__tests__/http-client-resource.test.d.ts +2 -0
  180. package/dist/notifications/__tests__/http-client-resource.test.d.ts.map +1 -0
  181. package/dist/notifications/__tests__/http-client-resource.test.js +41 -0
  182. package/dist/notifications/__tests__/http-client-resource.test.js.map +1 -0
  183. package/dist/notifications/__tests__/verbosity.test.js +20 -0
  184. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  185. package/dist/notifications/config.d.ts.map +1 -1
  186. package/dist/notifications/config.js +6 -3
  187. package/dist/notifications/config.js.map +1 -1
  188. package/dist/notifications/http-client.d.ts.map +1 -1
  189. package/dist/notifications/http-client.js +78 -27
  190. package/dist/notifications/http-client.js.map +1 -1
  191. package/dist/notifications/types.d.ts +2 -0
  192. package/dist/notifications/types.d.ts.map +1 -1
  193. package/dist/openclaw/__tests__/dispatcher.test.js +49 -1
  194. package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
  195. package/dist/openclaw/dispatcher.d.ts +7 -4
  196. package/dist/openclaw/dispatcher.d.ts.map +1 -1
  197. package/dist/openclaw/dispatcher.js +32 -69
  198. package/dist/openclaw/dispatcher.js.map +1 -1
  199. package/dist/pipeline/__tests__/orchestrator.test.js +128 -4
  200. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  201. package/dist/pipeline/__tests__/stages.test.js +460 -9
  202. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  203. package/dist/pipeline/index.d.ts +8 -2
  204. package/dist/pipeline/index.d.ts.map +1 -1
  205. package/dist/pipeline/index.js +5 -2
  206. package/dist/pipeline/index.js.map +1 -1
  207. package/dist/pipeline/orchestrator.d.ts +5 -4
  208. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  209. package/dist/pipeline/orchestrator.js +85 -17
  210. package/dist/pipeline/orchestrator.js.map +1 -1
  211. package/dist/pipeline/stages/code-review.d.ts +2 -2
  212. package/dist/pipeline/stages/code-review.d.ts.map +1 -1
  213. package/dist/pipeline/stages/code-review.js +5 -3
  214. package/dist/pipeline/stages/code-review.js.map +1 -1
  215. package/dist/pipeline/stages/deep-interview.d.ts +15 -0
  216. package/dist/pipeline/stages/deep-interview.d.ts.map +1 -0
  217. package/dist/pipeline/stages/deep-interview.js +32 -0
  218. package/dist/pipeline/stages/deep-interview.js.map +1 -0
  219. package/dist/pipeline/stages/ralph-verify.d.ts +5 -5
  220. package/dist/pipeline/stages/ralph-verify.d.ts.map +1 -1
  221. package/dist/pipeline/stages/ralph-verify.js +2 -2
  222. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  223. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  224. package/dist/pipeline/stages/ralplan.js +41 -6
  225. package/dist/pipeline/stages/ralplan.js.map +1 -1
  226. package/dist/pipeline/stages/ultragoal.d.ts +19 -0
  227. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -0
  228. package/dist/pipeline/stages/ultragoal.js +38 -0
  229. package/dist/pipeline/stages/ultragoal.js.map +1 -0
  230. package/dist/pipeline/stages/ultraqa.d.ts +30 -0
  231. package/dist/pipeline/stages/ultraqa.d.ts.map +1 -0
  232. package/dist/pipeline/stages/ultraqa.js +46 -0
  233. package/dist/pipeline/stages/ultraqa.js.map +1 -0
  234. package/dist/pipeline/types.d.ts +8 -6
  235. package/dist/pipeline/types.d.ts.map +1 -1
  236. package/dist/pipeline/types.js +2 -2
  237. package/dist/question/__tests__/ui.test.js +43 -10
  238. package/dist/question/__tests__/ui.test.js.map +1 -1
  239. package/dist/question/ui.d.ts +12 -0
  240. package/dist/question/ui.d.ts.map +1 -1
  241. package/dist/question/ui.js +83 -46
  242. package/dist/question/ui.js.map +1 -1
  243. package/dist/ralplan/__tests__/runtime.test.js +200 -10
  244. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  245. package/dist/ralplan/consensus-gate.d.ts +23 -0
  246. package/dist/ralplan/consensus-gate.d.ts.map +1 -0
  247. package/dist/ralplan/consensus-gate.js +212 -0
  248. package/dist/ralplan/consensus-gate.js.map +1 -0
  249. package/dist/ralplan/runtime.d.ts +25 -0
  250. package/dist/ralplan/runtime.d.ts.map +1 -1
  251. package/dist/ralplan/runtime.js +144 -8
  252. package/dist/ralplan/runtime.js.map +1 -1
  253. package/dist/scripts/__tests__/codex-native-hook.test.js +1358 -79
  254. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  255. package/dist/scripts/__tests__/docs-site-contract.test.d.ts +2 -0
  256. package/dist/scripts/__tests__/docs-site-contract.test.d.ts.map +1 -0
  257. package/dist/scripts/__tests__/docs-site-contract.test.js +42 -0
  258. package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -0
  259. package/dist/scripts/__tests__/notify-dispatcher.test.js +115 -2
  260. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  261. package/dist/scripts/__tests__/run-test-files.test.js +57 -0
  262. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  263. package/dist/scripts/__tests__/smoke-packed-install.test.js +23 -1
  264. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  265. package/dist/scripts/__tests__/verify-native-agents.test.js +18 -3
  266. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  267. package/dist/scripts/cleanup-explore-harness.js +1 -0
  268. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  269. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  270. package/dist/scripts/codex-native-hook.js +372 -44
  271. package/dist/scripts/codex-native-hook.js.map +1 -1
  272. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  273. package/dist/scripts/codex-native-pre-post.js +9 -1
  274. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  275. package/dist/scripts/notify-dispatcher.js +188 -4
  276. package/dist/scripts/notify-dispatcher.js.map +1 -1
  277. package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
  278. package/dist/scripts/notify-hook/process-runner.js +39 -17
  279. package/dist/scripts/notify-hook/process-runner.js.map +1 -1
  280. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  281. package/dist/scripts/notify-hook/team-dispatch.js +9 -5
  282. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  283. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -1
  284. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  285. package/dist/scripts/notify-hook/team-tmux-guard.js +7 -1
  286. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  287. package/dist/scripts/run-test-files.js +13 -0
  288. package/dist/scripts/run-test-files.js.map +1 -1
  289. package/dist/scripts/smoke-packed-install.d.ts +3 -0
  290. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  291. package/dist/scripts/smoke-packed-install.js +99 -1
  292. package/dist/scripts/smoke-packed-install.js.map +1 -1
  293. package/dist/scripts/sync-plugin-mirror.js +2 -2
  294. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  295. package/dist/scripts/verify-native-agents.js +2 -2
  296. package/dist/scripts/verify-native-agents.js.map +1 -1
  297. package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts +2 -0
  298. package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts.map +1 -0
  299. package/dist/sidecar/__tests__/resource-leak-watch.test.js +38 -0
  300. package/dist/sidecar/__tests__/resource-leak-watch.test.js.map +1 -0
  301. package/dist/sidecar/index.d.ts +1 -1
  302. package/dist/sidecar/index.d.ts.map +1 -1
  303. package/dist/sidecar/index.js +29 -12
  304. package/dist/sidecar/index.js.map +1 -1
  305. package/dist/state/__tests__/operations-ralph-phase.test.js +88 -1
  306. package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
  307. package/dist/state/__tests__/workflow-transition.test.js +6 -0
  308. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  309. package/dist/state/operations.d.ts.map +1 -1
  310. package/dist/state/operations.js +11 -0
  311. package/dist/state/operations.js.map +1 -1
  312. package/dist/state/workflow-transition.d.ts +1 -1
  313. package/dist/state/workflow-transition.d.ts.map +1 -1
  314. package/dist/state/workflow-transition.js +7 -0
  315. package/dist/state/workflow-transition.js.map +1 -1
  316. package/dist/subagents/tracker.d.ts.map +1 -1
  317. package/dist/subagents/tracker.js +4 -3
  318. package/dist/subagents/tracker.js.map +1 -1
  319. package/dist/team/__tests__/runtime.test.js +36 -44
  320. package/dist/team/__tests__/runtime.test.js.map +1 -1
  321. package/dist/team/__tests__/tmux-session.test.js +163 -15
  322. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  323. package/dist/team/runtime.d.ts.map +1 -1
  324. package/dist/team/runtime.js +10 -20
  325. package/dist/team/runtime.js.map +1 -1
  326. package/dist/team/tmux-session.d.ts.map +1 -1
  327. package/dist/team/tmux-session.js +51 -21
  328. package/dist/team/tmux-session.js.map +1 -1
  329. package/dist/ultragoal/__tests__/artifacts.test.js +764 -10
  330. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  331. package/dist/ultragoal/__tests__/docs-contract.test.js +57 -1
  332. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  333. package/dist/ultragoal/__tests__/steering-fixtures.d.ts +68 -0
  334. package/dist/ultragoal/__tests__/steering-fixtures.d.ts.map +1 -0
  335. package/dist/ultragoal/__tests__/steering-fixtures.js +259 -0
  336. package/dist/ultragoal/__tests__/steering-fixtures.js.map +1 -0
  337. package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts +2 -0
  338. package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts.map +1 -0
  339. package/dist/ultragoal/__tests__/steering-fixtures.test.js +65 -0
  340. package/dist/ultragoal/__tests__/steering-fixtures.test.js.map +1 -0
  341. package/dist/ultragoal/artifacts.d.ts +97 -2
  342. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  343. package/dist/ultragoal/artifacts.js +837 -256
  344. package/dist/ultragoal/artifacts.js.map +1 -1
  345. package/dist/utils/__tests__/sleep-resource.test.d.ts +2 -0
  346. package/dist/utils/__tests__/sleep-resource.test.d.ts.map +1 -0
  347. package/dist/utils/__tests__/sleep-resource.test.js +39 -0
  348. package/dist/utils/__tests__/sleep-resource.test.js.map +1 -0
  349. package/dist/utils/sleep.d.ts.map +1 -1
  350. package/dist/utils/sleep.js +17 -6
  351. package/dist/utils/sleep.js.map +1 -1
  352. package/package.json +2 -1
  353. package/plugins/oh-my-codex/.codex-plugin/plugin.json +4 -3
  354. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +56 -0
  355. package/plugins/oh-my-codex/hooks/hooks.json +77 -0
  356. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +92 -50
  357. package/plugins/oh-my-codex/skills/autoresearch/SKILL.md +4 -0
  358. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +1 -1
  359. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
  360. package/plugins/oh-my-codex/skills/cancel/SKILL.md +2 -2
  361. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +8 -8
  362. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
  363. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +23 -12
  364. package/plugins/oh-my-codex/skills/plan/SKILL.md +8 -8
  365. package/plugins/oh-my-codex/skills/prometheus-strict/README.md +35 -0
  366. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +219 -0
  367. package/plugins/oh-my-codex/skills/ralph/SKILL.md +7 -0
  368. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +22 -7
  369. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  370. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +38 -4
  371. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +1 -1
  372. package/prompts/planner.md +1 -1
  373. package/prompts/prometheus-strict-metis.md +274 -0
  374. package/prompts/prometheus-strict-momus.md +82 -0
  375. package/prompts/prometheus-strict-oracle.md +107 -0
  376. package/prompts/researcher.md +22 -3
  377. package/skills/autopilot/SKILL.md +92 -50
  378. package/skills/autoresearch/SKILL.md +4 -0
  379. package/skills/autoresearch-goal/SKILL.md +1 -1
  380. package/skills/best-practice-research/SKILL.md +1 -1
  381. package/skills/cancel/SKILL.md +2 -2
  382. package/skills/deep-interview/SKILL.md +8 -8
  383. package/skills/omx-setup/SKILL.md +1 -1
  384. package/skills/pipeline/SKILL.md +23 -12
  385. package/skills/plan/SKILL.md +8 -8
  386. package/skills/prometheus-strict/README.md +35 -0
  387. package/skills/prometheus-strict/SKILL.md +219 -0
  388. package/skills/ralph/SKILL.md +7 -0
  389. package/skills/ralplan/SKILL.md +22 -7
  390. package/skills/team/SKILL.md +1 -1
  391. package/skills/ultragoal/SKILL.md +38 -4
  392. package/skills/ultrawork/SKILL.md +1 -1
  393. package/src/scripts/__tests__/codex-native-hook.test.ts +1757 -210
  394. package/src/scripts/__tests__/docs-site-contract.test.ts +47 -0
  395. package/src/scripts/__tests__/notify-dispatcher.test.ts +132 -3
  396. package/src/scripts/__tests__/run-test-files.test.ts +67 -0
  397. package/src/scripts/__tests__/smoke-packed-install.test.ts +31 -0
  398. package/src/scripts/__tests__/verify-native-agents.test.ts +23 -3
  399. package/src/scripts/cleanup-explore-harness.ts +1 -0
  400. package/src/scripts/codex-native-hook.ts +393 -40
  401. package/src/scripts/codex-native-pre-post.ts +16 -1
  402. package/src/scripts/notify-dispatcher.ts +202 -4
  403. package/src/scripts/notify-hook/process-runner.ts +40 -16
  404. package/src/scripts/notify-hook/team-dispatch.ts +9 -5
  405. package/src/scripts/notify-hook/team-tmux-guard.ts +7 -0
  406. package/src/scripts/run-test-files.ts +13 -0
  407. package/src/scripts/smoke-packed-install.ts +105 -0
  408. package/src/scripts/sync-plugin-mirror.ts +3 -3
  409. package/src/scripts/verify-native-agents.ts +2 -2
  410. package/templates/catalog-manifest.json +22 -0
@@ -15,8 +15,11 @@ import { resetTriageConfigCache } from "../../hooks/triage-config.js";
15
15
  import { executeStateOperation } from "../../state/operations.js";
16
16
  import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
17
17
  import { readAllState } from "../../hud/state.js";
18
+ import { renderHud } from "../../hud/render.js";
18
19
  import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
19
20
  import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
21
+ import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
22
+ import { getBaseStateDir } from "../../state/paths.js";
20
23
  function nativeHookScriptPath() {
21
24
  return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
22
25
  }
@@ -39,6 +42,38 @@ async function writeJson(path, value) {
39
42
  await mkdir(dirname(path), { recursive: true }).catch(() => { });
40
43
  await writeFile(path, JSON.stringify(value, null, 2));
41
44
  }
45
+ async function withLoreGuardConfig(value, prefix, run) {
46
+ const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
47
+ const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
48
+ const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
49
+ const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
50
+ const originalCodexHome = process.env.CODEX_HOME;
51
+ const originalHome = process.env.HOME;
52
+ try {
53
+ delete process.env.OMX_LORE_COMMIT_GUARD;
54
+ process.env.CODEX_HOME = codexHome;
55
+ process.env.HOME = defaultHome;
56
+ await writeFile(join(codexHome, "config.toml"), `[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`, "utf-8");
57
+ return await run(cwd);
58
+ }
59
+ finally {
60
+ if (originalGuard === undefined)
61
+ delete process.env.OMX_LORE_COMMIT_GUARD;
62
+ else
63
+ process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
64
+ if (originalCodexHome === undefined)
65
+ delete process.env.CODEX_HOME;
66
+ else
67
+ process.env.CODEX_HOME = originalCodexHome;
68
+ if (originalHome === undefined)
69
+ delete process.env.HOME;
70
+ else
71
+ process.env.HOME = originalHome;
72
+ await rm(cwd, { recursive: true, force: true });
73
+ await rm(codexHome, { recursive: true, force: true });
74
+ await rm(defaultHome, { recursive: true, force: true });
75
+ }
76
+ }
42
77
  function buildWorkerStopFakeTmux(tmuxLogPath, options = {}) {
43
78
  return `#!/usr/bin/env bash
44
79
  set -eu
@@ -189,7 +224,7 @@ describe("codex native hook config", () => {
189
224
  assert.equal(sessionStart.matcher, "startup|resume|clear");
190
225
  assert.equal(sessionStart.hooks?.[0]?.statusMessage, undefined);
191
226
  const preToolUse = config.hooks.PreToolUse[0];
192
- assert.equal(preToolUse.matcher, "Bash");
227
+ assert.equal(preToolUse.matcher, undefined);
193
228
  assert.match(String(preToolUse.hooks?.[0]?.command || ""), /codex-native-hook\.js"?$/);
194
229
  assert.equal(preToolUse.hooks?.[0]?.statusMessage, undefined);
195
230
  const postToolUse = config.hooks.PostToolUse[0];
@@ -567,7 +602,16 @@ describe("codex native hook dispatch", () => {
567
602
  });
568
603
  it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
569
604
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
570
- try {
605
+ const originalCodexHome = process.env.CODEX_HOME;
606
+ try {
607
+ process.env.CODEX_HOME = join(cwd, "codex-home");
608
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
609
+ notifications: {
610
+ enabled: true,
611
+ verbosity: "session",
612
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
613
+ },
614
+ });
571
615
  const stateDir = join(cwd, ".omx", "state");
572
616
  const canonicalSessionId = "omx-leader-session";
573
617
  const leaderNativeSessionId = "codex-leader-thread";
@@ -583,6 +627,13 @@ describe("codex native hook dispatch", () => {
583
627
  iteration: 1,
584
628
  max_iterations: 5,
585
629
  });
630
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
631
+ await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
632
+ "import { appendFileSync } from 'node:fs';",
633
+ "export async function onHookEvent(event) {",
634
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
635
+ "}",
636
+ ].join("\n"));
586
637
  const transcriptPath = join(cwd, "subagent-rollout.jsonl");
587
638
  await writeFile(transcriptPath, `${JSON.stringify({
588
639
  type: "session_meta",
@@ -616,6 +667,7 @@ describe("codex native hook dispatch", () => {
616
667
  const leaderRalph = JSON.parse(await readFile(join(stateDir, "sessions", canonicalSessionId, "ralph-state.json"), "utf-8"));
617
668
  assert.equal(leaderRalph.active, true);
618
669
  assert.equal(leaderRalph.current_phase, "executing");
670
+ assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must not independently dispatch session-start hook notifications");
619
671
  const tracking = JSON.parse(await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"));
620
672
  assert.equal(tracking.sessions?.[canonicalSessionId]?.leader_thread_id, leaderNativeSessionId);
621
673
  assert.equal(tracking.sessions?.[canonicalSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
@@ -623,8 +675,213 @@ describe("codex native hook dispatch", () => {
623
675
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
624
676
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
625
677
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
678
+ await dispatchCodexNativeHook({
679
+ hook_event_name: "Stop",
680
+ cwd,
681
+ session_id: childNativeSessionId,
682
+ thread_id: childNativeSessionId,
683
+ turn_id: "child-stop-turn",
684
+ }, { cwd });
685
+ assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent Stop must not independently dispatch stop hook notifications");
686
+ }
687
+ finally {
688
+ if (originalCodexHome === undefined) {
689
+ delete process.env.CODEX_HOME;
690
+ }
691
+ else {
692
+ process.env.CODEX_HOME = originalCodexHome;
693
+ }
694
+ await rm(cwd, { recursive: true, force: true });
695
+ }
696
+ });
697
+ it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
698
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
699
+ const originalCodexHome = process.env.CODEX_HOME;
700
+ try {
701
+ process.env.CODEX_HOME = join(cwd, "codex-home");
702
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
703
+ notifications: {
704
+ enabled: true,
705
+ verbosity: "minimal",
706
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
707
+ },
708
+ });
709
+ const stateDir = join(cwd, ".omx", "state");
710
+ const canonicalSessionId = "omx-leader-session-minimal";
711
+ const leaderNativeSessionId = "codex-leader-thread-minimal";
712
+ const childNativeSessionId = "codex-child-thread-minimal";
713
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
714
+ await writeSessionStart(cwd, canonicalSessionId, {
715
+ nativeSessionId: leaderNativeSessionId,
716
+ });
717
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
718
+ await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
719
+ "import { appendFileSync } from 'node:fs';",
720
+ "export async function onHookEvent(event) {",
721
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
722
+ "}",
723
+ ].join("\n"));
724
+ const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
725
+ await writeFile(transcriptPath, `${JSON.stringify({
726
+ type: "session_meta",
727
+ payload: {
728
+ id: childNativeSessionId,
729
+ source: {
730
+ subagent: {
731
+ thread_spawn: {
732
+ parent_thread_id: leaderNativeSessionId,
733
+ agent_role: "verifier",
734
+ },
735
+ },
736
+ },
737
+ },
738
+ })}\n`);
739
+ await dispatchCodexNativeHook({
740
+ hook_event_name: "SessionStart",
741
+ cwd,
742
+ session_id: childNativeSessionId,
743
+ transcript_path: transcriptPath,
744
+ }, { cwd, sessionOwnerPid: process.pid });
745
+ assert.equal(existsSync(join(cwd, "hook-events.jsonl")), false, "subagent SessionStart must be suppressed at minimal verbosity");
746
+ }
747
+ finally {
748
+ if (originalCodexHome === undefined) {
749
+ delete process.env.CODEX_HOME;
750
+ }
751
+ else {
752
+ process.env.CODEX_HOME = originalCodexHome;
753
+ }
754
+ await rm(cwd, { recursive: true, force: true });
755
+ }
756
+ });
757
+ it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
758
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
759
+ const originalCodexHome = process.env.CODEX_HOME;
760
+ try {
761
+ process.env.CODEX_HOME = join(cwd, "codex-home");
762
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
763
+ notifications: {
764
+ enabled: true,
765
+ verbosity: "session",
766
+ includeChildAgents: true,
767
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
768
+ },
769
+ });
770
+ const stateDir = join(cwd, ".omx", "state");
771
+ const canonicalSessionId = "omx-leader-session-include";
772
+ const leaderNativeSessionId = "codex-leader-thread-include";
773
+ const childNativeSessionId = "codex-child-thread-include";
774
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
775
+ await writeSessionStart(cwd, canonicalSessionId, {
776
+ nativeSessionId: leaderNativeSessionId,
777
+ });
778
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
779
+ await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
780
+ "import { appendFileSync } from 'node:fs';",
781
+ "export async function onHookEvent(event) {",
782
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
783
+ "}",
784
+ ].join("\n"));
785
+ const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
786
+ await writeFile(transcriptPath, `${JSON.stringify({
787
+ type: "session_meta",
788
+ payload: {
789
+ id: childNativeSessionId,
790
+ source: {
791
+ subagent: {
792
+ thread_spawn: {
793
+ parent_thread_id: leaderNativeSessionId,
794
+ agent_role: "verifier",
795
+ },
796
+ },
797
+ },
798
+ },
799
+ })}\n`);
800
+ await dispatchCodexNativeHook({
801
+ hook_event_name: "SessionStart",
802
+ cwd,
803
+ session_id: childNativeSessionId,
804
+ transcript_path: transcriptPath,
805
+ }, { cwd, sessionOwnerPid: process.pid });
806
+ await dispatchCodexNativeHook({
807
+ hook_event_name: "Stop",
808
+ cwd,
809
+ session_id: childNativeSessionId,
810
+ thread_id: childNativeSessionId,
811
+ turn_id: "included-child-stop-turn",
812
+ }, { cwd });
813
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
814
+ assert.match(hookEvents, /"event":"session-start"/);
815
+ assert.match(hookEvents, /"event":"stop"/);
816
+ }
817
+ finally {
818
+ if (originalCodexHome === undefined) {
819
+ delete process.env.CODEX_HOME;
820
+ }
821
+ else {
822
+ process.env.CODEX_HOME = originalCodexHome;
823
+ }
824
+ await rm(cwd, { recursive: true, force: true });
825
+ }
826
+ });
827
+ it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
828
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
829
+ const originalCodexHome = process.env.CODEX_HOME;
830
+ try {
831
+ process.env.CODEX_HOME = join(cwd, "codex-home");
832
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
833
+ notifications: {
834
+ enabled: true,
835
+ verbosity: "agent",
836
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
837
+ },
838
+ });
839
+ const stateDir = join(cwd, ".omx", "state");
840
+ const canonicalSessionId = "omx-leader-session-agent";
841
+ const leaderNativeSessionId = "codex-leader-thread-agent";
842
+ const childNativeSessionId = "codex-child-thread-agent";
843
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
844
+ await writeSessionStart(cwd, canonicalSessionId, {
845
+ nativeSessionId: leaderNativeSessionId,
846
+ });
847
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
848
+ await writeFile(join(cwd, ".omx", "hooks", "record-lifecycle.mjs"), [
849
+ "import { appendFileSync } from 'node:fs';",
850
+ "export async function onHookEvent(event) {",
851
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
852
+ "}",
853
+ ].join("\n"));
854
+ const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
855
+ await writeFile(transcriptPath, `${JSON.stringify({
856
+ type: "session_meta",
857
+ payload: {
858
+ id: childNativeSessionId,
859
+ source: {
860
+ subagent: {
861
+ thread_spawn: {
862
+ parent_thread_id: leaderNativeSessionId,
863
+ agent_role: "verifier",
864
+ },
865
+ },
866
+ },
867
+ },
868
+ })}\n`);
869
+ await dispatchCodexNativeHook({
870
+ hook_event_name: "SessionStart",
871
+ cwd,
872
+ session_id: childNativeSessionId,
873
+ transcript_path: transcriptPath,
874
+ }, { cwd, sessionOwnerPid: process.pid });
875
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
876
+ assert.match(hookEvents, /"event":"session-start"/);
626
877
  }
627
878
  finally {
879
+ if (originalCodexHome === undefined) {
880
+ delete process.env.CODEX_HOME;
881
+ }
882
+ else {
883
+ process.env.CODEX_HOME = originalCodexHome;
884
+ }
628
885
  await rm(cwd, { recursive: true, force: true });
629
886
  }
630
887
  });
@@ -1218,6 +1475,62 @@ describe("codex native hook dispatch", () => {
1218
1475
  await rm(cwd, { recursive: true, force: true });
1219
1476
  }
1220
1477
  });
1478
+ it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
1479
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
1480
+ try {
1481
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1482
+ version: 1,
1483
+ workflow: "performance-goal",
1484
+ slug: "latency",
1485
+ objective: "Reduce latency",
1486
+ status: "blocked",
1487
+ lastValidation: {
1488
+ status: "blocked",
1489
+ evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
1490
+ recordedAt: "2026-05-20T00:00:00.000Z",
1491
+ },
1492
+ });
1493
+ const result = await dispatchCodexNativeHook({
1494
+ hook_event_name: "Stop",
1495
+ cwd,
1496
+ session_id: "sess-performance-mismatch-blocked-stop",
1497
+ thread_id: "thread-performance-mismatch-blocked-stop",
1498
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1499
+ }, { cwd });
1500
+ assert.notEqual(result.outputJson?.decision, "block");
1501
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1502
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1503
+ }
1504
+ finally {
1505
+ await rm(cwd, { recursive: true, force: true });
1506
+ }
1507
+ });
1508
+ it("does not block Stop for an already complete performance-goal state", async () => {
1509
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
1510
+ try {
1511
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1512
+ version: 1,
1513
+ workflow: "performance-goal",
1514
+ slug: "latency",
1515
+ objective: "Reduce latency",
1516
+ status: "complete",
1517
+ completedAt: "2026-05-20T00:00:00.000Z",
1518
+ });
1519
+ const result = await dispatchCodexNativeHook({
1520
+ hook_event_name: "Stop",
1521
+ cwd,
1522
+ session_id: "sess-performance-complete-stop",
1523
+ thread_id: "thread-performance-complete-stop",
1524
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1525
+ }, { cwd });
1526
+ assert.notEqual(result.outputJson?.decision, "block");
1527
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1528
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1529
+ }
1530
+ finally {
1531
+ await rm(cwd, { recursive: true, force: true });
1532
+ }
1533
+ });
1221
1534
  it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
1222
1535
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
1223
1536
  try {
@@ -1262,7 +1575,7 @@ describe("codex native hook dispatch", () => {
1262
1575
  await rm(cwd, { recursive: true, force: true });
1263
1576
  }
1264
1577
  });
1265
- it("blocks ultragoal Stop with blocked checkpoint and fresh-thread remediation for completed legacy snapshots", async () => {
1578
+ it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
1266
1579
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
1267
1580
  try {
1268
1581
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
@@ -1281,7 +1594,11 @@ describe("codex native hook dispatch", () => {
1281
1594
  assert.equal(result.outputJson?.decision, "block");
1282
1595
  assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
1283
1596
  assert.match(output, /--status blocked/);
1284
- assert.match(output, /fresh Codex thread/);
1597
+ assert.match(output, /Codex goal context/);
1598
+ assert.match(output, /no such table: thread_goals/);
1599
+ assert.match(output, /unavailable get_goal error JSON or path/);
1600
+ assert.match(output, /safe-recovery blocker/);
1601
+ assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
1285
1602
  assert.match(output, /Hooks must not mutate Codex goal state/);
1286
1603
  }
1287
1604
  finally {
@@ -1294,7 +1611,7 @@ describe("codex native hook dispatch", () => {
1294
1611
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1295
1612
  version: 1,
1296
1613
  codexGoalMode: "aggregate",
1297
- codexObjective: "Complete all ultragoal stories in .omx/ultragoal/goals.json: many micro goals",
1614
+ codexObjective: "Complete the durable ultragoal plan in .omx/ultragoal/goals.json, including later accepted/appended stories, under the original brief constraints; use .omx/ultragoal/ledger.jsonl as the audit trail.",
1298
1615
  activeGoalId: "G001-micro",
1299
1616
  aggregateCompletion: {
1300
1617
  status: "complete",
@@ -1552,6 +1869,30 @@ describe("codex native hook dispatch", () => {
1552
1869
  await rm(cwd, { recursive: true, force: true });
1553
1870
  }
1554
1871
  });
1872
+ it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => {
1873
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-"));
1874
+ try {
1875
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1876
+ const result = await dispatchCodexNativeHook({
1877
+ hook_event_name: "UserPromptSubmit",
1878
+ cwd,
1879
+ session_id: "sess-autopilot-ralplan-gate",
1880
+ thread_id: "thread-autopilot-ralplan-gate",
1881
+ turn_id: "turn-autopilot-ralplan-gate",
1882
+ prompt: "$autopilot implement issue #2430",
1883
+ }, { cwd });
1884
+ assert.equal(result.omxEventName, "keyword-detector");
1885
+ assert.equal(result.skillState?.skill, "autopilot");
1886
+ const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
1887
+ assert.match(message, /Autopilot protocol:/);
1888
+ assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
1889
+ assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
1890
+ assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
1891
+ }
1892
+ finally {
1893
+ await rm(cwd, { recursive: true, force: true });
1894
+ }
1895
+ });
1555
1896
  it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
1556
1897
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
1557
1898
  try {
@@ -1566,14 +1907,229 @@ describe("codex native hook dispatch", () => {
1566
1907
  }, { cwd });
1567
1908
  assert.equal(result.omxEventName, "keyword-detector");
1568
1909
  assert.equal(result.skillState?.skill, "ultragoal");
1569
- assert.equal(result.skillState?.initialized_mode, undefined);
1910
+ assert.equal(result.skillState?.initialized_mode, "ultragoal");
1911
+ assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json");
1570
1912
  const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
1571
1913
  assert.match(message, /"\$ultragoal" -> ultragoal/);
1572
1914
  assert.match(message, /Ultragoal protocol:/);
1573
1915
  assert.match(message, /get_goal/);
1574
1916
  assert.match(message, /create_goal/);
1575
1917
  assert.match(message, /update_goal/);
1576
- assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
1918
+ assert.match(message, /does not call `\/goal clear`/);
1919
+ assert.match(message, /multiple sequential ultragoal runs/);
1920
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true);
1921
+ }
1922
+ finally {
1923
+ await rm(cwd, { recursive: true, force: true });
1924
+ }
1925
+ });
1926
+ it("deactivates active deep-interview state on explicit ultragoal handoff", async () => {
1927
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-"));
1928
+ try {
1929
+ const stateDir = join(cwd, ".omx", "state");
1930
+ const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff");
1931
+ await mkdir(sessionDir, { recursive: true });
1932
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
1933
+ version: 1,
1934
+ active: true,
1935
+ skill: "deep-interview",
1936
+ phase: "planning",
1937
+ session_id: "sess-ultragoal-handoff",
1938
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }],
1939
+ });
1940
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
1941
+ active: true,
1942
+ mode: "deep-interview",
1943
+ current_phase: "intent-first",
1944
+ session_id: "sess-ultragoal-handoff",
1945
+ question_enforcement: {
1946
+ obligation_id: "obligation-ultragoal-handoff",
1947
+ source: "omx-question",
1948
+ status: "pending",
1949
+ requested_at: "2026-05-21T03:00:00.000Z",
1950
+ },
1951
+ });
1952
+ const result = await dispatchCodexNativeHook({
1953
+ hook_event_name: "UserPromptSubmit",
1954
+ cwd,
1955
+ session_id: "sess-ultragoal-handoff",
1956
+ thread_id: "thread-ultragoal-handoff",
1957
+ turn_id: "turn-ultragoal-handoff",
1958
+ prompt: "$ultragoal turn the clarified spec into goals",
1959
+ }, { cwd });
1960
+ assert.equal(result.omxEventName, "keyword-detector");
1961
+ assert.equal(result.skillState?.skill, "ultragoal");
1962
+ assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/);
1963
+ const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
1964
+ assert.equal(completed.active, false);
1965
+ assert.equal(completed.current_phase, "completed");
1966
+ assert.equal(completed.question_enforcement?.status, "cleared");
1967
+ assert.equal(completed.question_enforcement?.clear_reason, "handoff");
1968
+ assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true);
1969
+ const edit = await dispatchCodexNativeHook({
1970
+ hook_event_name: "PreToolUse",
1971
+ cwd,
1972
+ session_id: "sess-ultragoal-handoff",
1973
+ thread_id: "thread-ultragoal-handoff",
1974
+ tool_name: "Edit",
1975
+ tool_use_id: "tool-ultragoal-post-handoff-edit",
1976
+ tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
1977
+ }, { cwd });
1978
+ assert.equal(edit.outputJson, null);
1979
+ }
1980
+ finally {
1981
+ await rm(cwd, { recursive: true, force: true });
1982
+ }
1983
+ });
1984
+ it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
1985
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
1986
+ try {
1987
+ await createUltragoalPlan(cwd, {
1988
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
1989
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
1990
+ });
1991
+ const prose = await dispatchCodexNativeHook({
1992
+ hook_event_name: "UserPromptSubmit",
1993
+ cwd,
1994
+ session_id: "sess-ultragoal-steer-1",
1995
+ prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
1996
+ }, { cwd });
1997
+ assert.equal(prose.outputJson, null);
1998
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
1999
+ const jsonExample = await dispatchCodexNativeHook({
2000
+ hook_event_name: "UserPromptSubmit",
2001
+ cwd,
2002
+ session_id: "sess-ultragoal-steer-1",
2003
+ prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
2004
+ kind: "add_subgoal",
2005
+ source: "user_prompt_submit",
2006
+ evidence: "Example JSON should not mutate .omx/ultragoal.",
2007
+ rationale: "Only explicit steering fences or labels are executable.",
2008
+ title: "Inert JSON example",
2009
+ objective: "This example must not be added.",
2010
+ })}\n\`\`\``,
2011
+ }, { cwd });
2012
+ assert.equal(jsonExample.outputJson, null);
2013
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
2014
+ const result = await dispatchCodexNativeHook({
2015
+ hook_event_name: "UserPromptSubmit",
2016
+ cwd,
2017
+ session_id: "sess-ultragoal-steer-1",
2018
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2019
+ kind: "add_subgoal",
2020
+ source: "user_prompt_submit",
2021
+ evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
2022
+ rationale: "Add bounded hook regression work while preserving all completion gates.",
2023
+ title: "Prompt bridge regression",
2024
+ objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
2025
+ })}`,
2026
+ }, { cwd });
2027
+ const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
2028
+ assert.match(message, /bounded \.omx\/ultragoal steering/);
2029
+ assert.match(message, /G002-cli-and-prompt-submit-bridge/);
2030
+ assert.match(message, /accepted/);
2031
+ const plan = await readUltragoalPlan(cwd);
2032
+ assert.equal(plan.goals.length, 2);
2033
+ assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
2034
+ }
2035
+ finally {
2036
+ await rm(cwd, { recursive: true, force: true });
2037
+ }
2038
+ });
2039
+ it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
2040
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
2041
+ try {
2042
+ await createUltragoalPlan(cwd, {
2043
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
2044
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2045
+ });
2046
+ const stateDir = join(cwd, ".omx", "state");
2047
+ const canonicalSessionId = "sess-ultragoal-parent";
2048
+ const leaderNativeSessionId = "native-ultragoal-parent";
2049
+ const childNativeSessionId = "native-ultragoal-child";
2050
+ const nowIso = new Date().toISOString();
2051
+ await writeJson(join(stateDir, "session.json"), {
2052
+ session_id: canonicalSessionId,
2053
+ native_session_id: leaderNativeSessionId,
2054
+ });
2055
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
2056
+ schemaVersion: 1,
2057
+ sessions: {
2058
+ [canonicalSessionId]: {
2059
+ session_id: canonicalSessionId,
2060
+ leader_thread_id: leaderNativeSessionId,
2061
+ updated_at: nowIso,
2062
+ threads: {
2063
+ [leaderNativeSessionId]: {
2064
+ thread_id: leaderNativeSessionId,
2065
+ kind: "leader",
2066
+ first_seen_at: nowIso,
2067
+ last_seen_at: nowIso,
2068
+ turn_count: 1,
2069
+ },
2070
+ [childNativeSessionId]: {
2071
+ thread_id: childNativeSessionId,
2072
+ kind: "subagent",
2073
+ first_seen_at: nowIso,
2074
+ last_seen_at: nowIso,
2075
+ turn_count: 1,
2076
+ mode: "architect",
2077
+ },
2078
+ },
2079
+ },
2080
+ },
2081
+ });
2082
+ const result = await dispatchCodexNativeHook({
2083
+ hook_event_name: "UserPromptSubmit",
2084
+ cwd,
2085
+ session_id: childNativeSessionId,
2086
+ thread_id: childNativeSessionId,
2087
+ turn_id: "turn-ultragoal-child-1",
2088
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2089
+ kind: "add_subgoal",
2090
+ source: "user_prompt_submit",
2091
+ evidence: "Subagent prompt text must be literal delegated context.",
2092
+ rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
2093
+ title: "Subagent should not add this",
2094
+ objective: "This must remain literal prompt text.",
2095
+ })}`,
2096
+ }, { cwd });
2097
+ assert.equal(result.outputJson, null);
2098
+ const plan = await readUltragoalPlan(cwd);
2099
+ assert.equal(plan.goals.length, 1);
2100
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2101
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
2102
+ assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
2103
+ }
2104
+ finally {
2105
+ await rm(cwd, { recursive: true, force: true });
2106
+ }
2107
+ });
2108
+ it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
2109
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
2110
+ try {
2111
+ await createUltragoalPlan(cwd, {
2112
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
2113
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2114
+ });
2115
+ const prompt = `\`\`\`omx-ultragoal-steer
2116
+ ${JSON.stringify({
2117
+ kind: "add_subgoal",
2118
+ source: "user_prompt_submit",
2119
+ evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
2120
+ rationale: "Use idempotent bridge semantics for repeated hook delivery.",
2121
+ title: "Deduped bridge regression",
2122
+ objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
2123
+ })}
2124
+ \`\`\``;
2125
+ await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
2126
+ const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
2127
+ const message = String(second.outputJson?.hookSpecificOutput?.additionalContext || "");
2128
+ assert.match(message, /deduped/);
2129
+ const plan = await readUltragoalPlan(cwd);
2130
+ assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
2131
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2132
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
1577
2133
  }
1578
2134
  finally {
1579
2135
  await rm(cwd, { recursive: true, force: true });
@@ -2729,52 +3285,199 @@ exit 0
2729
3285
  const result = await dispatchCodexNativeHook({
2730
3286
  hook_event_name: "PreToolUse",
2731
3287
  cwd,
2732
- source: "codex-app",
2733
- session_id: "sess-team-node-native-block",
2734
- tool_name: "Bash",
2735
- tool_use_id: "tool-team-node-native-block",
2736
- tool_input: { command: "node ./dist/cli/omx.js team status my-team" },
3288
+ source: "codex-app",
3289
+ session_id: "sess-team-node-native-block",
3290
+ tool_name: "Bash",
3291
+ tool_use_id: "tool-team-node-native-block",
3292
+ tool_input: { command: "node ./dist/cli/omx.js team status my-team" },
3293
+ }, { cwd });
3294
+ assert.equal(result.omxEventName, "pre-tool-use");
3295
+ assert.equal(result.outputJson?.decision, "block");
3296
+ assert.match(String(result.outputJson?.systemMessage || ""), /Codex App\/native outside-tmux sessions/);
3297
+ }
3298
+ finally {
3299
+ await rm(cwd, { recursive: true, force: true });
3300
+ }
3301
+ });
3302
+ it("preserves direct CLI outside-tmux omx team Bash behavior", async () => {
3303
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-outside-"));
3304
+ try {
3305
+ const result = await dispatchCodexNativeHook({
3306
+ hook_event_name: "PreToolUse",
3307
+ cwd,
3308
+ source: "cli",
3309
+ session_id: "sess-team-cli-outside",
3310
+ tool_name: "Bash",
3311
+ tool_use_id: "tool-team-cli-outside",
3312
+ tool_input: { command: "omx team status my-team" },
3313
+ }, { cwd });
3314
+ assert.equal(result.omxEventName, "pre-tool-use");
3315
+ assert.equal(result.outputJson, null);
3316
+ }
3317
+ finally {
3318
+ await rm(cwd, { recursive: true, force: true });
3319
+ }
3320
+ });
3321
+ it("preserves source-less outside-tmux omx team Bash behavior when no native session evidence exists", async () => {
3322
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-nosource-"));
3323
+ try {
3324
+ const result = await dispatchCodexNativeHook({
3325
+ hook_event_name: "PreToolUse",
3326
+ cwd,
3327
+ session_id: "sess-team-cli-nosource",
3328
+ tool_name: "Bash",
3329
+ tool_use_id: "tool-team-cli-nosource",
3330
+ tool_input: { command: "omx team status my-team" },
3331
+ }, { cwd });
3332
+ assert.equal(result.omxEventName, "pre-tool-use");
3333
+ assert.equal(result.outputJson, null);
3334
+ }
3335
+ finally {
3336
+ await rm(cwd, { recursive: true, force: true });
3337
+ }
3338
+ });
3339
+ it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => {
3340
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-"));
3341
+ try {
3342
+ const stateDir = join(cwd, ".omx", "state");
3343
+ const sessionDir = join(stateDir, "sessions", "sess-di-edit-block");
3344
+ await mkdir(sessionDir, { recursive: true });
3345
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd });
3346
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
3347
+ version: 1,
3348
+ active: true,
3349
+ skill: "deep-interview",
3350
+ phase: "planning",
3351
+ session_id: "sess-di-edit-block",
3352
+ thread_id: "thread-di-edit-block",
3353
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }],
3354
+ });
3355
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
3356
+ active: true,
3357
+ mode: "deep-interview",
3358
+ current_phase: "intent-first",
3359
+ session_id: "sess-di-edit-block",
3360
+ thread_id: "thread-di-edit-block",
3361
+ rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }],
3362
+ });
3363
+ const result = await dispatchCodexNativeHook({
3364
+ hook_event_name: "PreToolUse",
3365
+ cwd,
3366
+ session_id: "sess-di-edit-block",
3367
+ thread_id: "thread-di-edit-block",
3368
+ tool_name: "Edit",
3369
+ tool_use_id: "tool-di-edit-block",
3370
+ tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" },
2737
3371
  }, { cwd });
2738
3372
  assert.equal(result.omxEventName, "pre-tool-use");
2739
3373
  assert.equal(result.outputJson?.decision, "block");
2740
- assert.match(String(result.outputJson?.systemMessage || ""), /Codex App\/native outside-tmux sessions/);
3374
+ assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active/);
3375
+ assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/);
3376
+ assert.match(JSON.stringify(result.outputJson), /\$ralplan/);
2741
3377
  }
2742
3378
  finally {
2743
3379
  await rm(cwd, { recursive: true, force: true });
2744
3380
  }
2745
3381
  });
2746
- it("preserves direct CLI outside-tmux omx team Bash behavior", async () => {
2747
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-outside-"));
3382
+ it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
3383
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
2748
3384
  try {
2749
- const result = await dispatchCodexNativeHook({
3385
+ const stateDir = join(cwd, ".omx", "state");
3386
+ const sessionDir = join(stateDir, "sessions", "sess-di-artifact");
3387
+ await mkdir(sessionDir, { recursive: true });
3388
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd });
3389
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
3390
+ version: 1,
3391
+ active: true,
3392
+ skill: "deep-interview",
3393
+ phase: "planning",
3394
+ session_id: "sess-di-artifact",
3395
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact" }],
3396
+ });
3397
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
3398
+ active: true,
3399
+ mode: "deep-interview",
3400
+ current_phase: "intent-first",
3401
+ session_id: "sess-di-artifact",
3402
+ });
3403
+ const allowedWrite = await dispatchCodexNativeHook({
2750
3404
  hook_event_name: "PreToolUse",
2751
3405
  cwd,
2752
- source: "cli",
2753
- session_id: "sess-team-cli-outside",
3406
+ session_id: "sess-di-artifact",
3407
+ tool_name: "Write",
3408
+ tool_use_id: "tool-di-spec-write",
3409
+ tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" },
3410
+ }, { cwd });
3411
+ assert.equal(allowedWrite.outputJson, null);
3412
+ const allowedBash = await dispatchCodexNativeHook({
3413
+ hook_event_name: "PreToolUse",
3414
+ cwd,
3415
+ session_id: "sess-di-artifact",
2754
3416
  tool_name: "Bash",
2755
- tool_use_id: "tool-team-cli-outside",
2756
- tool_input: { command: "omx team status my-team" },
3417
+ tool_use_id: "tool-di-context-bash",
3418
+ tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" },
2757
3419
  }, { cwd });
2758
- assert.equal(result.omxEventName, "pre-tool-use");
2759
- assert.equal(result.outputJson, null);
3420
+ assert.equal(allowedBash.outputJson, null);
3421
+ const allowedAppendBash = await dispatchCodexNativeHook({
3422
+ hook_event_name: "PreToolUse",
3423
+ cwd,
3424
+ session_id: "sess-di-artifact",
3425
+ tool_name: "Bash",
3426
+ tool_use_id: "tool-di-context-append-bash",
3427
+ tool_input: { command: "echo more context >> .omx/context/demo.md" },
3428
+ }, { cwd });
3429
+ assert.equal(allowedAppendBash.outputJson, null);
3430
+ const blockedBash = await dispatchCodexNativeHook({
3431
+ hook_event_name: "PreToolUse",
3432
+ cwd,
3433
+ session_id: "sess-di-artifact",
3434
+ tool_name: "Bash",
3435
+ tool_use_id: "tool-di-src-bash",
3436
+ tool_input: { command: "cat > src/implementation.ts <<'EOF'\nexport const x = 1;\nEOF" },
3437
+ }, { cwd });
3438
+ assert.equal(blockedBash.outputJson?.decision, "block");
2760
3439
  }
2761
3440
  finally {
2762
3441
  await rm(cwd, { recursive: true, force: true });
2763
3442
  }
2764
3443
  });
2765
- it("preserves source-less outside-tmux omx team Bash behavior when no native session evidence exists", async () => {
2766
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-team-cli-nosource-"));
3444
+ it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
3445
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
2767
3446
  try {
3447
+ const stateDir = join(cwd, ".omx", "state");
3448
+ const sessionDir = join(stateDir, "sessions", "sess-di-handoff");
3449
+ await mkdir(sessionDir, { recursive: true });
3450
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
3451
+ version: 1,
3452
+ active: true,
3453
+ skill: "deep-interview",
3454
+ phase: "planning",
3455
+ session_id: "sess-di-handoff",
3456
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }],
3457
+ });
3458
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
3459
+ active: true,
3460
+ mode: "deep-interview",
3461
+ current_phase: "intent-first",
3462
+ session_id: "sess-di-handoff",
3463
+ });
3464
+ await dispatchCodexNativeHook({
3465
+ hook_event_name: "UserPromptSubmit",
3466
+ cwd,
3467
+ session_id: "sess-di-handoff",
3468
+ prompt: "$ralph implement the clarified spec in src/implementation.ts",
3469
+ }, { cwd });
2768
3470
  const result = await dispatchCodexNativeHook({
2769
3471
  hook_event_name: "PreToolUse",
2770
3472
  cwd,
2771
- session_id: "sess-team-cli-nosource",
2772
- tool_name: "Bash",
2773
- tool_use_id: "tool-team-cli-nosource",
2774
- tool_input: { command: "omx team status my-team" },
3473
+ session_id: "sess-di-handoff",
3474
+ tool_name: "Edit",
3475
+ tool_use_id: "tool-di-post-handoff-edit",
3476
+ tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
2775
3477
  }, { cwd });
2776
- assert.equal(result.omxEventName, "pre-tool-use");
2777
3478
  assert.equal(result.outputJson, null);
3479
+ const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8"));
3480
+ assert.equal(completed.active, false);
2778
3481
  }
2779
3482
  finally {
2780
3483
  await rm(cwd, { recursive: true, force: true });
@@ -3109,7 +3812,7 @@ exit 0
3109
3812
  cwd,
3110
3813
  tool_name: "Bash",
3111
3814
  tool_use_id: "tool-slop-git-priority",
3112
- tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
3815
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
3113
3816
  }, { cwd });
3114
3817
  assert.equal(result.omxEventName, "pre-tool-use");
3115
3818
  assert.equal(result.outputJson?.decision, "block");
@@ -3128,7 +3831,7 @@ exit 0
3128
3831
  cwd,
3129
3832
  tool_name: "Bash",
3130
3833
  tool_use_id: "tool-git-commit-invalid",
3131
- tool_input: { command: 'git commit -m "fix tests"' },
3834
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
3132
3835
  }, { cwd });
3133
3836
  assert.equal(result.omxEventName, "pre-tool-use");
3134
3837
  assert.deepEqual(result.outputJson, {
@@ -3153,11 +3856,35 @@ exit 0
3153
3856
  await rm(cwd, { recursive: true, force: true });
3154
3857
  }
3155
3858
  });
3156
- it("allows non-Lore git commit messages when the Lore commit guard is explicitly disabled", async () => {
3859
+ it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
3860
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
3861
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
3862
+ try {
3863
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3864
+ const result = await dispatchCodexNativeHook({
3865
+ hook_event_name: "PreToolUse",
3866
+ cwd,
3867
+ tool_name: "Bash",
3868
+ tool_use_id: "tool-git-commit-lore-env-enabled",
3869
+ tool_input: { command: 'git commit -m "fix tests"' },
3870
+ }, { cwd });
3871
+ assert.equal(result.omxEventName, "pre-tool-use");
3872
+ assert.equal(result.outputJson?.decision, "block");
3873
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3874
+ }
3875
+ finally {
3876
+ if (original === undefined)
3877
+ delete process.env.OMX_LORE_COMMIT_GUARD;
3878
+ else
3879
+ process.env.OMX_LORE_COMMIT_GUARD = original;
3880
+ await rm(cwd, { recursive: true, force: true });
3881
+ }
3882
+ });
3883
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
3157
3884
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
3158
3885
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3159
3886
  try {
3160
- process.env.OMX_LORE_COMMIT_GUARD = "0";
3887
+ delete process.env.OMX_LORE_COMMIT_GUARD;
3161
3888
  const result = await dispatchCodexNativeHook({
3162
3889
  hook_event_name: "PreToolUse",
3163
3890
  cwd,
@@ -3176,6 +3903,60 @@ exit 0
3176
3903
  await rm(cwd, { recursive: true, force: true });
3177
3904
  }
3178
3905
  });
3906
+ it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
3907
+ await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
3908
+ const result = await dispatchCodexNativeHook({
3909
+ hook_event_name: "PreToolUse",
3910
+ cwd,
3911
+ tool_name: "Bash",
3912
+ tool_use_id: "tool-git-commit-lore-config-enabled",
3913
+ tool_input: { command: 'git commit -m "fix: conventional"' },
3914
+ }, { cwd });
3915
+ assert.equal(result.omxEventName, "pre-tool-use");
3916
+ assert.equal(result.outputJson?.decision, "block");
3917
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3918
+ });
3919
+ });
3920
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
3921
+ await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
3922
+ const result = await dispatchCodexNativeHook({
3923
+ hook_event_name: "PreToolUse",
3924
+ cwd,
3925
+ tool_name: "Bash",
3926
+ tool_use_id: "tool-git-commit-lore-config-disabled",
3927
+ tool_input: { command: 'git commit -m "fix: use conventional commit"' },
3928
+ }, { cwd });
3929
+ assert.equal(result.omxEventName, "pre-tool-use");
3930
+ assert.equal(result.outputJson, null);
3931
+ });
3932
+ });
3933
+ it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
3934
+ await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
3935
+ const result = await dispatchCodexNativeHook({
3936
+ hook_event_name: "PreToolUse",
3937
+ cwd,
3938
+ tool_name: "Bash",
3939
+ tool_use_id: "tool-git-commit-lore-config-inline-enabled",
3940
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
3941
+ }, { cwd });
3942
+ assert.equal(result.omxEventName, "pre-tool-use");
3943
+ assert.equal(result.outputJson?.decision, "block");
3944
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3945
+ });
3946
+ });
3947
+ it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
3948
+ await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
3949
+ const result = await dispatchCodexNativeHook({
3950
+ hook_event_name: "PreToolUse",
3951
+ cwd,
3952
+ tool_name: "Bash",
3953
+ tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
3954
+ tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
3955
+ }, { cwd });
3956
+ assert.equal(result.omxEventName, "pre-tool-use");
3957
+ assert.equal(result.outputJson, null);
3958
+ });
3959
+ });
3179
3960
  it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
3180
3961
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
3181
3962
  try {
@@ -3193,7 +3974,30 @@ exit 0
3193
3974
  await rm(cwd, { recursive: true, force: true });
3194
3975
  }
3195
3976
  });
3196
- it("does not treat newline-separated Lore guard assignment as inline git commit env", async () => {
3977
+ it("allows inline disabled guard to override an enabled process env", async () => {
3978
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
3979
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
3980
+ try {
3981
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3982
+ const result = await dispatchCodexNativeHook({
3983
+ hook_event_name: "PreToolUse",
3984
+ cwd,
3985
+ tool_name: "Bash",
3986
+ tool_use_id: "tool-git-commit-lore-inline-override-disabled",
3987
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
3988
+ }, { cwd });
3989
+ assert.equal(result.omxEventName, "pre-tool-use");
3990
+ assert.equal(result.outputJson, null);
3991
+ }
3992
+ finally {
3993
+ if (original === undefined)
3994
+ delete process.env.OMX_LORE_COMMIT_GUARD;
3995
+ else
3996
+ process.env.OMX_LORE_COMMIT_GUARD = original;
3997
+ await rm(cwd, { recursive: true, force: true });
3998
+ }
3999
+ });
4000
+ it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
3197
4001
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
3198
4002
  try {
3199
4003
  const result = await dispatchCodexNativeHook({
@@ -3201,21 +4005,33 @@ exit 0
3201
4005
  cwd,
3202
4006
  tool_name: "Bash",
3203
4007
  tool_use_id: "tool-git-commit-lore-newline-assignment",
3204
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0\ngit commit -m "fix: conventional"' },
4008
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
3205
4009
  }, { cwd });
3206
4010
  assert.equal(result.omxEventName, "pre-tool-use");
3207
- assert.equal(result.outputJson?.decision, "block");
3208
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4011
+ assert.equal(result.outputJson, null);
3209
4012
  }
3210
4013
  finally {
3211
4014
  await rm(cwd, { recursive: true, force: true });
3212
4015
  }
3213
4016
  });
3214
- it("restores default-on Lore guard when env -u unsets a disabled process env", async () => {
4017
+ it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
4018
+ await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
4019
+ const result = await dispatchCodexNativeHook({
4020
+ hook_event_name: "PreToolUse",
4021
+ cwd,
4022
+ tool_name: "Bash",
4023
+ tool_use_id: "tool-git-commit-lore-config-env-unset",
4024
+ tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4025
+ }, { cwd });
4026
+ assert.equal(result.omxEventName, "pre-tool-use");
4027
+ assert.equal(result.outputJson, null);
4028
+ });
4029
+ });
4030
+ it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
3215
4031
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
3216
4032
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3217
4033
  try {
3218
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4034
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3219
4035
  const result = await dispatchCodexNativeHook({
3220
4036
  hook_event_name: "PreToolUse",
3221
4037
  cwd,
@@ -3224,8 +4040,7 @@ exit 0
3224
4040
  tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
3225
4041
  }, { cwd });
3226
4042
  assert.equal(result.omxEventName, "pre-tool-use");
3227
- assert.equal(result.outputJson?.decision, "block");
3228
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4043
+ assert.equal(result.outputJson, null);
3229
4044
  }
3230
4045
  finally {
3231
4046
  if (original === undefined)
@@ -3235,11 +4050,11 @@ exit 0
3235
4050
  await rm(cwd, { recursive: true, force: true });
3236
4051
  }
3237
4052
  });
3238
- it("restores default-on Lore guard when env -i clears a disabled process env", async () => {
4053
+ it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
3239
4054
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
3240
4055
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3241
4056
  try {
3242
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4057
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3243
4058
  const result = await dispatchCodexNativeHook({
3244
4059
  hook_event_name: "PreToolUse",
3245
4060
  cwd,
@@ -3248,8 +4063,7 @@ exit 0
3248
4063
  tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
3249
4064
  }, { cwd });
3250
4065
  assert.equal(result.omxEventName, "pre-tool-use");
3251
- assert.equal(result.outputJson?.decision, "block");
3252
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4066
+ assert.equal(result.outputJson, null);
3253
4067
  }
3254
4068
  finally {
3255
4069
  if (original === undefined)
@@ -3259,7 +4073,7 @@ exit 0
3259
4073
  await rm(cwd, { recursive: true, force: true });
3260
4074
  }
3261
4075
  });
3262
- it("keeps Lore commit enforcement enabled for unknown inline guard values", async () => {
4076
+ it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
3263
4077
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
3264
4078
  try {
3265
4079
  const result = await dispatchCodexNativeHook({
@@ -3270,8 +4084,7 @@ exit 0
3270
4084
  tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
3271
4085
  }, { cwd });
3272
4086
  assert.equal(result.omxEventName, "pre-tool-use");
3273
- assert.equal(result.outputJson?.decision, "block");
3274
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4087
+ assert.equal(result.outputJson, null);
3275
4088
  }
3276
4089
  finally {
3277
4090
  await rm(cwd, { recursive: true, force: true });
@@ -3300,7 +4113,7 @@ exit 0
3300
4113
  await rm(cwd, { recursive: true, force: true });
3301
4114
  }
3302
4115
  });
3303
- it("keeps Lore commit enforcement enabled for unknown guard values", async () => {
4116
+ it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
3304
4117
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
3305
4118
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3306
4119
  try {
@@ -3313,8 +4126,7 @@ exit 0
3313
4126
  tool_input: { command: 'git commit -m "fix tests"' },
3314
4127
  }, { cwd });
3315
4128
  assert.equal(result.omxEventName, "pre-tool-use");
3316
- assert.equal(result.outputJson?.decision, "block");
3317
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4129
+ assert.equal(result.outputJson, null);
3318
4130
  }
3319
4131
  finally {
3320
4132
  if (original === undefined)
@@ -3407,7 +4219,7 @@ exit 0
3407
4219
  cwd,
3408
4220
  tool_name: "Bash",
3409
4221
  tool_use_id: "tool-git-commit-env-invalid",
3410
- tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
4222
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
3411
4223
  }, { cwd });
3412
4224
  assert.equal(result.omxEventName, "pre-tool-use");
3413
4225
  assert.deepEqual(result.outputJson, {
@@ -3437,7 +4249,7 @@ exit 0
3437
4249
  cwd,
3438
4250
  tool_name: "Bash",
3439
4251
  tool_use_id: "tool-git-commit-option-invalid",
3440
- tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
4252
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
3441
4253
  }, { cwd });
3442
4254
  assert.equal(result.omxEventName, "pre-tool-use");
3443
4255
  assert.deepEqual(result.outputJson, {
@@ -3467,7 +4279,7 @@ exit 0
3467
4279
  cwd,
3468
4280
  tool_name: "Bash",
3469
4281
  tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
3470
- tool_input: { command: 'env git.exe commit -m "fix tests"' },
4282
+ tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
3471
4283
  }, { cwd });
3472
4284
  assert.equal(result.omxEventName, "pre-tool-use");
3473
4285
  assert.deepEqual(result.outputJson, {
@@ -3497,7 +4309,7 @@ exit 0
3497
4309
  cwd,
3498
4310
  tool_name: "Bash",
3499
4311
  tool_use_id: "tool-git-exe-commit-invalid",
3500
- tool_input: { command: 'git.exe commit -m "fix tests"' },
4312
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
3501
4313
  }, { cwd });
3502
4314
  assert.equal(result.omxEventName, "pre-tool-use");
3503
4315
  assert.deepEqual(result.outputJson, {
@@ -3527,7 +4339,7 @@ exit 0
3527
4339
  cwd,
3528
4340
  tool_name: "Bash",
3529
4341
  tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid",
3530
- tool_input: { command: 'env -i PATH=/usr/bin git.exe commit -m "fix tests"' },
4342
+ tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
3531
4343
  }, { cwd });
3532
4344
  assert.equal(result.omxEventName, "pre-tool-use");
3533
4345
  assert.deepEqual(result.outputJson, {
@@ -3557,7 +4369,7 @@ exit 0
3557
4369
  cwd,
3558
4370
  tool_name: "Bash",
3559
4371
  tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
3560
- tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
4372
+ tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
3561
4373
  }, { cwd });
3562
4374
  assert.equal(result.omxEventName, "pre-tool-use");
3563
4375
  assert.deepEqual(result.outputJson, {
@@ -3587,7 +4399,7 @@ exit 0
3587
4399
  cwd,
3588
4400
  tool_name: "Bash",
3589
4401
  tool_use_id: "tool-git-exe-commit-windows-path-invalid",
3590
- tool_input: { command: '"C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
4402
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
3591
4403
  }, { cwd });
3592
4404
  assert.equal(result.omxEventName, "pre-tool-use");
3593
4405
  assert.deepEqual(result.outputJson, {
@@ -3617,7 +4429,7 @@ exit 0
3617
4429
  cwd,
3618
4430
  tool_name: "Bash",
3619
4431
  tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid",
3620
- tool_input: { command: '"C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
4432
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
3621
4433
  }, { cwd });
3622
4434
  assert.equal(result.omxEventName, "pre-tool-use");
3623
4435
  assert.deepEqual(result.outputJson, {
@@ -3647,7 +4459,7 @@ exit 0
3647
4459
  cwd,
3648
4460
  tool_name: "Bash",
3649
4461
  tool_use_id: "tool-git-commit-path-invalid",
3650
- tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
4462
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
3651
4463
  }, { cwd });
3652
4464
  assert.equal(result.omxEventName, "pre-tool-use");
3653
4465
  assert.deepEqual(result.outputJson, {
@@ -3677,7 +4489,7 @@ exit 0
3677
4489
  cwd,
3678
4490
  tool_name: "Bash",
3679
4491
  tool_use_id: "tool-git-commit-file",
3680
- tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
4492
+ tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
3681
4493
  }, { cwd });
3682
4494
  assert.equal(result.omxEventName, "pre-tool-use");
3683
4495
  assert.deepEqual(result.outputJson, {
@@ -3706,7 +4518,7 @@ exit 0
3706
4518
  tool_use_id: "tool-git-commit-missing-omx-coauthor",
3707
4519
  tool_input: {
3708
4520
  command: [
3709
- 'git commit',
4521
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
3710
4522
  '-m "Prevent invalid history from bypassing Lore enforcement"',
3711
4523
  '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
3712
4524
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -3741,7 +4553,7 @@ exit 0
3741
4553
  tool_use_id: "tool-git-commit-valid",
3742
4554
  tool_input: {
3743
4555
  command: [
3744
- 'git commit',
4556
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
3745
4557
  '-m "Prevent invalid history from bypassing Lore enforcement"',
3746
4558
  '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
3747
4559
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -3767,7 +4579,7 @@ exit 0
3767
4579
  tool_use_id: "tool-git-commit-compact-coauthor",
3768
4580
  tool_input: {
3769
4581
  command: [
3770
- 'git commit',
4582
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
3771
4583
  '-m "Launch lvisai.xyz intro site"',
3772
4584
  '-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
3773
4585
  ].join(" "),
@@ -3790,7 +4602,7 @@ exit 0
3790
4602
  tool_use_id: "tool-git-commit-compact-trailers",
3791
4603
  tool_input: {
3792
4604
  command: [
3793
- 'git commit',
4605
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
3794
4606
  '-m "Launch lvisai.xyz intro site"',
3795
4607
  '-m "Constraint: Native PreToolUse can only inspect inline Bash command text\nTested: node --test dist/scripts/__tests__/codex-native-hook.test.js\n\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
3796
4608
  ].join(" "),
@@ -3813,7 +4625,7 @@ exit 0
3813
4625
  tool_use_id: "tool-git-commit-compact-no-separator",
3814
4626
  tool_input: {
3815
4627
  command: [
3816
- 'git commit',
4628
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
3817
4629
  '--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
3818
4630
  ].join(" "),
3819
4631
  },
@@ -4622,6 +5434,80 @@ exit 0
4622
5434
  await rm(cwd, { recursive: true, force: true });
4623
5435
  }
4624
5436
  });
5437
+ it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
5438
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
5439
+ try {
5440
+ const stateDir = join(cwd, ".omx", "state");
5441
+ const sessionId = "sess-stop-autopilot-terminal-run-state";
5442
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
5443
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
5444
+ active: true,
5445
+ mode: "autopilot",
5446
+ current_phase: "ralplan",
5447
+ });
5448
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
5449
+ version: 1,
5450
+ active: false,
5451
+ mode: "autopilot",
5452
+ outcome: "finish",
5453
+ lifecycle_outcome: "finished",
5454
+ current_phase: "complete",
5455
+ completed_at: "2026-05-20T11:00:00.000Z",
5456
+ updated_at: "2026-05-20T11:00:00.000Z",
5457
+ });
5458
+ const result = await dispatchCodexNativeHook({
5459
+ hook_event_name: "Stop",
5460
+ cwd,
5461
+ session_id: sessionId,
5462
+ thread_id: "thread-stop-autopilot-terminal-run-state",
5463
+ turn_id: "turn-stop-autopilot-terminal-run-state-1",
5464
+ last_assistant_message: "Done. Verification passed.",
5465
+ }, { cwd });
5466
+ assert.equal(result.omxEventName, "stop");
5467
+ assert.equal(result.outputJson, null);
5468
+ }
5469
+ finally {
5470
+ await rm(cwd, { recursive: true, force: true });
5471
+ }
5472
+ });
5473
+ it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
5474
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
5475
+ try {
5476
+ const stateDir = join(cwd, ".omx", "state");
5477
+ const sessionId = "sess-stop-autopilot-active-ralplan";
5478
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
5479
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
5480
+ active: true,
5481
+ mode: "autopilot",
5482
+ current_phase: "ralplan",
5483
+ });
5484
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
5485
+ version: 1,
5486
+ active: true,
5487
+ mode: "autopilot",
5488
+ outcome: "continue",
5489
+ current_phase: "ralplan",
5490
+ updated_at: "2026-05-20T11:00:00.000Z",
5491
+ });
5492
+ const result = await dispatchCodexNativeHook({
5493
+ hook_event_name: "Stop",
5494
+ cwd,
5495
+ session_id: sessionId,
5496
+ thread_id: "thread-stop-autopilot-active-ralplan",
5497
+ turn_id: "turn-stop-autopilot-active-ralplan-1",
5498
+ }, { cwd });
5499
+ assert.equal(result.omxEventName, "stop");
5500
+ assert.deepEqual(result.outputJson, {
5501
+ decision: "block",
5502
+ reason: "OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
5503
+ stopReason: "autopilot_ralplan",
5504
+ systemMessage: "OMX autopilot is still active (phase: ralplan).",
5505
+ });
5506
+ }
5507
+ finally {
5508
+ await rm(cwd, { recursive: true, force: true });
5509
+ }
5510
+ });
4625
5511
  it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
4626
5512
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
4627
5513
  try {
@@ -4772,6 +5658,7 @@ exit 0
4772
5658
  active: true,
4773
5659
  current_phase: "team-exec",
4774
5660
  team_name: "review-team",
5661
+ session_id: "sess-stop-team",
4775
5662
  });
4776
5663
  await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
4777
5664
  current_phase: "team-verify",
@@ -5544,19 +6431,76 @@ exit 0
5544
6431
  await rm(cwd, { recursive: true, force: true });
5545
6432
  }
5546
6433
  });
5547
- it("returns Stop continuation output from canonical team state when coarse mode state is missing", async () => {
5548
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-"));
6434
+ it("returns Stop continuation output from canonical team state when coarse mode state is missing", async () => {
6435
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-"));
6436
+ try {
6437
+ await initTeamState("canonical-team", "canonical stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical" });
6438
+ const result = await dispatchCodexNativeHook({
6439
+ hook_event_name: "Stop",
6440
+ cwd,
6441
+ session_id: "sess-stop-team-canonical",
6442
+ }, { cwd });
6443
+ assert.equal(result.omxEventName, "stop");
6444
+ assert.deepEqual(result.outputJson, {
6445
+ decision: "block",
6446
+ reason: `OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
6447
+ stopReason: "team_team-exec",
6448
+ systemMessage: "OMX team pipeline is still active at phase team-exec.",
6449
+ });
6450
+ }
6451
+ finally {
6452
+ await rm(cwd, { recursive: true, force: true });
6453
+ }
6454
+ });
6455
+ it("does not block Stop from canonical team state owned by another thread", async () => {
6456
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-"));
6457
+ try {
6458
+ await initTeamState("canonical-other-thread-team", "canonical other-thread stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-thread" });
6459
+ const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json");
6460
+ const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
6461
+ await writeJson(manifestPath, {
6462
+ ...manifest,
6463
+ leader: {
6464
+ ...manifest.leader,
6465
+ thread_id: "thread-other",
6466
+ },
6467
+ });
6468
+ const result = await dispatchCodexNativeHook({
6469
+ hook_event_name: "Stop",
6470
+ cwd,
6471
+ session_id: "sess-stop-team-canonical-thread",
6472
+ thread_id: "thread-current",
6473
+ }, { cwd });
6474
+ assert.equal(result.omxEventName, "stop");
6475
+ assert.equal(result.outputJson, null);
6476
+ }
6477
+ finally {
6478
+ await rm(cwd, { recursive: true, force: true });
6479
+ }
6480
+ });
6481
+ it("blocks Stop from canonical team state owned by the current thread", async () => {
6482
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-"));
5549
6483
  try {
5550
- await initTeamState("canonical-team", "canonical stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical" });
6484
+ await initTeamState("canonical-current-thread-team", "canonical current-thread stop fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-current-thread" });
6485
+ const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json");
6486
+ const manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
6487
+ await writeJson(manifestPath, {
6488
+ ...manifest,
6489
+ leader: {
6490
+ ...manifest.leader,
6491
+ thread_id: "thread-current",
6492
+ },
6493
+ });
5551
6494
  const result = await dispatchCodexNativeHook({
5552
6495
  hook_event_name: "Stop",
5553
6496
  cwd,
5554
- session_id: "sess-stop-team-canonical",
6497
+ session_id: "sess-stop-team-canonical-current-thread",
6498
+ thread_id: "thread-current",
5555
6499
  }, { cwd });
5556
6500
  assert.equal(result.omxEventName, "stop");
5557
6501
  assert.deepEqual(result.outputJson, {
5558
6502
  decision: "block",
5559
- reason: `OMX team pipeline is still active (canonical-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
6503
+ reason: `OMX team pipeline is still active (canonical-current-thread-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
5560
6504
  stopReason: "team_team-exec",
5561
6505
  systemMessage: "OMX team pipeline is still active at phase team-exec.",
5562
6506
  });
@@ -5738,8 +6682,8 @@ exit 0
5738
6682
  const sharedRoot = join(cwd, "shared-root");
5739
6683
  const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
5740
6684
  try {
5741
- process.env.OMX_TEAM_STATE_ROOT = "shared-root";
5742
- await initTeamState("canonical-root-team", "canonical stop root fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: "shared-root" });
6685
+ process.env.OMX_TEAM_STATE_ROOT = sharedRoot;
6686
+ await initTeamState("canonical-root-team", "canonical stop root fallback", "executor", 1, cwd, undefined, { ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: sharedRoot });
5743
6687
  const result = await dispatchCodexNativeHook({
5744
6688
  hook_event_name: "Stop",
5745
6689
  cwd,
@@ -5796,13 +6740,14 @@ exit 0
5796
6740
  });
5797
6741
  it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
5798
6742
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
6743
+ const teamStateRoot = join(cwd, "shared-team-state");
5799
6744
  const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
5800
6745
  try {
5801
- process.env.OMX_TEAM_STATE_ROOT = "shared-team-state";
6746
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
5802
6747
  await initTeamState("env-root-team", "env root stop fallback", "executor", 1, cwd, undefined, {
5803
6748
  ...process.env,
5804
6749
  OMX_SESSION_ID: "sess-stop-team-env-root",
5805
- OMX_TEAM_STATE_ROOT: "shared-team-state",
6750
+ OMX_TEAM_STATE_ROOT: teamStateRoot,
5806
6751
  });
5807
6752
  const result = await dispatchCodexNativeHook({
5808
6753
  hook_event_name: "Stop",
@@ -6961,16 +7906,25 @@ exit 0
6961
7906
  assert.equal(result.omxEventName, "stop");
6962
7907
  const reason = String(result.outputJson?.reason);
6963
7908
  assert.match(reason, /Ralph completion audit is missing required evidence/);
6964
- assert.match(reason, /state\.completion_audit = \{ passed: true, prompt_to_artifact_checklist: \[\.\.\.\], verification_evidence: \[\.\.\.\] \}/);
7909
+ assert.match(reason, /set "completion_audit" on the Ralph state object/);
7910
+ assert.doesNotMatch(reason, /state\.completion_audit/);
6965
7911
  assert.match(reason, /repo-relative JSON file/);
6966
7912
  assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
6967
7913
  assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
6968
7914
  const reopened = JSON.parse(await readFile(statePath, "utf-8"));
6969
- assert.equal(reopened.active, true);
6970
- assert.equal(reopened.current_phase, "verifying");
7915
+ assert.equal(reopened.active, false);
7916
+ assert.equal(reopened.current_phase, "complete");
6971
7917
  assert.equal(reopened.completion_audit_gate, "blocked");
6972
7918
  assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
6973
- assert.equal(typeof reopened.completed_at, "undefined");
7919
+ assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
7920
+ const repeat = await dispatchCodexNativeHook({
7921
+ hook_event_name: "Stop",
7922
+ cwd,
7923
+ session_id: sessionId,
7924
+ last_assistant_message: "Done. Ralph complete.",
7925
+ }, { cwd });
7926
+ assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
7927
+ assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
6974
7928
  }
6975
7929
  finally {
6976
7930
  await rm(cwd, { recursive: true, force: true });
@@ -8531,6 +9485,8 @@ exit 0
8531
9485
  active: true,
8532
9486
  current_phase: "team-exec",
8533
9487
  team_name: "review-team",
9488
+ session_id: "sess-stop-team-refire",
9489
+ thread_id: "thread-stop-team-refire",
8534
9490
  });
8535
9491
  await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
8536
9492
  current_phase: "team-verify",
@@ -8719,6 +9675,214 @@ exit 0
8719
9675
  await rm(cwd, { recursive: true, force: true });
8720
9676
  }
8721
9677
  });
9678
+ it("does not block Stop from root team state without team_name when no session is known", async () => {
9679
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
9680
+ try {
9681
+ const stateDir = join(cwd, ".omx", "state");
9682
+ await mkdir(stateDir, { recursive: true });
9683
+ await writeJson(join(stateDir, "team-state.json"), {
9684
+ active: true,
9685
+ mode: "team",
9686
+ current_phase: "starting",
9687
+ });
9688
+ const result = await dispatchCodexNativeHook({
9689
+ hook_event_name: "Stop",
9690
+ cwd,
9691
+ }, { cwd });
9692
+ assert.equal(result.omxEventName, "stop");
9693
+ assert.equal(result.outputJson, null);
9694
+ }
9695
+ finally {
9696
+ await rm(cwd, { recursive: true, force: true });
9697
+ }
9698
+ });
9699
+ it("does not block Stop from root team state without team_name for a foreign session", async () => {
9700
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
9701
+ try {
9702
+ const stateDir = join(cwd, ".omx", "state");
9703
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
9704
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
9705
+ await writeJson(join(stateDir, "team-state.json"), {
9706
+ active: true,
9707
+ mode: "team",
9708
+ current_phase: "starting",
9709
+ });
9710
+ const result = await dispatchCodexNativeHook({
9711
+ hook_event_name: "Stop",
9712
+ cwd,
9713
+ session_id: "sess-current",
9714
+ thread_id: "thread-current",
9715
+ }, { cwd });
9716
+ assert.equal(result.omxEventName, "stop");
9717
+ assert.equal(result.outputJson, null);
9718
+ }
9719
+ finally {
9720
+ await rm(cwd, { recursive: true, force: true });
9721
+ }
9722
+ });
9723
+ it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => {
9724
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-"));
9725
+ try {
9726
+ const stateDir = join(cwd, ".omx", "state");
9727
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
9728
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
9729
+ await writeJson(join(stateDir, "team-state.json"), {
9730
+ active: true,
9731
+ current_phase: "starting",
9732
+ team_name: "stale-root-thread-team",
9733
+ session_id: "sess-current",
9734
+ thread_id: "thread-other",
9735
+ });
9736
+ await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), {
9737
+ current_phase: "team-exec",
9738
+ max_fix_attempts: 3,
9739
+ current_fix_attempt: 0,
9740
+ transitions: [],
9741
+ updated_at: new Date().toISOString(),
9742
+ });
9743
+ const result = await dispatchCodexNativeHook({
9744
+ hook_event_name: "Stop",
9745
+ cwd,
9746
+ session_id: "sess-current",
9747
+ thread_id: "thread-current",
9748
+ }, { cwd });
9749
+ assert.equal(result.omxEventName, "stop");
9750
+ assert.equal(result.outputJson, null);
9751
+ }
9752
+ finally {
9753
+ await rm(cwd, { recursive: true, force: true });
9754
+ }
9755
+ });
9756
+ it("does not block Stop from root team state with matching session but missing thread ownership", async () => {
9757
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-"));
9758
+ try {
9759
+ const stateDir = join(cwd, ".omx", "state");
9760
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
9761
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
9762
+ await writeJson(join(stateDir, "team-state.json"), {
9763
+ active: true,
9764
+ current_phase: "starting",
9765
+ team_name: "root-missing-thread-team",
9766
+ session_id: "sess-current",
9767
+ });
9768
+ await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), {
9769
+ current_phase: "team-exec",
9770
+ max_fix_attempts: 3,
9771
+ current_fix_attempt: 0,
9772
+ transitions: [],
9773
+ updated_at: new Date().toISOString(),
9774
+ });
9775
+ const result = await dispatchCodexNativeHook({
9776
+ hook_event_name: "Stop",
9777
+ cwd,
9778
+ session_id: "sess-current",
9779
+ thread_id: "thread-current",
9780
+ }, { cwd });
9781
+ assert.equal(result.omxEventName, "stop");
9782
+ assert.equal(result.outputJson, null);
9783
+ }
9784
+ finally {
9785
+ await rm(cwd, { recursive: true, force: true });
9786
+ }
9787
+ });
9788
+ it("does not block Stop from root team state when canonical phase is missing", async () => {
9789
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-"));
9790
+ try {
9791
+ const stateDir = join(cwd, ".omx", "state");
9792
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
9793
+ await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true });
9794
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
9795
+ await writeJson(join(stateDir, "team-state.json"), {
9796
+ active: true,
9797
+ current_phase: "starting",
9798
+ team_name: "root-missing-phase-team",
9799
+ session_id: "sess-current",
9800
+ thread_id: "thread-current",
9801
+ });
9802
+ const result = await dispatchCodexNativeHook({
9803
+ hook_event_name: "Stop",
9804
+ cwd,
9805
+ session_id: "sess-current",
9806
+ thread_id: "thread-current",
9807
+ }, { cwd });
9808
+ assert.equal(result.omxEventName, "stop");
9809
+ assert.equal(result.outputJson, null);
9810
+ }
9811
+ finally {
9812
+ await rm(cwd, { recursive: true, force: true });
9813
+ }
9814
+ });
9815
+ it("does not block Stop from session-scoped team state owned by another thread", async () => {
9816
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-"));
9817
+ try {
9818
+ const stateDir = join(cwd, ".omx", "state");
9819
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
9820
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
9821
+ await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
9822
+ active: true,
9823
+ current_phase: "starting",
9824
+ team_name: "scoped-other-thread-team",
9825
+ session_id: "sess-current",
9826
+ thread_id: "thread-other",
9827
+ });
9828
+ await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), {
9829
+ current_phase: "team-exec",
9830
+ max_fix_attempts: 3,
9831
+ current_fix_attempt: 0,
9832
+ transitions: [],
9833
+ updated_at: new Date().toISOString(),
9834
+ });
9835
+ const result = await dispatchCodexNativeHook({
9836
+ hook_event_name: "Stop",
9837
+ cwd,
9838
+ session_id: "sess-current",
9839
+ thread_id: "thread-current",
9840
+ }, { cwd });
9841
+ assert.equal(result.omxEventName, "stop");
9842
+ assert.equal(result.outputJson, null);
9843
+ }
9844
+ finally {
9845
+ await rm(cwd, { recursive: true, force: true });
9846
+ }
9847
+ });
9848
+ it("blocks Stop from session-scoped team state owned by the current session and thread", async () => {
9849
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-"));
9850
+ try {
9851
+ const stateDir = join(cwd, ".omx", "state");
9852
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
9853
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
9854
+ await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
9855
+ active: true,
9856
+ current_phase: "starting",
9857
+ team_name: "scoped-current-team",
9858
+ session_id: "sess-current",
9859
+ thread_id: "thread-current",
9860
+ });
9861
+ await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), {
9862
+ current_phase: "team-exec",
9863
+ max_fix_attempts: 3,
9864
+ current_fix_attempt: 0,
9865
+ transitions: [],
9866
+ updated_at: new Date().toISOString(),
9867
+ });
9868
+ const result = await dispatchCodexNativeHook({
9869
+ hook_event_name: "Stop",
9870
+ cwd,
9871
+ session_id: "sess-current",
9872
+ thread_id: "thread-current",
9873
+ }, { cwd });
9874
+ assert.equal(result.omxEventName, "stop");
9875
+ assert.deepEqual(result.outputJson, {
9876
+ decision: "block",
9877
+ reason: `OMX team pipeline is still active (scoped-current-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
9878
+ stopReason: "team_team-exec",
9879
+ systemMessage: "OMX team pipeline is still active at phase team-exec.",
9880
+ });
9881
+ }
9882
+ finally {
9883
+ await rm(cwd, { recursive: true, force: true });
9884
+ }
9885
+ });
8722
9886
  it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
8723
9887
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
8724
9888
  try {
@@ -8901,6 +10065,79 @@ describe("codex native hook triage integration", () => {
8901
10065
  await rm(cwd, { recursive: true, force: true });
8902
10066
  }
8903
10067
  });
10068
+ it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => {
10069
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-"));
10070
+ const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-"));
10071
+ const originalOmxRoot = process.env.OMX_ROOT;
10072
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
10073
+ const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
10074
+ try {
10075
+ process.env.OMX_ROOT = boxedRoot;
10076
+ delete process.env.OMX_STATE_ROOT;
10077
+ delete process.env.OMX_TEAM_STATE_ROOT;
10078
+ const boxedStateDir = getBaseStateDir(cwd);
10079
+ await mkdir(boxedStateDir, { recursive: true });
10080
+ await writeJson(join(boxedStateDir, "subagent-tracking.json"), {
10081
+ schemaVersion: 1,
10082
+ sessions: {
10083
+ "omx-parent-session": {
10084
+ session_id: "omx-parent-session",
10085
+ leader_thread_id: "parent-native-thread",
10086
+ updated_at: "2026-05-21T19:04:40.000Z",
10087
+ threads: {
10088
+ "parent-native-thread": {
10089
+ thread_id: "parent-native-thread",
10090
+ kind: "leader",
10091
+ first_seen_at: "2026-05-21T19:04:40.000Z",
10092
+ last_seen_at: "2026-05-21T19:04:40.000Z",
10093
+ turn_count: 1,
10094
+ },
10095
+ "child-native-session": {
10096
+ thread_id: "child-native-session",
10097
+ kind: "subagent",
10098
+ first_seen_at: "2026-05-21T19:04:41.000Z",
10099
+ last_seen_at: "2026-05-21T19:04:41.000Z",
10100
+ turn_count: 1,
10101
+ mode: "review",
10102
+ },
10103
+ },
10104
+ },
10105
+ },
10106
+ });
10107
+ const result = await dispatchCodexNativeHook({
10108
+ hook_event_name: "UserPromptSubmit",
10109
+ cwd,
10110
+ session_id: "child-native-session",
10111
+ thread_id: "child-native-session",
10112
+ turn_id: "turn-subagent-review",
10113
+ prompt: [
10114
+ "Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.",
10115
+ "Context: The user asked for $autopilot, and this subagent must only review the patch.",
10116
+ ].join("\n\n"),
10117
+ }, { cwd });
10118
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
10119
+ assert.equal(additionalContext, "");
10120
+ assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")), false);
10121
+ assert.equal(existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")), false);
10122
+ assert.equal(existsSync(join(cwd, ".omx", "state", "subagent-tracking.json")), false, "subagent tracking must not leak into the source worktree when OMX_ROOT is boxed");
10123
+ }
10124
+ finally {
10125
+ if (originalOmxRoot === undefined)
10126
+ delete process.env.OMX_ROOT;
10127
+ else
10128
+ process.env.OMX_ROOT = originalOmxRoot;
10129
+ if (originalOmxStateRoot === undefined)
10130
+ delete process.env.OMX_STATE_ROOT;
10131
+ else
10132
+ process.env.OMX_STATE_ROOT = originalOmxStateRoot;
10133
+ if (originalTeamStateRoot === undefined)
10134
+ delete process.env.OMX_TEAM_STATE_ROOT;
10135
+ else
10136
+ process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
10137
+ await rm(cwd, { recursive: true, force: true });
10138
+ await rm(boxedRoot, { recursive: true, force: true });
10139
+ }
10140
+ });
8904
10141
  it("does not inject triage advisory for autopilot keyword prompts", async () => {
8905
10142
  const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-"));
8906
10143
  try {
@@ -8925,6 +10162,48 @@ describe("codex native hook triage integration", () => {
8925
10162
  await rm(cwd, { recursive: true, force: true });
8926
10163
  }
8927
10164
  });
10165
+ it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => {
10166
+ const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-"));
10167
+ try {
10168
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
10169
+ await writeSessionStart(cwd, "sess-autopilot-observable");
10170
+ const result = await dispatchCodexNativeHook({
10171
+ hook_event_name: "UserPromptSubmit",
10172
+ cwd,
10173
+ session_id: "sess-autopilot-observable",
10174
+ thread_id: "thread-autopilot-observable",
10175
+ turn_id: "turn-autopilot-observable",
10176
+ prompt: "$autopilot implement issue #2430",
10177
+ }, { cwd });
10178
+ assert.equal(result.skillState?.skill, "autopilot");
10179
+ assert.equal(result.skillState?.phase, "deep-interview");
10180
+ assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json");
10181
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
10182
+ assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
10183
+ assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/);
10184
+ assert.match(additionalContext, /deep_interview_gate\.skip_reason/);
10185
+ assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/);
10186
+ assert.match(additionalContext, /Codex goal-mode handoff guidance/);
10187
+ assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/);
10188
+ const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json");
10189
+ const modeState = JSON.parse(await readFile(statePath, "utf-8"));
10190
+ assert.equal(modeState.active, true);
10191
+ assert.equal(modeState.current_phase, "deep-interview");
10192
+ assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]);
10193
+ assert.deepEqual(modeState.state?.deep_interview_gate, {
10194
+ status: "required",
10195
+ skip_reason: null,
10196
+ rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.",
10197
+ });
10198
+ const hudState = await readAllState(cwd);
10199
+ assert.equal(hudState.autopilot?.active, true);
10200
+ assert.equal(hudState.autopilot?.current_phase, "deep-interview");
10201
+ assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/);
10202
+ }
10203
+ finally {
10204
+ await rm(cwd, { recursive: true, force: true });
10205
+ }
10206
+ });
8928
10207
  // ── Group 2: HEAVY injection ─────────────────────────────────────────────
8929
10208
  it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => {
8930
10209
  const cwd = await mkdtemp(join(tmpdir(), "omx-triage-heavy-"));