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
@@ -26,8 +26,11 @@ import { resetTriageConfigCache } from "../../hooks/triage-config.js";
26
26
  import { executeStateOperation } from "../../state/operations.js";
27
27
  import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
28
28
  import { readAllState } from "../../hud/state.js";
29
+ import { renderHud } from "../../hud/render.js";
29
30
  import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
30
31
  import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
32
+ import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
33
+ import { getBaseStateDir } from "../../state/paths.js";
31
34
 
32
35
  function nativeHookScriptPath(): string {
33
36
  return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
@@ -62,6 +65,40 @@ async function writeJson(path: string, value: unknown): Promise<void> {
62
65
  await writeFile(path, JSON.stringify(value, null, 2));
63
66
  }
64
67
 
68
+ async function withLoreGuardConfig<T>(
69
+ value: string,
70
+ prefix: string,
71
+ run: (cwd: string) => Promise<T>,
72
+ ): Promise<T> {
73
+ const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
74
+ const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
75
+ const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
76
+ const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
77
+ const originalCodexHome = process.env.CODEX_HOME;
78
+ const originalHome = process.env.HOME;
79
+ try {
80
+ delete process.env.OMX_LORE_COMMIT_GUARD;
81
+ process.env.CODEX_HOME = codexHome;
82
+ process.env.HOME = defaultHome;
83
+ await writeFile(
84
+ join(codexHome, "config.toml"),
85
+ `[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`,
86
+ "utf-8",
87
+ );
88
+ return await run(cwd);
89
+ } finally {
90
+ if (originalGuard === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
91
+ else process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
92
+ if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
93
+ else process.env.CODEX_HOME = originalCodexHome;
94
+ if (originalHome === undefined) delete process.env.HOME;
95
+ else process.env.HOME = originalHome;
96
+ await rm(cwd, { recursive: true, force: true });
97
+ await rm(codexHome, { recursive: true, force: true });
98
+ await rm(defaultHome, { recursive: true, force: true });
99
+ }
100
+ }
101
+
65
102
  function buildWorkerStopFakeTmux(
66
103
  tmuxLogPath: string,
67
104
  options: { failSend?: boolean; busyLeader?: boolean } = {},
@@ -250,7 +287,7 @@ describe("codex native hook config", () => {
250
287
  matcher?: string;
251
288
  hooks?: Array<Record<string, unknown>>;
252
289
  };
253
- assert.equal(preToolUse.matcher, "Bash");
290
+ assert.equal(preToolUse.matcher, undefined);
254
291
  assert.match(
255
292
  String(preToolUse.hooks?.[0]?.command || ""),
256
293
  /codex-native-hook\.js"?$/,
@@ -729,7 +766,16 @@ describe("codex native hook dispatch", () => {
729
766
 
730
767
  it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
731
768
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
769
+ const originalCodexHome = process.env.CODEX_HOME;
732
770
  try {
771
+ process.env.CODEX_HOME = join(cwd, "codex-home");
772
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
773
+ notifications: {
774
+ enabled: true,
775
+ verbosity: "session",
776
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
777
+ },
778
+ });
733
779
  const stateDir = join(cwd, ".omx", "state");
734
780
  const canonicalSessionId = "omx-leader-session";
735
781
  const leaderNativeSessionId = "codex-leader-thread";
@@ -745,6 +791,16 @@ describe("codex native hook dispatch", () => {
745
791
  iteration: 1,
746
792
  max_iterations: 5,
747
793
  });
794
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
795
+ await writeFile(
796
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
797
+ [
798
+ "import { appendFileSync } from 'node:fs';",
799
+ "export async function onHookEvent(event) {",
800
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
801
+ "}",
802
+ ].join("\n"),
803
+ );
748
804
  const transcriptPath = join(cwd, "subagent-rollout.jsonl");
749
805
  await writeFile(
750
806
  transcriptPath,
@@ -794,6 +850,11 @@ describe("codex native hook dispatch", () => {
794
850
  ) as { active?: boolean; current_phase?: string };
795
851
  assert.equal(leaderRalph.active, true);
796
852
  assert.equal(leaderRalph.current_phase, "executing");
853
+ assert.equal(
854
+ existsSync(join(cwd, "hook-events.jsonl")),
855
+ false,
856
+ "subagent SessionStart must not independently dispatch session-start hook notifications",
857
+ );
797
858
 
798
859
  const tracking = JSON.parse(
799
860
  await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"),
@@ -809,7 +870,257 @@ describe("codex native hook dispatch", () => {
809
870
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
810
871
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
811
872
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
873
+
874
+ await dispatchCodexNativeHook(
875
+ {
876
+ hook_event_name: "Stop",
877
+ cwd,
878
+ session_id: childNativeSessionId,
879
+ thread_id: childNativeSessionId,
880
+ turn_id: "child-stop-turn",
881
+ },
882
+ { cwd },
883
+ );
884
+ assert.equal(
885
+ existsSync(join(cwd, "hook-events.jsonl")),
886
+ false,
887
+ "subagent Stop must not independently dispatch stop hook notifications",
888
+ );
889
+ } finally {
890
+ if (originalCodexHome === undefined) {
891
+ delete process.env.CODEX_HOME;
892
+ } else {
893
+ process.env.CODEX_HOME = originalCodexHome;
894
+ }
895
+ await rm(cwd, { recursive: true, force: true });
896
+ }
897
+ });
898
+
899
+ it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
900
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
901
+ const originalCodexHome = process.env.CODEX_HOME;
902
+ try {
903
+ process.env.CODEX_HOME = join(cwd, "codex-home");
904
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
905
+ notifications: {
906
+ enabled: true,
907
+ verbosity: "minimal",
908
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
909
+ },
910
+ });
911
+ const stateDir = join(cwd, ".omx", "state");
912
+ const canonicalSessionId = "omx-leader-session-minimal";
913
+ const leaderNativeSessionId = "codex-leader-thread-minimal";
914
+ const childNativeSessionId = "codex-child-thread-minimal";
915
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
916
+ await writeSessionStart(cwd, canonicalSessionId, {
917
+ nativeSessionId: leaderNativeSessionId,
918
+ });
919
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
920
+ await writeFile(
921
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
922
+ [
923
+ "import { appendFileSync } from 'node:fs';",
924
+ "export async function onHookEvent(event) {",
925
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
926
+ "}",
927
+ ].join("\n"),
928
+ );
929
+ const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
930
+ await writeFile(
931
+ transcriptPath,
932
+ `${JSON.stringify({
933
+ type: "session_meta",
934
+ payload: {
935
+ id: childNativeSessionId,
936
+ source: {
937
+ subagent: {
938
+ thread_spawn: {
939
+ parent_thread_id: leaderNativeSessionId,
940
+ agent_role: "verifier",
941
+ },
942
+ },
943
+ },
944
+ },
945
+ })}\n`,
946
+ );
947
+
948
+ await dispatchCodexNativeHook(
949
+ {
950
+ hook_event_name: "SessionStart",
951
+ cwd,
952
+ session_id: childNativeSessionId,
953
+ transcript_path: transcriptPath,
954
+ },
955
+ { cwd, sessionOwnerPid: process.pid },
956
+ );
957
+
958
+ assert.equal(
959
+ existsSync(join(cwd, "hook-events.jsonl")),
960
+ false,
961
+ "subagent SessionStart must be suppressed at minimal verbosity",
962
+ );
963
+ } finally {
964
+ if (originalCodexHome === undefined) {
965
+ delete process.env.CODEX_HOME;
966
+ } else {
967
+ process.env.CODEX_HOME = originalCodexHome;
968
+ }
969
+ await rm(cwd, { recursive: true, force: true });
970
+ }
971
+ });
972
+
973
+ it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
974
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
975
+ const originalCodexHome = process.env.CODEX_HOME;
976
+ try {
977
+ process.env.CODEX_HOME = join(cwd, "codex-home");
978
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
979
+ notifications: {
980
+ enabled: true,
981
+ verbosity: "session",
982
+ includeChildAgents: true,
983
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
984
+ },
985
+ });
986
+ const stateDir = join(cwd, ".omx", "state");
987
+ const canonicalSessionId = "omx-leader-session-include";
988
+ const leaderNativeSessionId = "codex-leader-thread-include";
989
+ const childNativeSessionId = "codex-child-thread-include";
990
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
991
+ await writeSessionStart(cwd, canonicalSessionId, {
992
+ nativeSessionId: leaderNativeSessionId,
993
+ });
994
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
995
+ await writeFile(
996
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
997
+ [
998
+ "import { appendFileSync } from 'node:fs';",
999
+ "export async function onHookEvent(event) {",
1000
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
1001
+ "}",
1002
+ ].join("\n"),
1003
+ );
1004
+ const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
1005
+ await writeFile(
1006
+ transcriptPath,
1007
+ `${JSON.stringify({
1008
+ type: "session_meta",
1009
+ payload: {
1010
+ id: childNativeSessionId,
1011
+ source: {
1012
+ subagent: {
1013
+ thread_spawn: {
1014
+ parent_thread_id: leaderNativeSessionId,
1015
+ agent_role: "verifier",
1016
+ },
1017
+ },
1018
+ },
1019
+ },
1020
+ })}\n`,
1021
+ );
1022
+
1023
+ await dispatchCodexNativeHook(
1024
+ {
1025
+ hook_event_name: "SessionStart",
1026
+ cwd,
1027
+ session_id: childNativeSessionId,
1028
+ transcript_path: transcriptPath,
1029
+ },
1030
+ { cwd, sessionOwnerPid: process.pid },
1031
+ );
1032
+
1033
+ await dispatchCodexNativeHook(
1034
+ {
1035
+ hook_event_name: "Stop",
1036
+ cwd,
1037
+ session_id: childNativeSessionId,
1038
+ thread_id: childNativeSessionId,
1039
+ turn_id: "included-child-stop-turn",
1040
+ },
1041
+ { cwd },
1042
+ );
1043
+
1044
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
1045
+ assert.match(hookEvents, /"event":"session-start"/);
1046
+ assert.match(hookEvents, /"event":"stop"/);
1047
+ } finally {
1048
+ if (originalCodexHome === undefined) {
1049
+ delete process.env.CODEX_HOME;
1050
+ } else {
1051
+ process.env.CODEX_HOME = originalCodexHome;
1052
+ }
1053
+ await rm(cwd, { recursive: true, force: true });
1054
+ }
1055
+ });
1056
+
1057
+ it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
1058
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
1059
+ const originalCodexHome = process.env.CODEX_HOME;
1060
+ try {
1061
+ process.env.CODEX_HOME = join(cwd, "codex-home");
1062
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
1063
+ notifications: {
1064
+ enabled: true,
1065
+ verbosity: "agent",
1066
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
1067
+ },
1068
+ });
1069
+ const stateDir = join(cwd, ".omx", "state");
1070
+ const canonicalSessionId = "omx-leader-session-agent";
1071
+ const leaderNativeSessionId = "codex-leader-thread-agent";
1072
+ const childNativeSessionId = "codex-child-thread-agent";
1073
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
1074
+ await writeSessionStart(cwd, canonicalSessionId, {
1075
+ nativeSessionId: leaderNativeSessionId,
1076
+ });
1077
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
1078
+ await writeFile(
1079
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
1080
+ [
1081
+ "import { appendFileSync } from 'node:fs';",
1082
+ "export async function onHookEvent(event) {",
1083
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
1084
+ "}",
1085
+ ].join("\n"),
1086
+ );
1087
+ const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
1088
+ await writeFile(
1089
+ transcriptPath,
1090
+ `${JSON.stringify({
1091
+ type: "session_meta",
1092
+ payload: {
1093
+ id: childNativeSessionId,
1094
+ source: {
1095
+ subagent: {
1096
+ thread_spawn: {
1097
+ parent_thread_id: leaderNativeSessionId,
1098
+ agent_role: "verifier",
1099
+ },
1100
+ },
1101
+ },
1102
+ },
1103
+ })}\n`,
1104
+ );
1105
+
1106
+ await dispatchCodexNativeHook(
1107
+ {
1108
+ hook_event_name: "SessionStart",
1109
+ cwd,
1110
+ session_id: childNativeSessionId,
1111
+ transcript_path: transcriptPath,
1112
+ },
1113
+ { cwd, sessionOwnerPid: process.pid },
1114
+ );
1115
+
1116
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
1117
+ assert.match(hookEvents, /"event":"session-start"/);
812
1118
  } finally {
1119
+ if (originalCodexHome === undefined) {
1120
+ delete process.env.CODEX_HOME;
1121
+ } else {
1122
+ process.env.CODEX_HOME = originalCodexHome;
1123
+ }
813
1124
  await rm(cwd, { recursive: true, force: true });
814
1125
  }
815
1126
  });
@@ -1498,6 +1809,66 @@ describe("codex native hook dispatch", () => {
1498
1809
  }
1499
1810
  });
1500
1811
 
1812
+ it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
1813
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
1814
+ try {
1815
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1816
+ version: 1,
1817
+ workflow: "performance-goal",
1818
+ slug: "latency",
1819
+ objective: "Reduce latency",
1820
+ status: "blocked",
1821
+ lastValidation: {
1822
+ status: "blocked",
1823
+ evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
1824
+ recordedAt: "2026-05-20T00:00:00.000Z",
1825
+ },
1826
+ });
1827
+
1828
+ const result = await dispatchCodexNativeHook({
1829
+ hook_event_name: "Stop",
1830
+ cwd,
1831
+ session_id: "sess-performance-mismatch-blocked-stop",
1832
+ thread_id: "thread-performance-mismatch-blocked-stop",
1833
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1834
+ }, { cwd });
1835
+
1836
+ assert.notEqual(result.outputJson?.decision, "block");
1837
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1838
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1839
+ } finally {
1840
+ await rm(cwd, { recursive: true, force: true });
1841
+ }
1842
+ });
1843
+
1844
+ it("does not block Stop for an already complete performance-goal state", async () => {
1845
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
1846
+ try {
1847
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1848
+ version: 1,
1849
+ workflow: "performance-goal",
1850
+ slug: "latency",
1851
+ objective: "Reduce latency",
1852
+ status: "complete",
1853
+ completedAt: "2026-05-20T00:00:00.000Z",
1854
+ });
1855
+
1856
+ const result = await dispatchCodexNativeHook({
1857
+ hook_event_name: "Stop",
1858
+ cwd,
1859
+ session_id: "sess-performance-complete-stop",
1860
+ thread_id: "thread-performance-complete-stop",
1861
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1862
+ }, { cwd });
1863
+
1864
+ assert.notEqual(result.outputJson?.decision, "block");
1865
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1866
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1867
+ } finally {
1868
+ await rm(cwd, { recursive: true, force: true });
1869
+ }
1870
+ });
1871
+
1501
1872
  it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
1502
1873
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
1503
1874
  try {
@@ -1546,7 +1917,7 @@ describe("codex native hook dispatch", () => {
1546
1917
  }
1547
1918
  });
1548
1919
 
1549
- it("blocks ultragoal Stop with blocked checkpoint and fresh-thread remediation for completed legacy snapshots", async () => {
1920
+ it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
1550
1921
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
1551
1922
  try {
1552
1923
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
@@ -1567,7 +1938,11 @@ describe("codex native hook dispatch", () => {
1567
1938
  assert.equal(result.outputJson?.decision, "block");
1568
1939
  assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
1569
1940
  assert.match(output, /--status blocked/);
1570
- assert.match(output, /fresh Codex thread/);
1941
+ assert.match(output, /Codex goal context/);
1942
+ assert.match(output, /no such table: thread_goals/);
1943
+ assert.match(output, /unavailable get_goal error JSON or path/);
1944
+ assert.match(output, /safe-recovery blocker/);
1945
+ assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
1571
1946
  assert.match(output, /Hooks must not mutate Codex goal state/);
1572
1947
  } finally {
1573
1948
  await rm(cwd, { recursive: true, force: true });
@@ -1581,7 +1956,7 @@ describe("codex native hook dispatch", () => {
1581
1956
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1582
1957
  version: 1,
1583
1958
  codexGoalMode: "aggregate",
1584
- codexObjective: "Complete all ultragoal stories in .omx/ultragoal/goals.json: many micro goals",
1959
+ 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.",
1585
1960
  activeGoalId: "G001-micro",
1586
1961
  aggregateCompletion: {
1587
1962
  status: "complete",
@@ -1863,51 +2238,335 @@ describe("codex native hook dispatch", () => {
1863
2238
  }
1864
2239
  });
1865
2240
 
1866
- it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
1867
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
2241
+ it("injects autopilot ralplan consensus gate guidance on prompt activation", async () => {
2242
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-gate-"));
1868
2243
  try {
1869
2244
  await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1870
2245
  const result = await dispatchCodexNativeHook(
1871
2246
  {
1872
2247
  hook_event_name: "UserPromptSubmit",
1873
2248
  cwd,
1874
- session_id: "sess-ultragoal-1",
1875
- thread_id: "thread-ultragoal-1",
1876
- turn_id: "turn-ultragoal-1",
1877
- prompt: "$ultragoal split this launch into durable goals",
2249
+ session_id: "sess-autopilot-ralplan-gate",
2250
+ thread_id: "thread-autopilot-ralplan-gate",
2251
+ turn_id: "turn-autopilot-ralplan-gate",
2252
+ prompt: "$autopilot implement issue #2430",
1878
2253
  },
1879
2254
  { cwd },
1880
2255
  );
1881
2256
 
1882
2257
  assert.equal(result.omxEventName, "keyword-detector");
1883
- assert.equal(result.skillState?.skill, "ultragoal");
1884
- assert.equal(result.skillState?.initialized_mode, undefined);
2258
+ assert.equal(result.skillState?.skill, "autopilot");
1885
2259
  const message = String(
1886
2260
  (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
1887
2261
  );
1888
- assert.match(message, /"\$ultragoal" -> ultragoal/);
1889
- assert.match(message, /Ultragoal protocol:/);
1890
- assert.match(message, /get_goal/);
1891
- assert.match(message, /create_goal/);
1892
- assert.match(message, /update_goal/);
1893
- assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
2262
+ assert.match(message, /Autopilot protocol:/);
2263
+ assert.match(message, /deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
2264
+ assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
2265
+ assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
1894
2266
  } finally {
1895
2267
  await rm(cwd, { recursive: true, force: true });
1896
2268
  }
1897
2269
  });
1898
2270
 
1899
- it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
1900
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
2271
+ it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
2272
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
1901
2273
  try {
1902
2274
  await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1903
2275
  const result = await dispatchCodexNativeHook(
1904
2276
  {
1905
2277
  hook_event_name: "UserPromptSubmit",
1906
2278
  cwd,
1907
- session_id: "sess-ulw-ko",
1908
- thread_id: "thread-ulw-ko",
1909
- turn_id: "turn-ulw-ko",
1910
- prompt: "ㅕㅣㅈ로 병렬 처리해줘",
2279
+ session_id: "sess-ultragoal-1",
2280
+ thread_id: "thread-ultragoal-1",
2281
+ turn_id: "turn-ultragoal-1",
2282
+ prompt: "$ultragoal split this launch into durable goals",
2283
+ },
2284
+ { cwd },
2285
+ );
2286
+
2287
+ assert.equal(result.omxEventName, "keyword-detector");
2288
+ assert.equal(result.skillState?.skill, "ultragoal");
2289
+ assert.equal(result.skillState?.initialized_mode, "ultragoal");
2290
+ assert.equal(
2291
+ result.skillState?.initialized_state_path,
2292
+ ".omx/state/sessions/sess-ultragoal-1/ultragoal-state.json",
2293
+ );
2294
+ const message = String(
2295
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2296
+ );
2297
+ assert.match(message, /"\$ultragoal" -> ultragoal/);
2298
+ assert.match(message, /Ultragoal protocol:/);
2299
+ assert.match(message, /get_goal/);
2300
+ assert.match(message, /create_goal/);
2301
+ assert.match(message, /update_goal/);
2302
+ assert.match(message, /does not call `\/goal clear`/);
2303
+ assert.match(message, /multiple sequential ultragoal runs/);
2304
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), true);
2305
+ } finally {
2306
+ await rm(cwd, { recursive: true, force: true });
2307
+ }
2308
+ });
2309
+
2310
+ it("deactivates active deep-interview state on explicit ultragoal handoff", async () => {
2311
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-deep-interview-handoff-"));
2312
+ try {
2313
+ const stateDir = join(cwd, ".omx", "state");
2314
+ const sessionDir = join(stateDir, "sessions", "sess-ultragoal-handoff");
2315
+ await mkdir(sessionDir, { recursive: true });
2316
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
2317
+ version: 1,
2318
+ active: true,
2319
+ skill: "deep-interview",
2320
+ phase: "planning",
2321
+ session_id: "sess-ultragoal-handoff",
2322
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-ultragoal-handoff" }],
2323
+ });
2324
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
2325
+ active: true,
2326
+ mode: "deep-interview",
2327
+ current_phase: "intent-first",
2328
+ session_id: "sess-ultragoal-handoff",
2329
+ question_enforcement: {
2330
+ obligation_id: "obligation-ultragoal-handoff",
2331
+ source: "omx-question",
2332
+ status: "pending",
2333
+ requested_at: "2026-05-21T03:00:00.000Z",
2334
+ },
2335
+ });
2336
+
2337
+ const result = await dispatchCodexNativeHook(
2338
+ {
2339
+ hook_event_name: "UserPromptSubmit",
2340
+ cwd,
2341
+ session_id: "sess-ultragoal-handoff",
2342
+ thread_id: "thread-ultragoal-handoff",
2343
+ turn_id: "turn-ultragoal-handoff",
2344
+ prompt: "$ultragoal turn the clarified spec into goals",
2345
+ },
2346
+ { cwd },
2347
+ );
2348
+
2349
+ assert.equal(result.omxEventName, "keyword-detector");
2350
+ assert.equal(result.skillState?.skill, "ultragoal");
2351
+ assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ultragoal/);
2352
+
2353
+ const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as {
2354
+ active?: boolean;
2355
+ current_phase?: string;
2356
+ question_enforcement?: { status?: string; clear_reason?: string };
2357
+ };
2358
+ assert.equal(completed.active, false);
2359
+ assert.equal(completed.current_phase, "completed");
2360
+ assert.equal(completed.question_enforcement?.status, "cleared");
2361
+ assert.equal(completed.question_enforcement?.clear_reason, "handoff");
2362
+ assert.equal(existsSync(join(sessionDir, "ultragoal-state.json")), true);
2363
+
2364
+ const edit = await dispatchCodexNativeHook(
2365
+ {
2366
+ hook_event_name: "PreToolUse",
2367
+ cwd,
2368
+ session_id: "sess-ultragoal-handoff",
2369
+ thread_id: "thread-ultragoal-handoff",
2370
+ tool_name: "Edit",
2371
+ tool_use_id: "tool-ultragoal-post-handoff-edit",
2372
+ tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
2373
+ },
2374
+ { cwd },
2375
+ );
2376
+ assert.equal(edit.outputJson, null);
2377
+ } finally {
2378
+ await rm(cwd, { recursive: true, force: true });
2379
+ }
2380
+ });
2381
+
2382
+ it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
2383
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
2384
+ try {
2385
+ await createUltragoalPlan(cwd, {
2386
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
2387
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2388
+ });
2389
+
2390
+ const prose = await dispatchCodexNativeHook(
2391
+ {
2392
+ hook_event_name: "UserPromptSubmit",
2393
+ cwd,
2394
+ session_id: "sess-ultragoal-steer-1",
2395
+ prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
2396
+ },
2397
+ { cwd },
2398
+ );
2399
+ assert.equal(prose.outputJson, null);
2400
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
2401
+
2402
+ const jsonExample = await dispatchCodexNativeHook(
2403
+ {
2404
+ hook_event_name: "UserPromptSubmit",
2405
+ cwd,
2406
+ session_id: "sess-ultragoal-steer-1",
2407
+ prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
2408
+ kind: "add_subgoal",
2409
+ source: "user_prompt_submit",
2410
+ evidence: "Example JSON should not mutate .omx/ultragoal.",
2411
+ rationale: "Only explicit steering fences or labels are executable.",
2412
+ title: "Inert JSON example",
2413
+ objective: "This example must not be added.",
2414
+ })}\n\`\`\``,
2415
+ },
2416
+ { cwd },
2417
+ );
2418
+ assert.equal(jsonExample.outputJson, null);
2419
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
2420
+
2421
+ const result = await dispatchCodexNativeHook(
2422
+ {
2423
+ hook_event_name: "UserPromptSubmit",
2424
+ cwd,
2425
+ session_id: "sess-ultragoal-steer-1",
2426
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2427
+ kind: "add_subgoal",
2428
+ source: "user_prompt_submit",
2429
+ evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
2430
+ rationale: "Add bounded hook regression work while preserving all completion gates.",
2431
+ title: "Prompt bridge regression",
2432
+ objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
2433
+ })}`,
2434
+ },
2435
+ { cwd },
2436
+ );
2437
+
2438
+ const message = String(
2439
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2440
+ );
2441
+ assert.match(message, /bounded \.omx\/ultragoal steering/);
2442
+ assert.match(message, /G002-cli-and-prompt-submit-bridge/);
2443
+ assert.match(message, /accepted/);
2444
+ const plan = await readUltragoalPlan(cwd);
2445
+ assert.equal(plan.goals.length, 2);
2446
+ assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
2447
+ } finally {
2448
+ await rm(cwd, { recursive: true, force: true });
2449
+ }
2450
+ });
2451
+
2452
+ it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
2453
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
2454
+ try {
2455
+ await createUltragoalPlan(cwd, {
2456
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
2457
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2458
+ });
2459
+ const stateDir = join(cwd, ".omx", "state");
2460
+ const canonicalSessionId = "sess-ultragoal-parent";
2461
+ const leaderNativeSessionId = "native-ultragoal-parent";
2462
+ const childNativeSessionId = "native-ultragoal-child";
2463
+ const nowIso = new Date().toISOString();
2464
+ await writeJson(join(stateDir, "session.json"), {
2465
+ session_id: canonicalSessionId,
2466
+ native_session_id: leaderNativeSessionId,
2467
+ });
2468
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
2469
+ schemaVersion: 1,
2470
+ sessions: {
2471
+ [canonicalSessionId]: {
2472
+ session_id: canonicalSessionId,
2473
+ leader_thread_id: leaderNativeSessionId,
2474
+ updated_at: nowIso,
2475
+ threads: {
2476
+ [leaderNativeSessionId]: {
2477
+ thread_id: leaderNativeSessionId,
2478
+ kind: "leader",
2479
+ first_seen_at: nowIso,
2480
+ last_seen_at: nowIso,
2481
+ turn_count: 1,
2482
+ },
2483
+ [childNativeSessionId]: {
2484
+ thread_id: childNativeSessionId,
2485
+ kind: "subagent",
2486
+ first_seen_at: nowIso,
2487
+ last_seen_at: nowIso,
2488
+ turn_count: 1,
2489
+ mode: "architect",
2490
+ },
2491
+ },
2492
+ },
2493
+ },
2494
+ });
2495
+
2496
+ const result = await dispatchCodexNativeHook(
2497
+ {
2498
+ hook_event_name: "UserPromptSubmit",
2499
+ cwd,
2500
+ session_id: childNativeSessionId,
2501
+ thread_id: childNativeSessionId,
2502
+ turn_id: "turn-ultragoal-child-1",
2503
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2504
+ kind: "add_subgoal",
2505
+ source: "user_prompt_submit",
2506
+ evidence: "Subagent prompt text must be literal delegated context.",
2507
+ rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
2508
+ title: "Subagent should not add this",
2509
+ objective: "This must remain literal prompt text.",
2510
+ })}`,
2511
+ },
2512
+ { cwd },
2513
+ );
2514
+
2515
+ assert.equal(result.outputJson, null);
2516
+ const plan = await readUltragoalPlan(cwd);
2517
+ assert.equal(plan.goals.length, 1);
2518
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2519
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
2520
+ assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
2521
+ } finally {
2522
+ await rm(cwd, { recursive: true, force: true });
2523
+ }
2524
+ });
2525
+
2526
+ it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
2527
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
2528
+ try {
2529
+ await createUltragoalPlan(cwd, {
2530
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
2531
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2532
+ });
2533
+ const prompt = `\`\`\`omx-ultragoal-steer
2534
+ ${JSON.stringify({
2535
+ kind: "add_subgoal",
2536
+ source: "user_prompt_submit",
2537
+ evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
2538
+ rationale: "Use idempotent bridge semantics for repeated hook delivery.",
2539
+ title: "Deduped bridge regression",
2540
+ objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
2541
+ })}
2542
+ \`\`\``;
2543
+ await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
2544
+ const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
2545
+ const message = String(
2546
+ (second.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2547
+ );
2548
+ assert.match(message, /deduped/);
2549
+ const plan = await readUltragoalPlan(cwd);
2550
+ assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
2551
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2552
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
2553
+ } finally {
2554
+ await rm(cwd, { recursive: true, force: true });
2555
+ }
2556
+ });
2557
+
2558
+ it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
2559
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
2560
+ try {
2561
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
2562
+ const result = await dispatchCodexNativeHook(
2563
+ {
2564
+ hook_event_name: "UserPromptSubmit",
2565
+ cwd,
2566
+ session_id: "sess-ulw-ko",
2567
+ thread_id: "thread-ulw-ko",
2568
+ turn_id: "turn-ulw-ko",
2569
+ prompt: "ㅕㅣㅈ로 병렬 처리해줘",
1911
2570
  },
1912
2571
  { cwd },
1913
2572
  );
@@ -3341,79 +4000,256 @@ exit 0
3341
4000
  }
3342
4001
  });
3343
4002
 
3344
- it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
3345
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
4003
+ it("blocks implementation file edits while deep-interview remains active after a clarified answer", async () => {
4004
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-edit-block-"));
3346
4005
  try {
4006
+ const stateDir = join(cwd, ".omx", "state");
4007
+ const sessionDir = join(stateDir, "sessions", "sess-di-edit-block");
4008
+ await mkdir(sessionDir, { recursive: true });
4009
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-edit-block", cwd });
4010
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
4011
+ version: 1,
4012
+ active: true,
4013
+ skill: "deep-interview",
4014
+ phase: "planning",
4015
+ session_id: "sess-di-edit-block",
4016
+ thread_id: "thread-di-edit-block",
4017
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-edit-block", thread_id: "thread-di-edit-block" }],
4018
+ });
4019
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
4020
+ active: true,
4021
+ mode: "deep-interview",
4022
+ current_phase: "intent-first",
4023
+ session_id: "sess-di-edit-block",
4024
+ thread_id: "thread-di-edit-block",
4025
+ rounds: [{ answer: "Implement by editing src/hooks/keyword-detector.ts and add tests." }],
4026
+ });
4027
+
3347
4028
  const result = await dispatchCodexNativeHook(
3348
4029
  {
3349
4030
  hook_event_name: "PreToolUse",
3350
4031
  cwd,
3351
- tool_name: "Bash",
3352
- tool_use_id: "tool-danger",
3353
- tool_input: { command: "rm -rf dist" },
4032
+ session_id: "sess-di-edit-block",
4033
+ thread_id: "thread-di-edit-block",
4034
+ tool_name: "Edit",
4035
+ tool_use_id: "tool-di-edit-block",
4036
+ tool_input: { file_path: "src/hooks/keyword-detector.ts", old_string: "a", new_string: "b" },
3354
4037
  },
3355
4038
  { cwd },
3356
4039
  );
3357
4040
 
3358
4041
  assert.equal(result.omxEventName, "pre-tool-use");
3359
- assert.deepEqual(result.outputJson, {
3360
- hookSpecificOutput: {
3361
- hookEventName: "PreToolUse",
3362
- },
3363
- systemMessage:
3364
- "Destructive Bash command detected (`rm -rf dist`). Confirm the target and expected side effects before running it.",
3365
- });
4042
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4043
+ assert.match(String((result.outputJson as { reason?: string } | null)?.reason ?? ""), /Deep-interview is active/);
4044
+ assert.match(JSON.stringify(result.outputJson), /requirements\/spec mode/);
4045
+ assert.match(JSON.stringify(result.outputJson), /\$ralplan/);
3366
4046
  } finally {
3367
4047
  await rm(cwd, { recursive: true, force: true });
3368
4048
  }
3369
4049
  });
3370
4050
 
3371
- it("stays silent on PreToolUse for neutral pwd", async () => {
3372
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-neutral-"));
4051
+ it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
4052
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
3373
4053
  try {
3374
- const result = await dispatchCodexNativeHook(
4054
+ const stateDir = join(cwd, ".omx", "state");
4055
+ const sessionDir = join(stateDir, "sessions", "sess-di-artifact");
4056
+ await mkdir(sessionDir, { recursive: true });
4057
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-di-artifact", cwd });
4058
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
4059
+ version: 1,
4060
+ active: true,
4061
+ skill: "deep-interview",
4062
+ phase: "planning",
4063
+ session_id: "sess-di-artifact",
4064
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-artifact" }],
4065
+ });
4066
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
4067
+ active: true,
4068
+ mode: "deep-interview",
4069
+ current_phase: "intent-first",
4070
+ session_id: "sess-di-artifact",
4071
+ });
4072
+
4073
+ const allowedWrite = await dispatchCodexNativeHook(
3375
4074
  {
3376
4075
  hook_event_name: "PreToolUse",
3377
4076
  cwd,
3378
- tool_name: "Bash",
3379
- tool_use_id: "tool-neutral",
3380
- tool_input: { command: "pwd" },
4077
+ session_id: "sess-di-artifact",
4078
+ tool_name: "Write",
4079
+ tool_use_id: "tool-di-spec-write",
4080
+ tool_input: { file_path: ".omx/specs/deep-interview-demo.md", content: "# Spec" },
3381
4081
  },
3382
4082
  { cwd },
3383
4083
  );
4084
+ assert.equal(allowedWrite.outputJson, null);
3384
4085
 
3385
- assert.equal(result.omxEventName, "pre-tool-use");
3386
- assert.equal(result.outputJson, null);
3387
- } finally {
3388
- await rm(cwd, { recursive: true, force: true });
3389
- }
3390
- });
4086
+ const allowedBash = await dispatchCodexNativeHook(
4087
+ {
4088
+ hook_event_name: "PreToolUse",
4089
+ cwd,
4090
+ session_id: "sess-di-artifact",
4091
+ tool_name: "Bash",
4092
+ tool_use_id: "tool-di-context-bash",
4093
+ tool_input: { command: "cat > .omx/context/demo.md <<'EOF'\n# Context\nEOF" },
4094
+ },
4095
+ { cwd },
4096
+ );
4097
+ assert.equal(allowedBash.outputJson, null);
3391
4098
 
3392
- it("warns on PreToolUse for vague sloppy fallback implementation framing", async () => {
3393
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-warn-"));
3394
- try {
3395
- const result = await dispatchCodexNativeHook(
4099
+ const allowedAppendBash = await dispatchCodexNativeHook(
3396
4100
  {
3397
4101
  hook_event_name: "PreToolUse",
3398
4102
  cwd,
4103
+ session_id: "sess-di-artifact",
3399
4104
  tool_name: "Bash",
3400
- tool_use_id: "tool-slop-warn",
3401
- tool_input: {
3402
- command: [
3403
- "cat > src/runtime.ts <<'EOF'",
3404
- "export function loadRuntime() {",
3405
- " // implement a quick hack fallback if it fails",
3406
- " return process.env.RUNTIME || 'local';",
3407
- "}",
3408
- "EOF",
3409
- ].join("\n"),
3410
- },
4105
+ tool_use_id: "tool-di-context-append-bash",
4106
+ tool_input: { command: "echo more context >> .omx/context/demo.md" },
3411
4107
  },
3412
4108
  { cwd },
3413
4109
  );
4110
+ assert.equal(allowedAppendBash.outputJson, null);
3414
4111
 
3415
- assert.equal(result.omxEventName, "pre-tool-use");
3416
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined);
4112
+ const blockedBash = await dispatchCodexNativeHook(
4113
+ {
4114
+ hook_event_name: "PreToolUse",
4115
+ cwd,
4116
+ session_id: "sess-di-artifact",
4117
+ tool_name: "Bash",
4118
+ tool_use_id: "tool-di-src-bash",
4119
+ tool_input: { command: "cat > src/implementation.ts <<'EOF'\nexport const x = 1;\nEOF" },
4120
+ },
4121
+ { cwd },
4122
+ );
4123
+ assert.equal((blockedBash.outputJson as { decision?: string } | null)?.decision, "block");
4124
+ } finally {
4125
+ await rm(cwd, { recursive: true, force: true });
4126
+ }
4127
+ });
4128
+
4129
+ it("allows implementation tools after an explicit deep-interview handoff deactivates the mode", async () => {
4130
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-handoff-"));
4131
+ try {
4132
+ const stateDir = join(cwd, ".omx", "state");
4133
+ const sessionDir = join(stateDir, "sessions", "sess-di-handoff");
4134
+ await mkdir(sessionDir, { recursive: true });
4135
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
4136
+ version: 1,
4137
+ active: true,
4138
+ skill: "deep-interview",
4139
+ phase: "planning",
4140
+ session_id: "sess-di-handoff",
4141
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-di-handoff" }],
4142
+ });
4143
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
4144
+ active: true,
4145
+ mode: "deep-interview",
4146
+ current_phase: "intent-first",
4147
+ session_id: "sess-di-handoff",
4148
+ });
4149
+
4150
+ await dispatchCodexNativeHook(
4151
+ {
4152
+ hook_event_name: "UserPromptSubmit",
4153
+ cwd,
4154
+ session_id: "sess-di-handoff",
4155
+ prompt: "$ralph implement the clarified spec in src/implementation.ts",
4156
+ },
4157
+ { cwd },
4158
+ );
4159
+
4160
+ const result = await dispatchCodexNativeHook(
4161
+ {
4162
+ hook_event_name: "PreToolUse",
4163
+ cwd,
4164
+ session_id: "sess-di-handoff",
4165
+ tool_name: "Edit",
4166
+ tool_use_id: "tool-di-post-handoff-edit",
4167
+ tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
4168
+ },
4169
+ { cwd },
4170
+ );
4171
+
4172
+ assert.equal(result.outputJson, null);
4173
+ const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as { active?: boolean };
4174
+ assert.equal(completed.active, false);
4175
+ } finally {
4176
+ await rm(cwd, { recursive: true, force: true });
4177
+ }
4178
+ });
4179
+
4180
+ it("returns a destructive-command caution on PreToolUse for rm -rf dist", async () => {
4181
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-danger-"));
4182
+ try {
4183
+ const result = await dispatchCodexNativeHook(
4184
+ {
4185
+ hook_event_name: "PreToolUse",
4186
+ cwd,
4187
+ tool_name: "Bash",
4188
+ tool_use_id: "tool-danger",
4189
+ tool_input: { command: "rm -rf dist" },
4190
+ },
4191
+ { cwd },
4192
+ );
4193
+
4194
+ assert.equal(result.omxEventName, "pre-tool-use");
4195
+ assert.deepEqual(result.outputJson, {
4196
+ hookSpecificOutput: {
4197
+ hookEventName: "PreToolUse",
4198
+ },
4199
+ systemMessage:
4200
+ "Destructive Bash command detected (`rm -rf dist`). Confirm the target and expected side effects before running it.",
4201
+ });
4202
+ } finally {
4203
+ await rm(cwd, { recursive: true, force: true });
4204
+ }
4205
+ });
4206
+
4207
+ it("stays silent on PreToolUse for neutral pwd", async () => {
4208
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-neutral-"));
4209
+ try {
4210
+ const result = await dispatchCodexNativeHook(
4211
+ {
4212
+ hook_event_name: "PreToolUse",
4213
+ cwd,
4214
+ tool_name: "Bash",
4215
+ tool_use_id: "tool-neutral",
4216
+ tool_input: { command: "pwd" },
4217
+ },
4218
+ { cwd },
4219
+ );
4220
+
4221
+ assert.equal(result.omxEventName, "pre-tool-use");
4222
+ assert.equal(result.outputJson, null);
4223
+ } finally {
4224
+ await rm(cwd, { recursive: true, force: true });
4225
+ }
4226
+ });
4227
+
4228
+ it("warns on PreToolUse for vague sloppy fallback implementation framing", async () => {
4229
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-warn-"));
4230
+ try {
4231
+ const result = await dispatchCodexNativeHook(
4232
+ {
4233
+ hook_event_name: "PreToolUse",
4234
+ cwd,
4235
+ tool_name: "Bash",
4236
+ tool_use_id: "tool-slop-warn",
4237
+ tool_input: {
4238
+ command: [
4239
+ "cat > src/runtime.ts <<'EOF'",
4240
+ "export function loadRuntime() {",
4241
+ " // implement a quick hack fallback if it fails",
4242
+ " return process.env.RUNTIME || 'local';",
4243
+ "}",
4244
+ "EOF",
4245
+ ].join("\n"),
4246
+ },
4247
+ },
4248
+ { cwd },
4249
+ );
4250
+
4251
+ assert.equal(result.omxEventName, "pre-tool-use");
4252
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined);
3417
4253
  assert.equal((result.outputJson as { hookSpecificOutput?: { hookEventName?: string } } | null)?.hookSpecificOutput?.hookEventName, "PreToolUse");
3418
4254
  assert.match(JSON.stringify(result.outputJson), /don't make potential slop/);
3419
4255
  assert.match(JSON.stringify(result.outputJson), /architect/);
@@ -3768,7 +4604,7 @@ exit 0
3768
4604
  cwd,
3769
4605
  tool_name: "Bash",
3770
4606
  tool_use_id: "tool-slop-git-priority",
3771
- tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
4607
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
3772
4608
  },
3773
4609
  { cwd },
3774
4610
  );
@@ -3791,7 +4627,7 @@ exit 0
3791
4627
  cwd,
3792
4628
  tool_name: "Bash",
3793
4629
  tool_use_id: "tool-git-commit-invalid",
3794
- tool_input: { command: 'git commit -m "fix tests"' },
4630
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
3795
4631
  },
3796
4632
  { cwd },
3797
4633
  );
@@ -3820,24 +4656,26 @@ exit 0
3820
4656
  }
3821
4657
  });
3822
4658
 
3823
- it("allows non-Lore git commit messages when the Lore commit guard is explicitly disabled", async () => {
3824
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
4659
+
4660
+ it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
4661
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
3825
4662
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3826
4663
  try {
3827
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4664
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3828
4665
  const result = await dispatchCodexNativeHook(
3829
4666
  {
3830
4667
  hook_event_name: "PreToolUse",
3831
4668
  cwd,
3832
4669
  tool_name: "Bash",
3833
- tool_use_id: "tool-git-commit-lore-disabled",
3834
- tool_input: { command: 'git commit -m "fix: use conventional commit"' },
4670
+ tool_use_id: "tool-git-commit-lore-env-enabled",
4671
+ tool_input: { command: 'git commit -m "fix tests"' },
3835
4672
  },
3836
4673
  { cwd },
3837
4674
  );
3838
4675
 
3839
4676
  assert.equal(result.omxEventName, "pre-tool-use");
3840
- assert.equal(result.outputJson, null);
4677
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4678
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3841
4679
  } finally {
3842
4680
  if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3843
4681
  else process.env.OMX_LORE_COMMIT_GUARD = original;
@@ -3845,16 +4683,18 @@ exit 0
3845
4683
  }
3846
4684
  });
3847
4685
 
3848
- it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
3849
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
4686
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
4687
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
4688
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
3850
4689
  try {
4690
+ delete process.env.OMX_LORE_COMMIT_GUARD;
3851
4691
  const result = await dispatchCodexNativeHook(
3852
4692
  {
3853
4693
  hook_event_name: "PreToolUse",
3854
4694
  cwd,
3855
4695
  tool_name: "Bash",
3856
- tool_use_id: "tool-git-commit-lore-inline-disabled",
3857
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
4696
+ tool_use_id: "tool-git-commit-lore-disabled",
4697
+ tool_input: { command: 'git commit -m "fix: use conventional commit"' },
3858
4698
  },
3859
4699
  { cwd },
3860
4700
  );
@@ -3862,20 +4702,21 @@ exit 0
3862
4702
  assert.equal(result.omxEventName, "pre-tool-use");
3863
4703
  assert.equal(result.outputJson, null);
3864
4704
  } finally {
4705
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4706
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
3865
4707
  await rm(cwd, { recursive: true, force: true });
3866
4708
  }
3867
4709
  });
3868
4710
 
3869
- it("does not treat newline-separated Lore guard assignment as inline git commit env", async () => {
3870
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
3871
- try {
4711
+ it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
4712
+ await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
3872
4713
  const result = await dispatchCodexNativeHook(
3873
4714
  {
3874
4715
  hook_event_name: "PreToolUse",
3875
4716
  cwd,
3876
4717
  tool_name: "Bash",
3877
- tool_use_id: "tool-git-commit-lore-newline-assignment",
3878
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0\ngit commit -m "fix: conventional"' },
4718
+ tool_use_id: "tool-git-commit-lore-config-enabled",
4719
+ tool_input: { command: 'git commit -m "fix: conventional"' },
3879
4720
  },
3880
4721
  { cwd },
3881
4722
  );
@@ -3883,49 +4724,36 @@ exit 0
3883
4724
  assert.equal(result.omxEventName, "pre-tool-use");
3884
4725
  assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3885
4726
  assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3886
- } finally {
3887
- await rm(cwd, { recursive: true, force: true });
3888
- }
4727
+ });
3889
4728
  });
3890
4729
 
3891
- it("restores default-on Lore guard when env -u unsets a disabled process env", async () => {
3892
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
3893
- const original = process.env.OMX_LORE_COMMIT_GUARD;
3894
- try {
3895
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4730
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
4731
+ await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
3896
4732
  const result = await dispatchCodexNativeHook(
3897
4733
  {
3898
4734
  hook_event_name: "PreToolUse",
3899
4735
  cwd,
3900
4736
  tool_name: "Bash",
3901
- tool_use_id: "tool-git-commit-lore-env-unset",
3902
- tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4737
+ tool_use_id: "tool-git-commit-lore-config-disabled",
4738
+ tool_input: { command: 'git commit -m "fix: use conventional commit"' },
3903
4739
  },
3904
4740
  { cwd },
3905
4741
  );
3906
4742
 
3907
4743
  assert.equal(result.omxEventName, "pre-tool-use");
3908
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3909
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3910
- } finally {
3911
- if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3912
- else process.env.OMX_LORE_COMMIT_GUARD = original;
3913
- await rm(cwd, { recursive: true, force: true });
3914
- }
4744
+ assert.equal(result.outputJson, null);
4745
+ });
3915
4746
  });
3916
4747
 
3917
- it("restores default-on Lore guard when env -i clears a disabled process env", async () => {
3918
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
3919
- const original = process.env.OMX_LORE_COMMIT_GUARD;
3920
- try {
3921
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4748
+ it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
4749
+ await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
3922
4750
  const result = await dispatchCodexNativeHook(
3923
4751
  {
3924
4752
  hook_event_name: "PreToolUse",
3925
4753
  cwd,
3926
4754
  tool_name: "Bash",
3927
- tool_use_id: "tool-git-commit-lore-env-ignore",
3928
- tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
4755
+ tool_use_id: "tool-git-commit-lore-config-inline-enabled",
4756
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
3929
4757
  },
3930
4758
  { cwd },
3931
4759
  );
@@ -3933,47 +4761,37 @@ exit 0
3933
4761
  assert.equal(result.omxEventName, "pre-tool-use");
3934
4762
  assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3935
4763
  assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3936
- } finally {
3937
- if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3938
- else process.env.OMX_LORE_COMMIT_GUARD = original;
3939
- await rm(cwd, { recursive: true, force: true });
3940
- }
4764
+ });
3941
4765
  });
3942
4766
 
3943
- it("keeps Lore commit enforcement enabled for unknown inline guard values", async () => {
3944
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
3945
- try {
4767
+ it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
4768
+ await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
3946
4769
  const result = await dispatchCodexNativeHook(
3947
4770
  {
3948
4771
  hook_event_name: "PreToolUse",
3949
4772
  cwd,
3950
4773
  tool_name: "Bash",
3951
- tool_use_id: "tool-git-commit-lore-inline-unknown",
3952
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
4774
+ tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
4775
+ tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
3953
4776
  },
3954
4777
  { cwd },
3955
4778
  );
3956
4779
 
3957
4780
  assert.equal(result.omxEventName, "pre-tool-use");
3958
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3959
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
3960
- } finally {
3961
- await rm(cwd, { recursive: true, force: true });
3962
- }
4781
+ assert.equal(result.outputJson, null);
4782
+ });
3963
4783
  });
3964
4784
 
3965
- it("treats Lore commit guard disabled values as trim and case tolerant", async () => {
3966
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-off-"));
3967
- const original = process.env.OMX_LORE_COMMIT_GUARD;
4785
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
4786
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
3968
4787
  try {
3969
- process.env.OMX_LORE_COMMIT_GUARD = " OFF ";
3970
4788
  const result = await dispatchCodexNativeHook(
3971
4789
  {
3972
4790
  hook_event_name: "PreToolUse",
3973
4791
  cwd,
3974
4792
  tool_name: "Bash",
3975
- tool_use_id: "tool-git-commit-lore-off",
3976
- tool_input: { command: 'git commit -m "chore: conventional commit"' },
4793
+ tool_use_id: "tool-git-commit-lore-inline-disabled",
4794
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
3977
4795
  },
3978
4796
  { cwd },
3979
4797
  );
@@ -3981,31 +4799,29 @@ exit 0
3981
4799
  assert.equal(result.omxEventName, "pre-tool-use");
3982
4800
  assert.equal(result.outputJson, null);
3983
4801
  } finally {
3984
- if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3985
- else process.env.OMX_LORE_COMMIT_GUARD = original;
3986
4802
  await rm(cwd, { recursive: true, force: true });
3987
4803
  }
3988
4804
  });
3989
4805
 
3990
- it("keeps Lore commit enforcement enabled for unknown guard values", async () => {
3991
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
4806
+
4807
+ it("allows inline disabled guard to override an enabled process env", async () => {
4808
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
3992
4809
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3993
4810
  try {
3994
- process.env.OMX_LORE_COMMIT_GUARD = "maybe";
4811
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3995
4812
  const result = await dispatchCodexNativeHook(
3996
4813
  {
3997
4814
  hook_event_name: "PreToolUse",
3998
4815
  cwd,
3999
4816
  tool_name: "Bash",
4000
- tool_use_id: "tool-git-commit-lore-unknown",
4001
- tool_input: { command: 'git commit -m "fix tests"' },
4817
+ tool_use_id: "tool-git-commit-lore-inline-override-disabled",
4818
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
4002
4819
  },
4003
4820
  { cwd },
4004
4821
  );
4005
4822
 
4006
4823
  assert.equal(result.omxEventName, "pre-tool-use");
4007
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4008
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4824
+ assert.equal(result.outputJson, null);
4009
4825
  } finally {
4010
4826
  if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4011
4827
  else process.env.OMX_LORE_COMMIT_GUARD = original;
@@ -4013,63 +4829,57 @@ exit 0
4013
4829
  }
4014
4830
  });
4015
4831
 
4016
- it("continues to later PreToolUse checks when Lore commit guard is disabled", async () => {
4017
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-lore-disabled-destructive-"));
4018
- const original = process.env.OMX_LORE_COMMIT_GUARD;
4832
+ it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
4833
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
4019
4834
  try {
4020
- process.env.OMX_LORE_COMMIT_GUARD = "false";
4021
4835
  const result = await dispatchCodexNativeHook(
4022
4836
  {
4023
4837
  hook_event_name: "PreToolUse",
4024
4838
  cwd,
4025
4839
  tool_name: "Bash",
4026
- tool_use_id: "tool-lore-disabled-destructive",
4027
- tool_input: { command: "rm -rf dist" },
4840
+ tool_use_id: "tool-git-commit-lore-newline-assignment",
4841
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
4028
4842
  },
4029
4843
  { cwd },
4030
4844
  );
4031
4845
 
4032
4846
  assert.equal(result.omxEventName, "pre-tool-use");
4033
- assert.doesNotMatch(JSON.stringify(result.outputJson), /Lore protocol/);
4034
- assert.match(JSON.stringify(result.outputJson), /Destructive Bash command detected/);
4847
+ assert.equal(result.outputJson, null);
4035
4848
  } finally {
4036
- if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4037
- else process.env.OMX_LORE_COMMIT_GUARD = original;
4038
4849
  await rm(cwd, { recursive: true, force: true });
4039
4850
  }
4040
4851
  });
4041
4852
 
4042
- it("stays silent on PreToolUse for `git help commit`", async () => {
4043
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-help-commit-"));
4044
- try {
4853
+ it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
4854
+ await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
4045
4855
  const result = await dispatchCodexNativeHook(
4046
4856
  {
4047
4857
  hook_event_name: "PreToolUse",
4048
4858
  cwd,
4049
4859
  tool_name: "Bash",
4050
- tool_use_id: "tool-git-help-commit",
4051
- tool_input: { command: "git help commit" },
4860
+ tool_use_id: "tool-git-commit-lore-config-env-unset",
4861
+ tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4052
4862
  },
4053
4863
  { cwd },
4054
4864
  );
4055
4865
 
4056
4866
  assert.equal(result.omxEventName, "pre-tool-use");
4057
4867
  assert.equal(result.outputJson, null);
4058
- } finally {
4059
- await rm(cwd, { recursive: true, force: true });
4060
- }
4868
+ });
4061
4869
  });
4062
4870
 
4063
- it("stays silent on PreToolUse for `git config alias.ci commit`", async () => {
4064
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-config-alias-commit-"));
4871
+ it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
4872
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
4873
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4065
4874
  try {
4875
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
4066
4876
  const result = await dispatchCodexNativeHook(
4067
4877
  {
4068
4878
  hook_event_name: "PreToolUse",
4069
4879
  cwd,
4070
4880
  tool_name: "Bash",
4071
- tool_use_id: "tool-git-config-alias-commit",
4072
- tool_input: { command: "git config alias.ci commit" },
4881
+ tool_use_id: "tool-git-commit-lore-env-unset",
4882
+ tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4073
4883
  },
4074
4884
  { cwd },
4075
4885
  );
@@ -4077,20 +4887,24 @@ exit 0
4077
4887
  assert.equal(result.omxEventName, "pre-tool-use");
4078
4888
  assert.equal(result.outputJson, null);
4079
4889
  } finally {
4890
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4891
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4080
4892
  await rm(cwd, { recursive: true, force: true });
4081
4893
  }
4082
4894
  });
4083
4895
 
4084
- it("stays silent on PreToolUse for `git tag commit`", async () => {
4085
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-tag-commit-"));
4896
+ it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
4897
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
4898
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4086
4899
  try {
4900
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
4087
4901
  const result = await dispatchCodexNativeHook(
4088
4902
  {
4089
4903
  hook_event_name: "PreToolUse",
4090
4904
  cwd,
4091
4905
  tool_name: "Bash",
4092
- tool_use_id: "tool-git-tag-commit",
4093
- tool_input: { command: "git tag commit" },
4906
+ tool_use_id: "tool-git-commit-lore-env-ignore",
4907
+ tool_input: { command: 'env -i PATH=/usr/bin git commit -m "fix: conventional"' },
4094
4908
  },
4095
4909
  { cwd },
4096
4910
  );
@@ -4098,20 +4912,182 @@ exit 0
4098
4912
  assert.equal(result.omxEventName, "pre-tool-use");
4099
4913
  assert.equal(result.outputJson, null);
4100
4914
  } finally {
4915
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4916
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4101
4917
  await rm(cwd, { recursive: true, force: true });
4102
4918
  }
4103
4919
  });
4104
4920
 
4105
- it("blocks PreToolUse env-prefixed git commit when the inline message is not Lore-compliant", async () => {
4106
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-env-invalid-"));
4921
+ it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
4922
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
4107
4923
  try {
4108
4924
  const result = await dispatchCodexNativeHook(
4109
4925
  {
4110
4926
  hook_event_name: "PreToolUse",
4111
4927
  cwd,
4112
4928
  tool_name: "Bash",
4113
- tool_use_id: "tool-git-commit-env-invalid",
4114
- tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
4929
+ tool_use_id: "tool-git-commit-lore-inline-unknown",
4930
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=maybe git commit -m "fix: conventional"' },
4931
+ },
4932
+ { cwd },
4933
+ );
4934
+
4935
+ assert.equal(result.omxEventName, "pre-tool-use");
4936
+ assert.equal(result.outputJson, null);
4937
+ } finally {
4938
+ await rm(cwd, { recursive: true, force: true });
4939
+ }
4940
+ });
4941
+
4942
+ it("treats Lore commit guard disabled values as trim and case tolerant", async () => {
4943
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-off-"));
4944
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4945
+ try {
4946
+ process.env.OMX_LORE_COMMIT_GUARD = " OFF ";
4947
+ const result = await dispatchCodexNativeHook(
4948
+ {
4949
+ hook_event_name: "PreToolUse",
4950
+ cwd,
4951
+ tool_name: "Bash",
4952
+ tool_use_id: "tool-git-commit-lore-off",
4953
+ tool_input: { command: 'git commit -m "chore: conventional commit"' },
4954
+ },
4955
+ { cwd },
4956
+ );
4957
+
4958
+ assert.equal(result.omxEventName, "pre-tool-use");
4959
+ assert.equal(result.outputJson, null);
4960
+ } finally {
4961
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4962
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4963
+ await rm(cwd, { recursive: true, force: true });
4964
+ }
4965
+ });
4966
+
4967
+ it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
4968
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
4969
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4970
+ try {
4971
+ process.env.OMX_LORE_COMMIT_GUARD = "maybe";
4972
+ const result = await dispatchCodexNativeHook(
4973
+ {
4974
+ hook_event_name: "PreToolUse",
4975
+ cwd,
4976
+ tool_name: "Bash",
4977
+ tool_use_id: "tool-git-commit-lore-unknown",
4978
+ tool_input: { command: 'git commit -m "fix tests"' },
4979
+ },
4980
+ { cwd },
4981
+ );
4982
+
4983
+ assert.equal(result.omxEventName, "pre-tool-use");
4984
+ assert.equal(result.outputJson, null);
4985
+ } finally {
4986
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4987
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4988
+ await rm(cwd, { recursive: true, force: true });
4989
+ }
4990
+ });
4991
+
4992
+ it("continues to later PreToolUse checks when Lore commit guard is disabled", async () => {
4993
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-lore-disabled-destructive-"));
4994
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4995
+ try {
4996
+ process.env.OMX_LORE_COMMIT_GUARD = "false";
4997
+ const result = await dispatchCodexNativeHook(
4998
+ {
4999
+ hook_event_name: "PreToolUse",
5000
+ cwd,
5001
+ tool_name: "Bash",
5002
+ tool_use_id: "tool-lore-disabled-destructive",
5003
+ tool_input: { command: "rm -rf dist" },
5004
+ },
5005
+ { cwd },
5006
+ );
5007
+
5008
+ assert.equal(result.omxEventName, "pre-tool-use");
5009
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /Lore protocol/);
5010
+ assert.match(JSON.stringify(result.outputJson), /Destructive Bash command detected/);
5011
+ } finally {
5012
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
5013
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
5014
+ await rm(cwd, { recursive: true, force: true });
5015
+ }
5016
+ });
5017
+
5018
+ it("stays silent on PreToolUse for `git help commit`", async () => {
5019
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-help-commit-"));
5020
+ try {
5021
+ const result = await dispatchCodexNativeHook(
5022
+ {
5023
+ hook_event_name: "PreToolUse",
5024
+ cwd,
5025
+ tool_name: "Bash",
5026
+ tool_use_id: "tool-git-help-commit",
5027
+ tool_input: { command: "git help commit" },
5028
+ },
5029
+ { cwd },
5030
+ );
5031
+
5032
+ assert.equal(result.omxEventName, "pre-tool-use");
5033
+ assert.equal(result.outputJson, null);
5034
+ } finally {
5035
+ await rm(cwd, { recursive: true, force: true });
5036
+ }
5037
+ });
5038
+
5039
+ it("stays silent on PreToolUse for `git config alias.ci commit`", async () => {
5040
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-config-alias-commit-"));
5041
+ try {
5042
+ const result = await dispatchCodexNativeHook(
5043
+ {
5044
+ hook_event_name: "PreToolUse",
5045
+ cwd,
5046
+ tool_name: "Bash",
5047
+ tool_use_id: "tool-git-config-alias-commit",
5048
+ tool_input: { command: "git config alias.ci commit" },
5049
+ },
5050
+ { cwd },
5051
+ );
5052
+
5053
+ assert.equal(result.omxEventName, "pre-tool-use");
5054
+ assert.equal(result.outputJson, null);
5055
+ } finally {
5056
+ await rm(cwd, { recursive: true, force: true });
5057
+ }
5058
+ });
5059
+
5060
+ it("stays silent on PreToolUse for `git tag commit`", async () => {
5061
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-tag-commit-"));
5062
+ try {
5063
+ const result = await dispatchCodexNativeHook(
5064
+ {
5065
+ hook_event_name: "PreToolUse",
5066
+ cwd,
5067
+ tool_name: "Bash",
5068
+ tool_use_id: "tool-git-tag-commit",
5069
+ tool_input: { command: "git tag commit" },
5070
+ },
5071
+ { cwd },
5072
+ );
5073
+
5074
+ assert.equal(result.omxEventName, "pre-tool-use");
5075
+ assert.equal(result.outputJson, null);
5076
+ } finally {
5077
+ await rm(cwd, { recursive: true, force: true });
5078
+ }
5079
+ });
5080
+
5081
+ it("blocks PreToolUse env-prefixed git commit when the inline message is not Lore-compliant", async () => {
5082
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-env-invalid-"));
5083
+ try {
5084
+ const result = await dispatchCodexNativeHook(
5085
+ {
5086
+ hook_event_name: "PreToolUse",
5087
+ cwd,
5088
+ tool_name: "Bash",
5089
+ tool_use_id: "tool-git-commit-env-invalid",
5090
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
4115
5091
  },
4116
5092
  { cwd },
4117
5093
  );
@@ -4146,7 +5122,7 @@ exit 0
4146
5122
  cwd,
4147
5123
  tool_name: "Bash",
4148
5124
  tool_use_id: "tool-git-commit-option-invalid",
4149
- tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
5125
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
4150
5126
  },
4151
5127
  { cwd },
4152
5128
  );
@@ -4181,7 +5157,7 @@ exit 0
4181
5157
  cwd,
4182
5158
  tool_name: "Bash",
4183
5159
  tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
4184
- tool_input: { command: 'env git.exe commit -m "fix tests"' },
5160
+ tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4185
5161
  },
4186
5162
  { cwd },
4187
5163
  );
@@ -4216,7 +5192,7 @@ exit 0
4216
5192
  cwd,
4217
5193
  tool_name: "Bash",
4218
5194
  tool_use_id: "tool-git-exe-commit-invalid",
4219
- tool_input: { command: 'git.exe commit -m "fix tests"' },
5195
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4220
5196
  },
4221
5197
  { cwd },
4222
5198
  );
@@ -4251,7 +5227,7 @@ exit 0
4251
5227
  cwd,
4252
5228
  tool_name: "Bash",
4253
5229
  tool_use_id: "tool-git-exe-commit-env-flag-wrapper-invalid",
4254
- tool_input: { command: 'env -i PATH=/usr/bin git.exe commit -m "fix tests"' },
5230
+ tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4255
5231
  },
4256
5232
  { cwd },
4257
5233
  );
@@ -4286,7 +5262,7 @@ exit 0
4286
5262
  cwd,
4287
5263
  tool_name: "Bash",
4288
5264
  tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
4289
- tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
5265
+ tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4290
5266
  },
4291
5267
  { cwd },
4292
5268
  );
@@ -4321,7 +5297,7 @@ exit 0
4321
5297
  cwd,
4322
5298
  tool_name: "Bash",
4323
5299
  tool_use_id: "tool-git-exe-commit-windows-path-invalid",
4324
- tool_input: { command: '"C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
5300
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
4325
5301
  },
4326
5302
  { cwd },
4327
5303
  );
@@ -4356,7 +5332,7 @@ exit 0
4356
5332
  cwd,
4357
5333
  tool_name: "Bash",
4358
5334
  tool_use_id: "tool-git-exe-commit-windows-backslash-path-invalid",
4359
- tool_input: { command: '"C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
5335
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
4360
5336
  },
4361
5337
  { cwd },
4362
5338
  );
@@ -4391,7 +5367,7 @@ exit 0
4391
5367
  cwd,
4392
5368
  tool_name: "Bash",
4393
5369
  tool_use_id: "tool-git-commit-path-invalid",
4394
- tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
5370
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
4395
5371
  },
4396
5372
  { cwd },
4397
5373
  );
@@ -4426,7 +5402,7 @@ exit 0
4426
5402
  cwd,
4427
5403
  tool_name: "Bash",
4428
5404
  tool_use_id: "tool-git-commit-file",
4429
- tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
5405
+ tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
4430
5406
  },
4431
5407
  { cwd },
4432
5408
  );
@@ -4460,7 +5436,7 @@ exit 0
4460
5436
  tool_use_id: "tool-git-commit-missing-omx-coauthor",
4461
5437
  tool_input: {
4462
5438
  command: [
4463
- 'git commit',
5439
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4464
5440
  '-m "Prevent invalid history from bypassing Lore enforcement"',
4465
5441
  '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
4466
5442
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -4500,7 +5476,7 @@ exit 0
4500
5476
  tool_use_id: "tool-git-commit-valid",
4501
5477
  tool_input: {
4502
5478
  command: [
4503
- 'git commit',
5479
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4504
5480
  '-m "Prevent invalid history from bypassing Lore enforcement"',
4505
5481
  '-m "The native pre-tool-use hook now blocks inline git commit messages that skip Lore trailers or the required OmX co-author trailer."',
4506
5482
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -4530,7 +5506,7 @@ exit 0
4530
5506
  tool_use_id: "tool-git-commit-compact-coauthor",
4531
5507
  tool_input: {
4532
5508
  command: [
4533
- 'git commit',
5509
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4534
5510
  '-m "Launch lvisai.xyz intro site"',
4535
5511
  '-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
4536
5512
  ].join(" "),
@@ -4557,7 +5533,7 @@ exit 0
4557
5533
  tool_use_id: "tool-git-commit-compact-trailers",
4558
5534
  tool_input: {
4559
5535
  command: [
4560
- 'git commit',
5536
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4561
5537
  '-m "Launch lvisai.xyz intro site"',
4562
5538
  '-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>"',
4563
5539
  ].join(" "),
@@ -4584,7 +5560,7 @@ exit 0
4584
5560
  tool_use_id: "tool-git-commit-compact-no-separator",
4585
5561
  tool_input: {
4586
5562
  command: [
4587
- 'git commit',
5563
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4588
5564
  '--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
4589
5565
  ].join(" "),
4590
5566
  },
@@ -5617,6 +6593,91 @@ exit 0
5617
6593
  }
5618
6594
  });
5619
6595
 
6596
+ it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
6597
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
6598
+ try {
6599
+ const stateDir = join(cwd, ".omx", "state");
6600
+ const sessionId = "sess-stop-autopilot-terminal-run-state";
6601
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6602
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
6603
+ active: true,
6604
+ mode: "autopilot",
6605
+ current_phase: "ralplan",
6606
+ });
6607
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
6608
+ version: 1,
6609
+ active: false,
6610
+ mode: "autopilot",
6611
+ outcome: "finish",
6612
+ lifecycle_outcome: "finished",
6613
+ current_phase: "complete",
6614
+ completed_at: "2026-05-20T11:00:00.000Z",
6615
+ updated_at: "2026-05-20T11:00:00.000Z",
6616
+ });
6617
+
6618
+ const result = await dispatchCodexNativeHook(
6619
+ {
6620
+ hook_event_name: "Stop",
6621
+ cwd,
6622
+ session_id: sessionId,
6623
+ thread_id: "thread-stop-autopilot-terminal-run-state",
6624
+ turn_id: "turn-stop-autopilot-terminal-run-state-1",
6625
+ last_assistant_message: "Done. Verification passed.",
6626
+ },
6627
+ { cwd },
6628
+ );
6629
+
6630
+ assert.equal(result.omxEventName, "stop");
6631
+ assert.equal(result.outputJson, null);
6632
+ } finally {
6633
+ await rm(cwd, { recursive: true, force: true });
6634
+ }
6635
+ });
6636
+
6637
+ it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
6638
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
6639
+ try {
6640
+ const stateDir = join(cwd, ".omx", "state");
6641
+ const sessionId = "sess-stop-autopilot-active-ralplan";
6642
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6643
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
6644
+ active: true,
6645
+ mode: "autopilot",
6646
+ current_phase: "ralplan",
6647
+ });
6648
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
6649
+ version: 1,
6650
+ active: true,
6651
+ mode: "autopilot",
6652
+ outcome: "continue",
6653
+ current_phase: "ralplan",
6654
+ updated_at: "2026-05-20T11:00:00.000Z",
6655
+ });
6656
+
6657
+ const result = await dispatchCodexNativeHook(
6658
+ {
6659
+ hook_event_name: "Stop",
6660
+ cwd,
6661
+ session_id: sessionId,
6662
+ thread_id: "thread-stop-autopilot-active-ralplan",
6663
+ turn_id: "turn-stop-autopilot-active-ralplan-1",
6664
+ },
6665
+ { cwd },
6666
+ );
6667
+
6668
+ assert.equal(result.omxEventName, "stop");
6669
+ assert.deepEqual(result.outputJson, {
6670
+ decision: "block",
6671
+ reason:
6672
+ "OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
6673
+ stopReason: "autopilot_ralplan",
6674
+ systemMessage: "OMX autopilot is still active (phase: ralplan).",
6675
+ });
6676
+ } finally {
6677
+ await rm(cwd, { recursive: true, force: true });
6678
+ }
6679
+ });
6680
+
5620
6681
  it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
5621
6682
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
5622
6683
  try {
@@ -5808,6 +6869,7 @@ exit 0
5808
6869
  active: true,
5809
6870
  current_phase: "team-exec",
5810
6871
  team_name: "review-team",
6872
+ session_id: "sess-stop-team",
5811
6873
  });
5812
6874
  await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
5813
6875
  current_phase: "team-verify",
@@ -6695,45 +7757,129 @@ exit 0
6695
7757
  }
6696
7758
  });
6697
7759
 
6698
- it("emits one concise final decision summary and auto-finalize guidance when release-readiness already has a stable final recommendation and no active worker tasks", async () => {
6699
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
7760
+ it("does not block Stop from canonical team state owned by another thread", async () => {
7761
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-other-thread-"));
6700
7762
  try {
6701
7763
  await initTeamState(
6702
- "release-ready-team",
6703
- "release readiness finalize",
7764
+ "canonical-other-thread-team",
7765
+ "canonical other-thread stop fallback",
6704
7766
  "executor",
6705
7767
  1,
6706
7768
  cwd,
6707
7769
  undefined,
6708
- { ...process.env, OMX_SESSION_ID: "sess-stop-release-ready" },
6709
- );
6710
- await writeReleaseReadinessLeaderAttention(
6711
- "release-ready-team",
6712
- "sess-stop-release-ready",
6713
- cwd,
6714
- { workRemaining: false },
6715
- );
6716
- await writeReleaseReadinessStateMarker(
6717
- "sess-stop-release-ready",
6718
- "release-ready-team",
6719
- cwd,
7770
+ { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-thread" },
6720
7771
  );
7772
+ const manifestPath = join(cwd, ".omx", "state", "team", "canonical-other-thread-team", "manifest.v2.json");
7773
+ const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record<string, unknown>;
7774
+ await writeJson(manifestPath, {
7775
+ ...manifest,
7776
+ leader: {
7777
+ ...(manifest.leader as Record<string, unknown> | undefined),
7778
+ thread_id: "thread-other",
7779
+ },
7780
+ });
6721
7781
 
6722
7782
  const result = await dispatchCodexNativeHook(
6723
7783
  {
6724
7784
  hook_event_name: "Stop",
6725
7785
  cwd,
6726
- session_id: "sess-stop-release-ready",
6727
- thread_id: "thread-stop-release-ready",
6728
- turn_id: "turn-stop-release-ready-1",
6729
- mode: "release-readiness",
6730
- last_assistant_message: "Launch-ready: yes",
7786
+ session_id: "sess-stop-team-canonical-thread",
7787
+ thread_id: "thread-current",
6731
7788
  },
6732
7789
  { cwd },
6733
7790
  );
6734
7791
 
6735
7792
  assert.equal(result.omxEventName, "stop");
6736
- assert.deepEqual(result.outputJson, {
7793
+ assert.equal(result.outputJson, null);
7794
+ } finally {
7795
+ await rm(cwd, { recursive: true, force: true });
7796
+ }
7797
+ });
7798
+
7799
+ it("blocks Stop from canonical team state owned by the current thread", async () => {
7800
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-current-thread-"));
7801
+ try {
7802
+ await initTeamState(
7803
+ "canonical-current-thread-team",
7804
+ "canonical current-thread stop fallback",
7805
+ "executor",
7806
+ 1,
7807
+ cwd,
7808
+ undefined,
7809
+ { ...process.env, OMX_SESSION_ID: "sess-stop-team-canonical-current-thread" },
7810
+ );
7811
+ const manifestPath = join(cwd, ".omx", "state", "team", "canonical-current-thread-team", "manifest.v2.json");
7812
+ const manifest = JSON.parse(await readFile(manifestPath, "utf-8")) as Record<string, unknown>;
7813
+ await writeJson(manifestPath, {
7814
+ ...manifest,
7815
+ leader: {
7816
+ ...(manifest.leader as Record<string, unknown> | undefined),
7817
+ thread_id: "thread-current",
7818
+ },
7819
+ });
7820
+
7821
+ const result = await dispatchCodexNativeHook(
7822
+ {
7823
+ hook_event_name: "Stop",
7824
+ cwd,
7825
+ session_id: "sess-stop-team-canonical-current-thread",
7826
+ thread_id: "thread-current",
7827
+ },
7828
+ { cwd },
7829
+ );
7830
+
7831
+ assert.equal(result.omxEventName, "stop");
7832
+ assert.deepEqual(result.outputJson, {
7833
+ decision: "block",
7834
+ reason:
7835
+ `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}`,
7836
+ stopReason: "team_team-exec",
7837
+ systemMessage: "OMX team pipeline is still active at phase team-exec.",
7838
+ });
7839
+ } finally {
7840
+ await rm(cwd, { recursive: true, force: true });
7841
+ }
7842
+ });
7843
+
7844
+ it("emits one concise final decision summary and auto-finalize guidance when release-readiness already has a stable final recommendation and no active worker tasks", async () => {
7845
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
7846
+ try {
7847
+ await initTeamState(
7848
+ "release-ready-team",
7849
+ "release readiness finalize",
7850
+ "executor",
7851
+ 1,
7852
+ cwd,
7853
+ undefined,
7854
+ { ...process.env, OMX_SESSION_ID: "sess-stop-release-ready" },
7855
+ );
7856
+ await writeReleaseReadinessLeaderAttention(
7857
+ "release-ready-team",
7858
+ "sess-stop-release-ready",
7859
+ cwd,
7860
+ { workRemaining: false },
7861
+ );
7862
+ await writeReleaseReadinessStateMarker(
7863
+ "sess-stop-release-ready",
7864
+ "release-ready-team",
7865
+ cwd,
7866
+ );
7867
+
7868
+ const result = await dispatchCodexNativeHook(
7869
+ {
7870
+ hook_event_name: "Stop",
7871
+ cwd,
7872
+ session_id: "sess-stop-release-ready",
7873
+ thread_id: "thread-stop-release-ready",
7874
+ turn_id: "turn-stop-release-ready-1",
7875
+ mode: "release-readiness",
7876
+ last_assistant_message: "Launch-ready: yes",
7877
+ },
7878
+ { cwd },
7879
+ );
7880
+
7881
+ assert.equal(result.omxEventName, "stop");
7882
+ assert.deepEqual(result.outputJson, {
6737
7883
  decision: "block",
6738
7884
  reason:
6739
7885
  'Stable final recommendation already reached with no active worker tasks. Emit exactly one concise final decision summary aligned to "Launch-ready: yes." with no filler or residual acknowledgements (for example "yes"), then stop.',
@@ -6969,7 +8115,7 @@ exit 0
6969
8115
  const sharedRoot = join(cwd, "shared-root");
6970
8116
  const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
6971
8117
  try {
6972
- process.env.OMX_TEAM_STATE_ROOT = "shared-root";
8118
+ process.env.OMX_TEAM_STATE_ROOT = sharedRoot;
6973
8119
  await initTeamState(
6974
8120
  "canonical-root-team",
6975
8121
  "canonical stop root fallback",
@@ -6977,7 +8123,7 @@ exit 0
6977
8123
  1,
6978
8124
  cwd,
6979
8125
  undefined,
6980
- { ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: "shared-root" },
8126
+ { ...process.env, OMX_SESSION_ID: "sess-stop-team-root", OMX_TEAM_STATE_ROOT: sharedRoot },
6981
8127
  );
6982
8128
 
6983
8129
  const result = await dispatchCodexNativeHook(
@@ -7042,9 +8188,10 @@ exit 0
7042
8188
 
7043
8189
  it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
7044
8190
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
8191
+ const teamStateRoot = join(cwd, "shared-team-state");
7045
8192
  const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
7046
8193
  try {
7047
- process.env.OMX_TEAM_STATE_ROOT = "shared-team-state";
8194
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
7048
8195
  await initTeamState(
7049
8196
  "env-root-team",
7050
8197
  "env root stop fallback",
@@ -7055,7 +8202,7 @@ exit 0
7055
8202
  {
7056
8203
  ...process.env,
7057
8204
  OMX_SESSION_ID: "sess-stop-team-env-root",
7058
- OMX_TEAM_STATE_ROOT: "shared-team-state",
8205
+ OMX_TEAM_STATE_ROOT: teamStateRoot,
7059
8206
  },
7060
8207
  );
7061
8208
 
@@ -8381,16 +9528,29 @@ exit 0
8381
9528
  assert.equal(result.omxEventName, "stop");
8382
9529
  const reason = String(result.outputJson?.reason);
8383
9530
  assert.match(reason, /Ralph completion audit is missing required evidence/);
8384
- assert.match(reason, /state\.completion_audit = \{ passed: true, prompt_to_artifact_checklist: \[\.\.\.\], verification_evidence: \[\.\.\.\] \}/);
9531
+ assert.match(reason, /set "completion_audit" on the Ralph state object/);
9532
+ assert.doesNotMatch(reason, /state\.completion_audit/);
8385
9533
  assert.match(reason, /repo-relative JSON file/);
8386
9534
  assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
8387
9535
  assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
8388
9536
  const reopened = JSON.parse(await readFile(statePath, "utf-8")) as Record<string, unknown>;
8389
- assert.equal(reopened.active, true);
8390
- assert.equal(reopened.current_phase, "verifying");
9537
+ assert.equal(reopened.active, false);
9538
+ assert.equal(reopened.current_phase, "complete");
8391
9539
  assert.equal(reopened.completion_audit_gate, "blocked");
8392
9540
  assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
8393
- assert.equal(typeof reopened.completed_at, "undefined");
9541
+ assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
9542
+
9543
+ const repeat = await dispatchCodexNativeHook(
9544
+ {
9545
+ hook_event_name: "Stop",
9546
+ cwd,
9547
+ session_id: sessionId,
9548
+ last_assistant_message: "Done. Ralph complete.",
9549
+ },
9550
+ { cwd },
9551
+ );
9552
+ assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
9553
+ assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
8394
9554
  } finally {
8395
9555
  await rm(cwd, { recursive: true, force: true });
8396
9556
  }
@@ -10266,6 +11426,8 @@ exit 0
10266
11426
  active: true,
10267
11427
  current_phase: "team-exec",
10268
11428
  team_name: "review-team",
11429
+ session_id: "sess-stop-team-refire",
11430
+ thread_id: "thread-stop-team-refire",
10269
11431
  });
10270
11432
  await writeJson(join(stateDir, "team", "review-team", "phase.json"), {
10271
11433
  current_phase: "team-verify",
@@ -10505,6 +11667,250 @@ exit 0
10505
11667
  }
10506
11668
  });
10507
11669
 
11670
+ it("does not block Stop from root team state without team_name when no session is known", async () => {
11671
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
11672
+ try {
11673
+ const stateDir = join(cwd, ".omx", "state");
11674
+ await mkdir(stateDir, { recursive: true });
11675
+ await writeJson(join(stateDir, "team-state.json"), {
11676
+ active: true,
11677
+ mode: "team",
11678
+ current_phase: "starting",
11679
+ });
11680
+
11681
+ const result = await dispatchCodexNativeHook(
11682
+ {
11683
+ hook_event_name: "Stop",
11684
+ cwd,
11685
+ },
11686
+ { cwd },
11687
+ );
11688
+
11689
+ assert.equal(result.omxEventName, "stop");
11690
+ assert.equal(result.outputJson, null);
11691
+ } finally {
11692
+ await rm(cwd, { recursive: true, force: true });
11693
+ }
11694
+ });
11695
+
11696
+ it("does not block Stop from root team state without team_name for a foreign session", async () => {
11697
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
11698
+ try {
11699
+ const stateDir = join(cwd, ".omx", "state");
11700
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
11701
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
11702
+ await writeJson(join(stateDir, "team-state.json"), {
11703
+ active: true,
11704
+ mode: "team",
11705
+ current_phase: "starting",
11706
+ });
11707
+
11708
+ const result = await dispatchCodexNativeHook(
11709
+ {
11710
+ hook_event_name: "Stop",
11711
+ cwd,
11712
+ session_id: "sess-current",
11713
+ thread_id: "thread-current",
11714
+ },
11715
+ { cwd },
11716
+ );
11717
+
11718
+ assert.equal(result.omxEventName, "stop");
11719
+ assert.equal(result.outputJson, null);
11720
+ } finally {
11721
+ await rm(cwd, { recursive: true, force: true });
11722
+ }
11723
+ });
11724
+
11725
+ it("does not block Stop from another thread's stale root team state when no scoped team state exists", async () => {
11726
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-thread-"));
11727
+ try {
11728
+ const stateDir = join(cwd, ".omx", "state");
11729
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
11730
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
11731
+ await writeJson(join(stateDir, "team-state.json"), {
11732
+ active: true,
11733
+ current_phase: "starting",
11734
+ team_name: "stale-root-thread-team",
11735
+ session_id: "sess-current",
11736
+ thread_id: "thread-other",
11737
+ });
11738
+ await writeJson(join(stateDir, "team", "stale-root-thread-team", "phase.json"), {
11739
+ current_phase: "team-exec",
11740
+ max_fix_attempts: 3,
11741
+ current_fix_attempt: 0,
11742
+ transitions: [],
11743
+ updated_at: new Date().toISOString(),
11744
+ });
11745
+
11746
+ const result = await dispatchCodexNativeHook(
11747
+ {
11748
+ hook_event_name: "Stop",
11749
+ cwd,
11750
+ session_id: "sess-current",
11751
+ thread_id: "thread-current",
11752
+ },
11753
+ { cwd },
11754
+ );
11755
+
11756
+ assert.equal(result.omxEventName, "stop");
11757
+ assert.equal(result.outputJson, null);
11758
+ } finally {
11759
+ await rm(cwd, { recursive: true, force: true });
11760
+ }
11761
+ });
11762
+
11763
+ it("does not block Stop from root team state with matching session but missing thread ownership", async () => {
11764
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-thread-"));
11765
+ try {
11766
+ const stateDir = join(cwd, ".omx", "state");
11767
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
11768
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
11769
+ await writeJson(join(stateDir, "team-state.json"), {
11770
+ active: true,
11771
+ current_phase: "starting",
11772
+ team_name: "root-missing-thread-team",
11773
+ session_id: "sess-current",
11774
+ });
11775
+ await writeJson(join(stateDir, "team", "root-missing-thread-team", "phase.json"), {
11776
+ current_phase: "team-exec",
11777
+ max_fix_attempts: 3,
11778
+ current_fix_attempt: 0,
11779
+ transitions: [],
11780
+ updated_at: new Date().toISOString(),
11781
+ });
11782
+
11783
+ const result = await dispatchCodexNativeHook(
11784
+ {
11785
+ hook_event_name: "Stop",
11786
+ cwd,
11787
+ session_id: "sess-current",
11788
+ thread_id: "thread-current",
11789
+ },
11790
+ { cwd },
11791
+ );
11792
+
11793
+ assert.equal(result.omxEventName, "stop");
11794
+ assert.equal(result.outputJson, null);
11795
+ } finally {
11796
+ await rm(cwd, { recursive: true, force: true });
11797
+ }
11798
+ });
11799
+
11800
+ it("does not block Stop from root team state when canonical phase is missing", async () => {
11801
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-missing-phase-"));
11802
+ try {
11803
+ const stateDir = join(cwd, ".omx", "state");
11804
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
11805
+ await mkdir(join(stateDir, "team", "root-missing-phase-team"), { recursive: true });
11806
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
11807
+ await writeJson(join(stateDir, "team-state.json"), {
11808
+ active: true,
11809
+ current_phase: "starting",
11810
+ team_name: "root-missing-phase-team",
11811
+ session_id: "sess-current",
11812
+ thread_id: "thread-current",
11813
+ });
11814
+
11815
+ const result = await dispatchCodexNativeHook(
11816
+ {
11817
+ hook_event_name: "Stop",
11818
+ cwd,
11819
+ session_id: "sess-current",
11820
+ thread_id: "thread-current",
11821
+ },
11822
+ { cwd },
11823
+ );
11824
+
11825
+ assert.equal(result.omxEventName, "stop");
11826
+ assert.equal(result.outputJson, null);
11827
+ } finally {
11828
+ await rm(cwd, { recursive: true, force: true });
11829
+ }
11830
+ });
11831
+
11832
+ it("does not block Stop from session-scoped team state owned by another thread", async () => {
11833
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-other-thread-"));
11834
+ try {
11835
+ const stateDir = join(cwd, ".omx", "state");
11836
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
11837
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
11838
+ await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
11839
+ active: true,
11840
+ current_phase: "starting",
11841
+ team_name: "scoped-other-thread-team",
11842
+ session_id: "sess-current",
11843
+ thread_id: "thread-other",
11844
+ });
11845
+ await writeJson(join(stateDir, "team", "scoped-other-thread-team", "phase.json"), {
11846
+ current_phase: "team-exec",
11847
+ max_fix_attempts: 3,
11848
+ current_fix_attempt: 0,
11849
+ transitions: [],
11850
+ updated_at: new Date().toISOString(),
11851
+ });
11852
+
11853
+ const result = await dispatchCodexNativeHook(
11854
+ {
11855
+ hook_event_name: "Stop",
11856
+ cwd,
11857
+ session_id: "sess-current",
11858
+ thread_id: "thread-current",
11859
+ },
11860
+ { cwd },
11861
+ );
11862
+
11863
+ assert.equal(result.omxEventName, "stop");
11864
+ assert.equal(result.outputJson, null);
11865
+ } finally {
11866
+ await rm(cwd, { recursive: true, force: true });
11867
+ }
11868
+ });
11869
+
11870
+ it("blocks Stop from session-scoped team state owned by the current session and thread", async () => {
11871
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-scoped-team-current-thread-"));
11872
+ try {
11873
+ const stateDir = join(cwd, ".omx", "state");
11874
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
11875
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
11876
+ await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
11877
+ active: true,
11878
+ current_phase: "starting",
11879
+ team_name: "scoped-current-team",
11880
+ session_id: "sess-current",
11881
+ thread_id: "thread-current",
11882
+ });
11883
+ await writeJson(join(stateDir, "team", "scoped-current-team", "phase.json"), {
11884
+ current_phase: "team-exec",
11885
+ max_fix_attempts: 3,
11886
+ current_fix_attempt: 0,
11887
+ transitions: [],
11888
+ updated_at: new Date().toISOString(),
11889
+ });
11890
+
11891
+ const result = await dispatchCodexNativeHook(
11892
+ {
11893
+ hook_event_name: "Stop",
11894
+ cwd,
11895
+ session_id: "sess-current",
11896
+ thread_id: "thread-current",
11897
+ },
11898
+ { cwd },
11899
+ );
11900
+
11901
+ assert.equal(result.omxEventName, "stop");
11902
+ assert.deepEqual(result.outputJson, {
11903
+ decision: "block",
11904
+ reason:
11905
+ `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}`,
11906
+ stopReason: "team_team-exec",
11907
+ systemMessage: "OMX team pipeline is still active at phase team-exec.",
11908
+ });
11909
+ } finally {
11910
+ await rm(cwd, { recursive: true, force: true });
11911
+ }
11912
+ });
11913
+
10508
11914
  it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
10509
11915
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
10510
11916
  try {
@@ -10718,6 +12124,91 @@ describe("codex native hook triage integration", () => {
10718
12124
  }
10719
12125
  });
10720
12126
 
12127
+
12128
+ it("does not activate workflow state for native subagent prompts even when canonical id is the child session", async () => {
12129
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-"));
12130
+ const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-subagent-keyword-boxed-"));
12131
+ const originalOmxRoot = process.env.OMX_ROOT;
12132
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
12133
+ const originalTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
12134
+ try {
12135
+ process.env.OMX_ROOT = boxedRoot;
12136
+ delete process.env.OMX_STATE_ROOT;
12137
+ delete process.env.OMX_TEAM_STATE_ROOT;
12138
+ const boxedStateDir = getBaseStateDir(cwd);
12139
+ await mkdir(boxedStateDir, { recursive: true });
12140
+ await writeJson(join(boxedStateDir, "subagent-tracking.json"), {
12141
+ schemaVersion: 1,
12142
+ sessions: {
12143
+ "omx-parent-session": {
12144
+ session_id: "omx-parent-session",
12145
+ leader_thread_id: "parent-native-thread",
12146
+ updated_at: "2026-05-21T19:04:40.000Z",
12147
+ threads: {
12148
+ "parent-native-thread": {
12149
+ thread_id: "parent-native-thread",
12150
+ kind: "leader",
12151
+ first_seen_at: "2026-05-21T19:04:40.000Z",
12152
+ last_seen_at: "2026-05-21T19:04:40.000Z",
12153
+ turn_count: 1,
12154
+ },
12155
+ "child-native-session": {
12156
+ thread_id: "child-native-session",
12157
+ kind: "subagent",
12158
+ first_seen_at: "2026-05-21T19:04:41.000Z",
12159
+ last_seen_at: "2026-05-21T19:04:41.000Z",
12160
+ turn_count: 1,
12161
+ mode: "review",
12162
+ },
12163
+ },
12164
+ },
12165
+ },
12166
+ });
12167
+
12168
+ const result = await dispatchCodexNativeHook(
12169
+ {
12170
+ hook_event_name: "UserPromptSubmit",
12171
+ cwd,
12172
+ session_id: "child-native-session",
12173
+ thread_id: "child-native-session",
12174
+ turn_id: "turn-subagent-review",
12175
+ prompt: [
12176
+ "Read-only review only. Do not edit files. Do not inspect/mutate OMX state/hooks.",
12177
+ "Context: The user asked for $autopilot, and this subagent must only review the patch.",
12178
+ ].join("\n\n"),
12179
+ },
12180
+ { cwd },
12181
+ );
12182
+
12183
+ const additionalContext = String(
12184
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
12185
+ );
12186
+ assert.equal(additionalContext, "");
12187
+ assert.equal(
12188
+ existsSync(join(boxedStateDir, "sessions", "child-native-session", "skill-active-state.json")),
12189
+ false,
12190
+ );
12191
+ assert.equal(
12192
+ existsSync(join(boxedStateDir, "sessions", "child-native-session", "autopilot-state.json")),
12193
+ false,
12194
+ );
12195
+ assert.equal(
12196
+ existsSync(join(cwd, ".omx", "state", "subagent-tracking.json")),
12197
+ false,
12198
+ "subagent tracking must not leak into the source worktree when OMX_ROOT is boxed",
12199
+ );
12200
+ } finally {
12201
+ if (originalOmxRoot === undefined) delete process.env.OMX_ROOT;
12202
+ else process.env.OMX_ROOT = originalOmxRoot;
12203
+ if (originalOmxStateRoot === undefined) delete process.env.OMX_STATE_ROOT;
12204
+ else process.env.OMX_STATE_ROOT = originalOmxStateRoot;
12205
+ if (originalTeamStateRoot === undefined) delete process.env.OMX_TEAM_STATE_ROOT;
12206
+ else process.env.OMX_TEAM_STATE_ROOT = originalTeamStateRoot;
12207
+ await rm(cwd, { recursive: true, force: true });
12208
+ await rm(boxedRoot, { recursive: true, force: true });
12209
+ }
12210
+ });
12211
+
10721
12212
  it("does not inject triage advisory for autopilot keyword prompts", async () => {
10722
12213
  const cwd = await mkdtemp(join(tmpdir(), "omx-triage-keyword-autopilot-"));
10723
12214
  try {
@@ -10749,6 +12240,62 @@ describe("codex native hook triage integration", () => {
10749
12240
  }
10750
12241
  });
10751
12242
 
12243
+ it("makes autopilot keyword activation observable in state, HUD context, and prompt guidance", async () => {
12244
+ const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-"));
12245
+ try {
12246
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
12247
+ await writeSessionStart(cwd, "sess-autopilot-observable");
12248
+
12249
+ const result = await dispatchCodexNativeHook(
12250
+ {
12251
+ hook_event_name: "UserPromptSubmit",
12252
+ cwd,
12253
+ session_id: "sess-autopilot-observable",
12254
+ thread_id: "thread-autopilot-observable",
12255
+ turn_id: "turn-autopilot-observable",
12256
+ prompt: "$autopilot implement issue #2430",
12257
+ },
12258
+ { cwd },
12259
+ );
12260
+
12261
+ assert.equal(result.skillState?.skill, "autopilot");
12262
+ assert.equal(result.skillState?.phase, "deep-interview");
12263
+ assert.equal(result.skillState?.initialized_state_path, ".omx/state/sessions/sess-autopilot-observable/autopilot-state.json");
12264
+
12265
+ const additionalContext = String(
12266
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
12267
+ );
12268
+ assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
12269
+ assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal \(\+ \$team if needed\) -> \$code-review -> \$ultraqa/);
12270
+ assert.match(additionalContext, /deep_interview_gate\.skip_reason/);
12271
+ assert.match(additionalContext, /Do not silently fall back to ordinary \$plan\/ralplan-only handling/);
12272
+ assert.match(additionalContext, /Codex goal-mode handoff guidance/);
12273
+ assert.doesNotMatch(additionalContext, /multi-step goal with no workflow keyword/);
12274
+
12275
+ const statePath = join(cwd, ".omx", "state", "sessions", "sess-autopilot-observable", "autopilot-state.json");
12276
+ const modeState = JSON.parse(await readFile(statePath, "utf-8")) as {
12277
+ active: boolean;
12278
+ current_phase: string;
12279
+ state?: { phase_cycle?: string[]; deep_interview_gate?: { status?: string; skip_reason?: string | null } };
12280
+ };
12281
+ assert.equal(modeState.active, true);
12282
+ assert.equal(modeState.current_phase, "deep-interview");
12283
+ assert.deepEqual(modeState.state?.phase_cycle, ["deep-interview", "ralplan", "ultragoal", "code-review", "ultraqa"]);
12284
+ assert.deepEqual(modeState.state?.deep_interview_gate, {
12285
+ status: "required",
12286
+ skip_reason: null,
12287
+ rationale: "Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.",
12288
+ });
12289
+
12290
+ const hudState = await readAllState(cwd);
12291
+ assert.equal(hudState.autopilot?.active, true);
12292
+ assert.equal(hudState.autopilot?.current_phase, "deep-interview");
12293
+ assert.match(renderHud(hudState, "focused"), /autopilot:deep-interview/);
12294
+ } finally {
12295
+ await rm(cwd, { recursive: true, force: true });
12296
+ }
12297
+ });
12298
+
10752
12299
  // ── Group 2: HEAVY injection ─────────────────────────────────────────────
10753
12300
 
10754
12301
  it("injects HEAVY advisory and writes prompt-routing-state for a multi-step goal prompt", async () => {