oh-my-codex 0.16.3 → 0.17.0

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 (339) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +3 -3
  4. package/dist/catalog/__tests__/generator.test.js +2 -0
  5. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  6. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +9 -0
  7. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  8. package/dist/cli/__tests__/cleanup.test.js +27 -0
  9. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  10. package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -5
  11. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  12. package/dist/cli/__tests__/doctor-warning-copy.test.js +175 -7
  13. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  14. package/dist/cli/__tests__/index.test.js +147 -12
  15. package/dist/cli/__tests__/index.test.js.map +1 -1
  16. package/dist/cli/__tests__/mcp-serve.test.js +4 -0
  17. package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
  18. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +2 -0
  19. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  20. package/dist/cli/__tests__/ralph.test.js +47 -0
  21. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  22. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +10 -5
  23. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  24. package/dist/cli/__tests__/setup-install-mode.test.js +299 -27
  25. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  26. package/dist/cli/__tests__/setup-refresh.test.js +85 -3
  27. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  28. package/dist/cli/__tests__/setup-scope.test.js +1 -1
  29. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  30. package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -1
  31. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  32. package/dist/cli/__tests__/team.test.js +108 -0
  33. package/dist/cli/__tests__/team.test.js.map +1 -1
  34. package/dist/cli/__tests__/ultragoal.test.js +91 -0
  35. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  36. package/dist/cli/__tests__/uninstall.test.js +54 -8
  37. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  38. package/dist/cli/cleanup.d.ts.map +1 -1
  39. package/dist/cli/cleanup.js +8 -4
  40. package/dist/cli/cleanup.js.map +1 -1
  41. package/dist/cli/codex-feature-probe.d.ts +9 -0
  42. package/dist/cli/codex-feature-probe.d.ts.map +1 -0
  43. package/dist/cli/codex-feature-probe.js +28 -0
  44. package/dist/cli/codex-feature-probe.js.map +1 -0
  45. package/dist/cli/doctor.d.ts +1 -0
  46. package/dist/cli/doctor.d.ts.map +1 -1
  47. package/dist/cli/doctor.js +214 -23
  48. package/dist/cli/doctor.js.map +1 -1
  49. package/dist/cli/index.d.ts +17 -4
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +152 -24
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/cli/mcp-parity.js +8 -8
  54. package/dist/cli/mcp-parity.js.map +1 -1
  55. package/dist/cli/mcp-serve.d.ts.map +1 -1
  56. package/dist/cli/mcp-serve.js +4 -0
  57. package/dist/cli/mcp-serve.js.map +1 -1
  58. package/dist/cli/plugin-marketplace.d.ts +23 -0
  59. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  60. package/dist/cli/plugin-marketplace.js +203 -1
  61. package/dist/cli/plugin-marketplace.js.map +1 -1
  62. package/dist/cli/ralph.d.ts.map +1 -1
  63. package/dist/cli/ralph.js +21 -0
  64. package/dist/cli/ralph.js.map +1 -1
  65. package/dist/cli/setup-preferences.d.ts +4 -0
  66. package/dist/cli/setup-preferences.d.ts.map +1 -1
  67. package/dist/cli/setup-preferences.js +7 -0
  68. package/dist/cli/setup-preferences.js.map +1 -1
  69. package/dist/cli/setup.d.ts +5 -3
  70. package/dist/cli/setup.d.ts.map +1 -1
  71. package/dist/cli/setup.js +140 -51
  72. package/dist/cli/setup.js.map +1 -1
  73. package/dist/cli/ultragoal.d.ts +1 -1
  74. package/dist/cli/ultragoal.d.ts.map +1 -1
  75. package/dist/cli/ultragoal.js +70 -5
  76. package/dist/cli/ultragoal.js.map +1 -1
  77. package/dist/cli/uninstall.d.ts +2 -0
  78. package/dist/cli/uninstall.d.ts.map +1 -1
  79. package/dist/cli/uninstall.js +12 -3
  80. package/dist/cli/uninstall.js.map +1 -1
  81. package/dist/config/__tests__/codex-feature-flags.test.d.ts +2 -0
  82. package/dist/config/__tests__/codex-feature-flags.test.d.ts.map +1 -0
  83. package/dist/config/__tests__/codex-feature-flags.test.js +35 -0
  84. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -0
  85. package/dist/config/__tests__/codex-hooks.test.js +143 -9
  86. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  87. package/dist/config/__tests__/generator-idempotent.test.js +85 -9
  88. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  89. package/dist/config/__tests__/generator-notify.test.js +116 -11
  90. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  91. package/dist/config/__tests__/wiki-config-contract.test.js +6 -3
  92. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -1
  93. package/dist/config/codex-feature-flags.d.ts +21 -0
  94. package/dist/config/codex-feature-flags.d.ts.map +1 -0
  95. package/dist/config/codex-feature-flags.js +56 -0
  96. package/dist/config/codex-feature-flags.js.map +1 -0
  97. package/dist/config/codex-hooks.d.ts +14 -13
  98. package/dist/config/codex-hooks.d.ts.map +1 -1
  99. package/dist/config/codex-hooks.js +108 -8
  100. package/dist/config/codex-hooks.js.map +1 -1
  101. package/dist/config/generator.d.ts +15 -3
  102. package/dist/config/generator.d.ts.map +1 -1
  103. package/dist/config/generator.js +233 -129
  104. package/dist/config/generator.js.map +1 -1
  105. package/dist/config/omx-first-party-mcp.d.ts +3 -1
  106. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  107. package/dist/config/omx-first-party-mcp.js +9 -2
  108. package/dist/config/omx-first-party-mcp.js.map +1 -1
  109. package/dist/hooks/__tests__/design-skill.test.d.ts +2 -0
  110. package/dist/hooks/__tests__/design-skill.test.d.ts.map +1 -0
  111. package/dist/hooks/__tests__/design-skill.test.js +55 -0
  112. package/dist/hooks/__tests__/design-skill.test.js.map +1 -0
  113. package/dist/hooks/__tests__/keyword-detector.test.js +92 -2
  114. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  115. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +125 -1
  116. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +265 -0
  118. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  119. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts +2 -0
  120. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts.map +1 -0
  121. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +84 -0
  122. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -0
  123. package/dist/hooks/__tests__/skill-guidance-contract.test.js +41 -0
  124. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  125. package/dist/hooks/agents-overlay.js +2 -2
  126. package/dist/hooks/agents-overlay.js.map +1 -1
  127. package/dist/hooks/keyword-detector.d.ts +1 -0
  128. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  129. package/dist/hooks/keyword-detector.js +12 -6
  130. package/dist/hooks/keyword-detector.js.map +1 -1
  131. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  132. package/dist/hooks/keyword-registry.js +2 -0
  133. package/dist/hooks/keyword-registry.js.map +1 -1
  134. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  135. package/dist/hooks/prompt-guidance-contract.js +47 -2
  136. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  137. package/dist/hud/__tests__/state.test.js +164 -0
  138. package/dist/hud/__tests__/state.test.js.map +1 -1
  139. package/dist/hud/state.d.ts.map +1 -1
  140. package/dist/hud/state.js +4 -5
  141. package/dist/hud/state.js.map +1 -1
  142. package/dist/mcp/__tests__/bootstrap.test.js +3 -0
  143. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  144. package/dist/mcp/__tests__/hermes-bridge.test.d.ts +2 -0
  145. package/dist/mcp/__tests__/hermes-bridge.test.d.ts.map +1 -0
  146. package/dist/mcp/__tests__/hermes-bridge.test.js +374 -0
  147. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -0
  148. package/dist/mcp/__tests__/state-paths.test.js +155 -11
  149. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  150. package/dist/mcp/__tests__/state-server.test.js +166 -0
  151. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  152. package/dist/mcp/bootstrap.d.ts +1 -1
  153. package/dist/mcp/bootstrap.d.ts.map +1 -1
  154. package/dist/mcp/bootstrap.js +2 -0
  155. package/dist/mcp/bootstrap.js.map +1 -1
  156. package/dist/mcp/hermes-bridge.d.ts +81 -0
  157. package/dist/mcp/hermes-bridge.d.ts.map +1 -0
  158. package/dist/mcp/hermes-bridge.js +400 -0
  159. package/dist/mcp/hermes-bridge.js.map +1 -0
  160. package/dist/mcp/hermes-server.d.ts +269 -0
  161. package/dist/mcp/hermes-server.d.ts.map +1 -0
  162. package/dist/mcp/hermes-server.js +121 -0
  163. package/dist/mcp/hermes-server.js.map +1 -0
  164. package/dist/mcp/state-paths.d.ts.map +1 -1
  165. package/dist/mcp/state-paths.js +64 -11
  166. package/dist/mcp/state-paths.js.map +1 -1
  167. package/dist/modes/__tests__/base-session-scope.test.js +22 -0
  168. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  169. package/dist/modes/__tests__/base-tmux-pane.test.js +88 -27
  170. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  171. package/dist/modes/base.d.ts.map +1 -1
  172. package/dist/modes/base.js +5 -0
  173. package/dist/modes/base.js.map +1 -1
  174. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts +2 -0
  175. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts.map +1 -0
  176. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +316 -0
  177. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -0
  178. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts +2 -0
  179. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts.map +1 -0
  180. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +481 -0
  181. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -0
  182. package/dist/planning/__tests__/artifacts.test.js +533 -4
  183. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  184. package/dist/planning/__tests__/context-pack-status.test.js +524 -0
  185. package/dist/planning/__tests__/context-pack-status.test.js.map +1 -1
  186. package/dist/planning/__tests__/markdown-structure.test.d.ts +2 -0
  187. package/dist/planning/__tests__/markdown-structure.test.d.ts.map +1 -0
  188. package/dist/planning/__tests__/markdown-structure.test.js +459 -0
  189. package/dist/planning/__tests__/markdown-structure.test.js.map +1 -0
  190. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +523 -1
  191. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -1
  192. package/dist/planning/artifacts.d.ts +1 -1
  193. package/dist/planning/artifacts.d.ts.map +1 -1
  194. package/dist/planning/artifacts.js +227 -28
  195. package/dist/planning/artifacts.js.map +1 -1
  196. package/dist/planning/context-pack-status.d.ts +25 -0
  197. package/dist/planning/context-pack-status.d.ts.map +1 -1
  198. package/dist/planning/context-pack-status.js +272 -31
  199. package/dist/planning/context-pack-status.js.map +1 -1
  200. package/dist/planning/markdown-structure.d.ts +20 -0
  201. package/dist/planning/markdown-structure.d.ts.map +1 -0
  202. package/dist/planning/markdown-structure.js +137 -0
  203. package/dist/planning/markdown-structure.js.map +1 -0
  204. package/dist/ralph/__tests__/completion-audit.test.d.ts +2 -0
  205. package/dist/ralph/__tests__/completion-audit.test.d.ts.map +1 -0
  206. package/dist/ralph/__tests__/completion-audit.test.js +121 -0
  207. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -0
  208. package/dist/ralph/completion-audit.d.ts +8 -0
  209. package/dist/ralph/completion-audit.d.ts.map +1 -0
  210. package/dist/ralph/completion-audit.js +99 -0
  211. package/dist/ralph/completion-audit.js.map +1 -0
  212. package/dist/scripts/__tests__/codex-native-hook.test.js +407 -15
  213. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  214. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts +2 -0
  215. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts.map +1 -0
  216. package/dist/scripts/__tests__/notify-dispatcher.test.js +126 -0
  217. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -0
  218. package/dist/scripts/codex-native-hook.d.ts +1 -0
  219. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  220. package/dist/scripts/codex-native-hook.js +177 -71
  221. package/dist/scripts/codex-native-hook.js.map +1 -1
  222. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  223. package/dist/scripts/codex-native-pre-post.js +4 -2
  224. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  225. package/dist/scripts/notify-dispatcher.js +30 -1
  226. package/dist/scripts/notify-dispatcher.js.map +1 -1
  227. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  228. package/dist/scripts/notify-hook/tmux-injection.js +91 -2
  229. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  230. package/dist/scripts/notify-hook.js +3 -1
  231. package/dist/scripts/notify-hook.js.map +1 -1
  232. package/dist/state/__tests__/workflow-transition.test.js +102 -27
  233. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  234. package/dist/state/mode-state-context.d.ts +2 -0
  235. package/dist/state/mode-state-context.d.ts.map +1 -1
  236. package/dist/state/mode-state-context.js +21 -0
  237. package/dist/state/mode-state-context.js.map +1 -1
  238. package/dist/state/operations.d.ts.map +1 -1
  239. package/dist/state/operations.js +9 -3
  240. package/dist/state/operations.js.map +1 -1
  241. package/dist/state/skill-active.d.ts +7 -0
  242. package/dist/state/skill-active.d.ts.map +1 -1
  243. package/dist/state/skill-active.js +25 -8
  244. package/dist/state/skill-active.js.map +1 -1
  245. package/dist/state/workflow-transition-reconcile.d.ts +1 -0
  246. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  247. package/dist/state/workflow-transition-reconcile.js +22 -15
  248. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  249. package/dist/state/workflow-transition.js +3 -3
  250. package/dist/state/workflow-transition.js.map +1 -1
  251. package/dist/team/__tests__/approved-execution.test.js +39 -0
  252. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  253. package/dist/team/__tests__/runtime.test.js +5 -0
  254. package/dist/team/__tests__/runtime.test.js.map +1 -1
  255. package/dist/team/__tests__/scaling.test.js +497 -2
  256. package/dist/team/__tests__/scaling.test.js.map +1 -1
  257. package/dist/team/__tests__/state-root.test.js +1 -1
  258. package/dist/team/__tests__/state-root.test.js.map +1 -1
  259. package/dist/team/__tests__/worker-bootstrap.test.js +8 -0
  260. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  261. package/dist/team/approved-execution.d.ts.map +1 -1
  262. package/dist/team/approved-execution.js +3 -0
  263. package/dist/team/approved-execution.js.map +1 -1
  264. package/dist/team/scaling.d.ts.map +1 -1
  265. package/dist/team/scaling.js +43 -0
  266. package/dist/team/scaling.js.map +1 -1
  267. package/dist/team/state-root.d.ts.map +1 -1
  268. package/dist/team/state-root.js +4 -0
  269. package/dist/team/state-root.js.map +1 -1
  270. package/dist/team/state.d.ts.map +1 -1
  271. package/dist/team/state.js +2 -6
  272. package/dist/team/state.js.map +1 -1
  273. package/dist/ultragoal/__tests__/artifacts.test.js +245 -1
  274. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  275. package/dist/ultragoal/__tests__/docs-contract.test.js +21 -0
  276. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  277. package/dist/ultragoal/artifacts.d.ts +52 -2
  278. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  279. package/dist/ultragoal/artifacts.js +301 -15
  280. package/dist/ultragoal/artifacts.js.map +1 -1
  281. package/dist/utils/__tests__/paths.test.js +31 -1
  282. package/dist/utils/__tests__/paths.test.js.map +1 -1
  283. package/dist/utils/paths.d.ts +6 -0
  284. package/dist/utils/paths.d.ts.map +1 -1
  285. package/dist/utils/paths.js +18 -0
  286. package/dist/utils/paths.js.map +1 -1
  287. package/dist/wiki/lifecycle.js +4 -4
  288. package/dist/wiki/lifecycle.js.map +1 -1
  289. package/package.json +1 -1
  290. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  291. package/plugins/oh-my-codex/.mcp.json +13 -5
  292. package/plugins/oh-my-codex/skills/analyze/SKILL.md +0 -2
  293. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +2 -2
  294. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -3
  295. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +5 -7
  296. package/plugins/oh-my-codex/skills/design/SKILL.md +180 -0
  297. package/plugins/oh-my-codex/skills/doctor/SKILL.md +2 -2
  298. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +3 -3
  299. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +3 -3
  300. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -6
  301. package/plugins/oh-my-codex/skills/ralph/SKILL.md +9 -10
  302. package/plugins/oh-my-codex/skills/skill/SKILL.md +2 -1
  303. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +36 -3
  304. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +175 -64
  305. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +8 -8
  306. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +2 -2
  307. package/plugins/oh-my-codex/skills/wiki/SKILL.md +13 -13
  308. package/skills/analyze/SKILL.md +0 -2
  309. package/skills/ask-claude/SKILL.md +5 -3
  310. package/skills/ask-gemini/SKILL.md +5 -3
  311. package/skills/autopilot/SKILL.md +2 -2
  312. package/skills/code-review/SKILL.md +1 -3
  313. package/skills/deep-interview/SKILL.md +5 -7
  314. package/skills/design/SKILL.md +180 -0
  315. package/skills/doctor/SKILL.md +2 -2
  316. package/skills/ecomode/SKILL.md +105 -1
  317. package/skills/frontend-ui-ux/SKILL.md +6 -24
  318. package/skills/git-master/SKILL.md +2 -4
  319. package/skills/omx-setup/SKILL.md +3 -3
  320. package/skills/pipeline/SKILL.md +3 -3
  321. package/skills/plan/SKILL.md +3 -6
  322. package/skills/ralph/SKILL.md +9 -10
  323. package/skills/skill/SKILL.md +2 -1
  324. package/skills/swarm/SKILL.md +5 -3
  325. package/skills/tdd/SKILL.md +95 -1
  326. package/skills/ultragoal/SKILL.md +36 -3
  327. package/skills/ultraqa/SKILL.md +175 -64
  328. package/skills/ultrawork/SKILL.md +8 -8
  329. package/skills/visual-ralph/SKILL.md +2 -2
  330. package/skills/web-clone/SKILL.md +348 -1
  331. package/skills/wiki/SKILL.md +13 -13
  332. package/src/scripts/__tests__/codex-native-hook.test.ts +437 -14
  333. package/src/scripts/__tests__/notify-dispatcher.test.ts +153 -0
  334. package/src/scripts/codex-native-hook.ts +205 -61
  335. package/src/scripts/codex-native-pre-post.ts +4 -1
  336. package/src/scripts/notify-dispatcher.ts +40 -1
  337. package/src/scripts/notify-hook/tmux-injection.ts +110 -3
  338. package/src/scripts/notify-hook.ts +3 -1
  339. package/templates/catalog-manifest.json +9 -2
@@ -17,6 +17,7 @@ import {
17
17
  import {
18
18
  dispatchCodexNativeHook,
19
19
  isCodexNativeHookMainModule,
20
+ looksLikeGoalCompletionPrompt,
20
21
  mapCodexHookEventToOmxEvent,
21
22
  resolveSessionOwnerPidFromAncestry,
22
23
  } from "../codex-native-hook.js";
@@ -24,7 +25,8 @@ import { writeSessionStart } from "../../hooks/session.js";
24
25
  import { resetTriageConfigCache } from "../../hooks/triage-config.js";
25
26
  import { executeStateOperation } from "../../state/operations.js";
26
27
  import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
27
- import { writePage } from "../../wiki/storage.js";
28
+ import { readAllState } from "../../hud/state.js";
29
+ import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
28
30
  import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
29
31
 
30
32
  function nativeHookScriptPath(): string {
@@ -236,7 +238,7 @@ describe("codex native hook config", () => {
236
238
  matcher?: string;
237
239
  hooks?: Array<Record<string, unknown>>;
238
240
  };
239
- assert.equal(sessionStart.matcher, "startup|resume");
241
+ assert.equal(sessionStart.matcher, "startup|resume|clear");
240
242
  assert.equal(sessionStart.hooks?.[0]?.statusMessage, undefined);
241
243
 
242
244
  const preToolUse = config.hooks.PreToolUse[0] as {
@@ -1093,6 +1095,68 @@ describe("codex native hook dispatch", () => {
1093
1095
  }
1094
1096
  });
1095
1097
 
1098
+ it("prefers repository project-memory.json during SessionStart while preserving legacy wiki guidance", async () => {
1099
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-root-memory-legacy-wiki-"));
1100
+ try {
1101
+ const now = new Date().toISOString();
1102
+ const legacyWikiDir = getLegacyWikiDir(cwd);
1103
+ await mkdir(legacyWikiDir, { recursive: true });
1104
+ await writeFile(join(legacyWikiDir, "legacy.md"), serializePage({
1105
+ filename: "legacy.md",
1106
+ frontmatter: {
1107
+ title: "Legacy",
1108
+ tags: ["legacy"],
1109
+ created: now,
1110
+ updated: now,
1111
+ sources: [],
1112
+ links: [],
1113
+ category: "reference",
1114
+ confidence: "medium",
1115
+ schemaVersion: WIKI_SCHEMA_VERSION,
1116
+ },
1117
+ content: "\n# Legacy\n\nLegacy wiki context must remain visible.\n",
1118
+ }));
1119
+ await writeJson(join(cwd, ".omx", "project-memory.json"), {
1120
+ techStack: "Legacy runtime memory should not win",
1121
+ notes: [{ category: "legacy", content: "stale legacy note", timestamp: now }],
1122
+ });
1123
+ await writeJson(join(cwd, "project-memory.json"), {
1124
+ techStack: "Canonical root memory",
1125
+ build: "npm run build && node --test dist/scripts/__tests__/codex-native-hook.test.js",
1126
+ conventions: "prefer repository-visible project memory at startup",
1127
+ directives: [
1128
+ { directive: "Load root project-memory.json before legacy .omx memory.", priority: "high", timestamp: now },
1129
+ ],
1130
+ notes: [
1131
+ { category: "issue", content: "Regression fixture for issue #2273.", timestamp: now },
1132
+ ],
1133
+ });
1134
+
1135
+ const result = await dispatchCodexNativeHook(
1136
+ {
1137
+ hook_event_name: "SessionStart",
1138
+ cwd,
1139
+ session_id: "sess-root-memory-legacy-wiki",
1140
+ },
1141
+ { cwd, sessionOwnerPid: 43210 },
1142
+ );
1143
+
1144
+ const additionalContext = String(
1145
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
1146
+ );
1147
+ assert.match(additionalContext, /\[Project memory\]/);
1148
+ assert.match(additionalContext, /source: project-memory\.json/);
1149
+ assert.match(additionalContext, /Canonical root memory/);
1150
+ assert.match(additionalContext, /Load root project-memory\.json before legacy \.omx memory\./);
1151
+ assert.match(additionalContext, /Regression fixture for issue #2273\./);
1152
+ assert.doesNotMatch(additionalContext, /Legacy runtime memory should not win/);
1153
+ assert.match(additionalContext, /legacy pages at \.omx\/wiki\//);
1154
+ assert.match(additionalContext, /Legacy wiki fallback is read-only/);
1155
+ } finally {
1156
+ await rm(cwd, { recursive: true, force: true });
1157
+ }
1158
+ });
1159
+
1096
1160
  it("starts a fresh native session without inheriting stale task-scoped context", async () => {
1097
1161
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-isolation-"));
1098
1162
  try {
@@ -1217,7 +1281,7 @@ describe("codex native hook dispatch", () => {
1217
1281
  assert.equal(result.omxEventName, "keyword-detector");
1218
1282
  assert.equal(result.skillState?.skill, "ralplan");
1219
1283
  assert.ok(result.outputJson, "UserPromptSubmit should emit developer context");
1220
- assert.match(JSON.stringify(result.outputJson), /skill: ralplan activated and initial state initialized at \.omx\/state\/sessions\/sess-1\/ralplan-state\.json; write subsequent updates via omx_state MCP\./);
1284
+ assert.match(JSON.stringify(result.outputJson), /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1221
1285
 
1222
1286
  assert.equal(
1223
1287
  existsSync(join(cwd, ".omx", "state", "skill-active-state.json")),
@@ -1240,6 +1304,142 @@ describe("codex native hook dispatch", () => {
1240
1304
  }
1241
1305
  });
1242
1306
 
1307
+ it("records boxed keyword activation mode detail and skill state under OMX_ROOT", async () => {
1308
+ const root = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-"));
1309
+ const cwd = join(root, "source");
1310
+ const omxRoot = join(root, "box");
1311
+ const sessionId = "sess-boxed-ralplan";
1312
+ const previousOmxRoot = process.env.OMX_ROOT;
1313
+ const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
1314
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
1315
+ const previousOmxSessionId = process.env.OMX_SESSION_ID;
1316
+ try {
1317
+ await mkdir(cwd, { recursive: true });
1318
+ process.env.OMX_ROOT = omxRoot;
1319
+ delete process.env.OMX_STATE_ROOT;
1320
+ delete process.env.OMX_TEAM_STATE_ROOT;
1321
+ process.env.OMX_SESSION_ID = sessionId;
1322
+
1323
+ const result = await dispatchCodexNativeHook(
1324
+ {
1325
+ hook_event_name: "UserPromptSubmit",
1326
+ cwd,
1327
+ session_id: sessionId,
1328
+ thread_id: "thread-boxed",
1329
+ turn_id: "turn-boxed",
1330
+ prompt: "$ralplan implement issue #1307",
1331
+ },
1332
+ { cwd },
1333
+ );
1334
+
1335
+ assert.equal(result.omxEventName, "keyword-detector");
1336
+ assert.equal(result.skillState?.skill, "ralplan");
1337
+
1338
+ const boxedSessionDir = join(omxRoot, ".omx", "state", "sessions", sessionId);
1339
+ assert.equal(existsSync(join(boxedSessionDir, "skill-active-state.json")), true);
1340
+ assert.equal(existsSync(join(boxedSessionDir, "ralplan-state.json")), true);
1341
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json")), false);
1342
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "ralplan-state.json")), false);
1343
+
1344
+ const hudState = await readAllState(cwd);
1345
+ assert.equal(hudState.ralplan?.active, true);
1346
+ assert.equal(hudState.ralplan?.current_phase, "planning");
1347
+ } finally {
1348
+ if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot;
1349
+ else delete process.env.OMX_ROOT;
1350
+ if (typeof previousOmxStateRoot === "string") process.env.OMX_STATE_ROOT = previousOmxStateRoot;
1351
+ else delete process.env.OMX_STATE_ROOT;
1352
+ if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
1353
+ else delete process.env.OMX_TEAM_STATE_ROOT;
1354
+ if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId;
1355
+ else delete process.env.OMX_SESSION_ID;
1356
+ await rm(root, { recursive: true, force: true });
1357
+ }
1358
+ });
1359
+
1360
+ it("records native keyword activation mode detail and skill state under OMX_TEAM_STATE_ROOT", async () => {
1361
+ const root = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-"));
1362
+ const cwd = join(root, "source");
1363
+ const teamStateRoot = join(root, "team-state");
1364
+ const sessionId = "sess-team-root-ralplan";
1365
+ const previousOmxRoot = process.env.OMX_ROOT;
1366
+ const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
1367
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
1368
+ const previousOmxSessionId = process.env.OMX_SESSION_ID;
1369
+ try {
1370
+ await mkdir(cwd, { recursive: true });
1371
+ delete process.env.OMX_ROOT;
1372
+ delete process.env.OMX_STATE_ROOT;
1373
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
1374
+ process.env.OMX_SESSION_ID = sessionId;
1375
+
1376
+ const result = await dispatchCodexNativeHook(
1377
+ {
1378
+ hook_event_name: "UserPromptSubmit",
1379
+ cwd,
1380
+ session_id: sessionId,
1381
+ thread_id: "thread-team-root",
1382
+ turn_id: "turn-team-root",
1383
+ prompt: "$ralplan implement issue #1307",
1384
+ },
1385
+ { cwd },
1386
+ );
1387
+
1388
+ assert.equal(result.omxEventName, "keyword-detector");
1389
+ assert.equal(result.skillState?.skill, "ralplan");
1390
+
1391
+ const teamSessionDir = join(teamStateRoot, "sessions", sessionId);
1392
+ assert.equal(existsSync(join(teamSessionDir, "skill-active-state.json")), true);
1393
+ assert.equal(existsSync(join(teamSessionDir, "ralplan-state.json")), true);
1394
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json")), false);
1395
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "ralplan-state.json")), false);
1396
+
1397
+ const hudState = await readAllState(cwd);
1398
+ assert.equal(hudState.ralplan?.active, true);
1399
+ assert.equal(hudState.ralplan?.current_phase, "planning");
1400
+ } finally {
1401
+ if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot;
1402
+ else delete process.env.OMX_ROOT;
1403
+ if (typeof previousOmxStateRoot === "string") process.env.OMX_STATE_ROOT = previousOmxStateRoot;
1404
+ else delete process.env.OMX_STATE_ROOT;
1405
+ if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
1406
+ else delete process.env.OMX_TEAM_STATE_ROOT;
1407
+ if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId;
1408
+ else delete process.env.OMX_SESSION_ID;
1409
+ await rm(root, { recursive: true, force: true });
1410
+ }
1411
+ });
1412
+
1413
+ it("classifies only actionable goal completion wording", () => {
1414
+ const actionable = [
1415
+ "complete this goal now",
1416
+ "Performance goal complete; next call update_goal({status: \"complete\"}).",
1417
+ "get_goal returned a completed legacy goal, so ultragoal complete failed; marking complete now.",
1418
+ "omx ultragoal checkpoint --goal-id G001-demo --status complete --codex-goal-json goal.json",
1419
+ "Call update_goal({status: \"complete\"}) after verification.",
1420
+ "Goal complete.",
1421
+ "The goal is complete.",
1422
+ "Goal complete: verified with tests.",
1423
+ "Goal complete — verified with tests.",
1424
+ "The goal is complete: verified.",
1425
+ "The goal is complete — verified.",
1426
+ ];
1427
+
1428
+ const ordinary = [
1429
+ "my goal is to complete the migration without regressions",
1430
+ "Our goal is to finish this carefully after tests pass.",
1431
+ "The goal of this patch is to close a review gap.",
1432
+ "A goal can be complete only after a human review.",
1433
+ ];
1434
+
1435
+ for (const text of actionable) {
1436
+ assert.equal(looksLikeGoalCompletionPrompt(text), true, text);
1437
+ }
1438
+ for (const text of ordinary) {
1439
+ assert.equal(looksLikeGoalCompletionPrompt(text), false, text);
1440
+ }
1441
+ });
1442
+
1243
1443
  it("warns completion-like prompts when active goal workflows need Codex snapshot reconciliation", async () => {
1244
1444
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-goal-warning-"));
1245
1445
  try {
@@ -1293,6 +1493,118 @@ describe("codex native hook dispatch", () => {
1293
1493
  }
1294
1494
  });
1295
1495
 
1496
+ it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
1497
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
1498
+ try {
1499
+ await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1500
+ version: 1,
1501
+ activeGoalId: "G001-demo",
1502
+ goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }],
1503
+ });
1504
+
1505
+ const result = await dispatchCodexNativeHook({
1506
+ hook_event_name: "Stop",
1507
+ cwd,
1508
+ session_id: "sess-ultragoal-generic-complete-stop",
1509
+ thread_id: "thread-ultragoal-generic-complete-stop",
1510
+ last_assistant_message: "Goal complete.",
1511
+ }, { cwd });
1512
+
1513
+ assert.equal(result.outputJson?.decision, "block");
1514
+ assert.match(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
1515
+ } finally {
1516
+ await rm(cwd, { recursive: true, force: true });
1517
+ }
1518
+ });
1519
+
1520
+ it("does not block ultragoal Stop for ordinary prose about a goal to complete work", async () => {
1521
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-ordinary-stop-"));
1522
+ try {
1523
+ await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1524
+ version: 1,
1525
+ activeGoalId: "G001-demo",
1526
+ goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }],
1527
+ });
1528
+
1529
+ const result = await dispatchCodexNativeHook({
1530
+ hook_event_name: "Stop",
1531
+ cwd,
1532
+ session_id: "sess-ultragoal-ordinary-stop",
1533
+ thread_id: "thread-ultragoal-ordinary-stop",
1534
+ last_assistant_message: "My goal is to complete the migration without regressions, so I will keep testing.",
1535
+ }, { cwd });
1536
+
1537
+ assert.notEqual(result.outputJson?.stopReason, "ultragoal_codex_goal_snapshot_required");
1538
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
1539
+ } finally {
1540
+ await rm(cwd, { recursive: true, force: true });
1541
+ }
1542
+ });
1543
+
1544
+ it("blocks ultragoal Stop with blocked checkpoint and fresh-thread remediation for completed legacy snapshots", async () => {
1545
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
1546
+ try {
1547
+ await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1548
+ version: 1,
1549
+ activeGoalId: "G001-demo",
1550
+ goals: [{ id: "G001-demo", status: "in_progress", objective: "Demo goal" }],
1551
+ });
1552
+
1553
+ const result = await dispatchCodexNativeHook({
1554
+ hook_event_name: "Stop",
1555
+ cwd,
1556
+ session_id: "sess-ultragoal-legacy-stop",
1557
+ thread_id: "thread-ultragoal-legacy-stop",
1558
+ last_assistant_message: "get_goal returned a completed legacy goal, so ultragoal complete failed; marking complete now.",
1559
+ }, { cwd });
1560
+
1561
+ const output = JSON.stringify(result.outputJson);
1562
+ assert.equal(result.outputJson?.decision, "block");
1563
+ assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
1564
+ assert.match(output, /--status blocked/);
1565
+ assert.match(output, /fresh Codex thread/);
1566
+ assert.match(output, /Hooks must not mutate Codex goal state/);
1567
+ } finally {
1568
+ await rm(cwd, { recursive: true, force: true });
1569
+ }
1570
+ });
1571
+
1572
+
1573
+ it("does not block ultragoal Stop after task-scoped reconciliation finishes exploded bookkeeping", async () => {
1574
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-reconciled-stop-"));
1575
+ try {
1576
+ await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1577
+ version: 1,
1578
+ codexGoalMode: "aggregate",
1579
+ codexObjective: "Complete all ultragoal stories in .omx/ultragoal/goals.json: many micro goals",
1580
+ activeGoalId: "G001-micro",
1581
+ aggregateCompletion: {
1582
+ status: "complete",
1583
+ completedAt: "2026-05-04T10:04:00.000Z",
1584
+ evidence: "planned work done; validation complete; reviews clean",
1585
+ },
1586
+ goals: Array.from({ length: 136 }, (_, index) => ({
1587
+ id: `G${String(index + 1).padStart(3, "0")}-micro`,
1588
+ status: index === 0 ? "in_progress" : "pending",
1589
+ objective: `Synthetic slice ${index + 1}.`,
1590
+ })),
1591
+ });
1592
+
1593
+ const result = await dispatchCodexNativeHook({
1594
+ hook_event_name: "Stop",
1595
+ cwd,
1596
+ session_id: "sess-ultragoal-reconciled-stop",
1597
+ thread_id: "thread-ultragoal-reconciled-stop",
1598
+ last_assistant_message: "Yes — planned implementation work is done; ultragoal bookkeeping reconciled complete.",
1599
+ }, { cwd });
1600
+
1601
+ assert.notEqual(result.outputJson?.stopReason, "ultragoal_codex_goal_snapshot_required");
1602
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx ultragoal checkpoint --goal-id/);
1603
+ } finally {
1604
+ await rm(cwd, { recursive: true, force: true });
1605
+ }
1606
+ });
1607
+
1296
1608
  it("does not block Stop for non-passing autoresearch-goal professor-critic verdicts", async () => {
1297
1609
  for (const verdict of ["blocked", "fail", "failed"]) {
1298
1610
  const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-autoresearch-${verdict}-stop-`));
@@ -1474,7 +1786,7 @@ describe("codex native hook dispatch", () => {
1474
1786
  (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
1475
1787
  );
1476
1788
  assert.match(message, /\$oh-my-codex:ralplan" -> ralplan/);
1477
- assert.match(message, /skill: ralplan activated and initial state initialized at \.omx\/state\/sessions\/sess-plugin-1\/ralplan-state\.json; write subsequent updates via omx_state MCP\./);
1789
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1478
1790
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-plugin-1", "ralplan-state.json")), true);
1479
1791
  } finally {
1480
1792
  await rm(cwd, { recursive: true, force: true });
@@ -1691,7 +2003,7 @@ describe("codex native hook dispatch", () => {
1691
2003
  (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
1692
2004
  );
1693
2005
  assert.match(message, /\$ralph" -> ralph/);
1694
- assert.match(message, /skill: ralph activated and initial state initialized at \.omx\/state\/sessions\/sess-ralph-msg\/ralph-state\.json; write subsequent updates via omx_state MCP\./);
2006
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1695
2007
  assert.match(message, /Prompt-side `\$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`\./);
1696
2008
  assert.match(message, /Use `omx ralph --prd \.\.\.` only when you explicitly want the PRD-gated CLI startup path\./);
1697
2009
  } finally {
@@ -1721,7 +2033,7 @@ describe("codex native hook dispatch", () => {
1721
2033
  (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
1722
2034
  );
1723
2035
  assert.match(message, /\$oh-my-codex:ralph" -> ralph/);
1724
- assert.match(message, /skill: ralph activated and initial state initialized at \.omx\/state\/sessions\/sess-plugin-ralph-msg\/ralph-state\.json; write subsequent updates via omx_state MCP\./);
2036
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1725
2037
  assert.match(message, /Prompt-side `\$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`\./);
1726
2038
  } finally {
1727
2039
  await rm(cwd, { recursive: true, force: true });
@@ -1803,7 +2115,7 @@ describe("codex native hook dispatch", () => {
1803
2115
  (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
1804
2116
  );
1805
2117
  assert.match(message, /\$deep-interview" -> deep-interview/);
1806
- assert.match(message, /skill: deep-interview activated and initial state initialized at \.omx\/state\/sessions\/sess-deep-interview-msg\/deep-interview-state\.json; write subsequent updates via omx_state MCP\./);
2118
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1807
2119
  assert.match(message, /Deep-interview is active, but this session is not attached to tmux/);
1808
2120
  assert.match(message, /Do not invoke `omx question`, `omx hud`, or `omx team`/);
1809
2121
  assert.match(message, /native structured question tool when available/);
@@ -2279,8 +2591,11 @@ export async function onHookEvent(event) {
2279
2591
 
2280
2592
  assert.match(JSON.stringify(denied.outputJson), /denied workflow keyword/i);
2281
2593
  assert.match(JSON.stringify(denied.outputJson), /Unsupported workflow overlap: team \+ autopilot\./);
2282
- assert.match(JSON.stringify(denied.outputJson), /`omx state clear --mode <mode>`/);
2283
- assert.match(JSON.stringify(denied.outputJson), /`omx_state\.\*` MCP tools/);
2594
+ assert.match(JSON.stringify(denied.outputJson), /omx state clear --input/);
2595
+ assert.match(JSON.stringify(denied.outputJson), /mode\\":\\"<mode>/);
2596
+ assert.match(JSON.stringify(denied.outputJson), /--json/);
2597
+ assert.match(JSON.stringify(denied.outputJson), /explicit MCP compatibility is enabled/);
2598
+ assert.match(JSON.stringify(denied.outputJson), /`omx_state\.\*` tools/);
2284
2599
  assert.equal(
2285
2600
  existsSync(join(cwd, ".omx", "state", "sessions", "sess-deny-1", "autopilot-state.json")),
2286
2601
  false,
@@ -2357,7 +2672,7 @@ export async function onHookEvent(event) {
2357
2672
  assert.match(message, /\$ralph" -> ralph/);
2358
2673
  assert.doesNotMatch(message, /mode transiting:/);
2359
2674
  assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./);
2360
- assert.match(message, /skill: ralplan activated and initial state initialized at \.omx\/state\/sessions\/sess-multi-1\/ralplan-state\.json; write subsequent updates via omx_state MCP\./);
2675
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
2361
2676
  assert.doesNotMatch(message, /Use the durable OMX team runtime via `omx team \.\.\.`/);
2362
2677
  } finally {
2363
2678
  await rm(cwd, { recursive: true, force: true });
@@ -2389,7 +2704,7 @@ export async function onHookEvent(event) {
2389
2704
  assert.match(message, /\$oh-my-codex:ralph" -> ralph/);
2390
2705
  assert.doesNotMatch(message, /mode transiting:/);
2391
2706
  assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./);
2392
- assert.match(message, /skill: ralplan activated and initial state initialized at \.omx\/state\/sessions\/sess-plugin-multi-1\/ralplan-state\.json; write subsequent updates via omx_state MCP\./);
2707
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
2393
2708
  } finally {
2394
2709
  await rm(cwd, { recursive: true, force: true });
2395
2710
  }
@@ -4499,7 +4814,7 @@ exit 0
4499
4814
  );
4500
4815
  assert.match(
4501
4816
  additionalContext,
4502
- /omx state state_write --input/,
4817
+ /omx state write --input/,
4503
4818
  );
4504
4819
  assert.match(
4505
4820
  additionalContext,
@@ -4747,7 +5062,7 @@ exit 0
4747
5062
  assert.equal(hookSpecificOutput?.hookEventName, "PostToolUse");
4748
5063
  assert.match(
4749
5064
  String(hookSpecificOutput?.additionalContext || ""),
4750
- /Retry via CLI parity with `omx state state_write --input '\{\}' --json`\./,
5065
+ /Retry via CLI parity with `omx state write --input '\{\}' --json`\./,
4751
5066
  );
4752
5067
  assert.match(
4753
5068
  String(hookSpecificOutput?.additionalContext || ""),
@@ -4833,7 +5148,7 @@ exit 0
4833
5148
  hookSpecificOutput: {
4834
5149
  hookEventName: "PostToolUse",
4835
5150
  additionalContext:
4836
- "Clear MCP transport-death signal detected. Preserve current team/runtime state. Retry via CLI parity with `omx state state_write --input '{\"mode\":\"team\",\"active\":true}' --json`. OMX MCP servers are plain Node stdio processes, so they still shut down when stdin/transport closes. If this happened during team runtime, inspect first with `omx team status <team>` or `omx team api read-stall-state --input '{\"team_name\":\"<team>\"}' --json`, and only force cleanup after capturing needed state. For root-cause debugging, rerun with `OMX_MCP_TRANSPORT_DEBUG=1` to log why the stdio transport closed.",
5151
+ "Clear MCP transport-death signal detected. Preserve current team/runtime state. Retry via CLI parity with `omx state write --input '{\"mode\":\"team\",\"active\":true}' --json`. OMX MCP servers are plain Node stdio processes, so they still shut down when stdin/transport closes. If this happened during team runtime, inspect first with `omx team status <team>` or `omx team api read-stall-state --input '{\"team_name\":\"<team>\"}' --json`, and only force cleanup after capturing needed state. For root-cause debugging, rerun with `OMX_MCP_TRANSPORT_DEBUG=1` to log why the stdio transport closed.",
4837
5152
  },
4838
5153
  });
4839
5154
 
@@ -6253,6 +6568,41 @@ exit 0
6253
6568
  }
6254
6569
  });
6255
6570
 
6571
+ it("ignores stale source-root team Stop fallback when OMX_TEAM_STATE_ROOT is authoritative", async () => {
6572
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-stale-source-root-"));
6573
+ const teamStateRoot = join(cwd, "shared-team-state");
6574
+ const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
6575
+ try {
6576
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
6577
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
6578
+ await mkdir(join(teamStateRoot, "team", "stale-source-team"), { recursive: true });
6579
+ await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
6580
+ active: true,
6581
+ team_name: "stale-source-team",
6582
+ current_phase: "team-exec",
6583
+ });
6584
+ await writeJson(join(teamStateRoot, "team", "stale-source-team", "phase.json"), {
6585
+ current_phase: "team-exec",
6586
+ });
6587
+
6588
+ const result = await dispatchCodexNativeHook(
6589
+ {
6590
+ hook_event_name: "Stop",
6591
+ cwd,
6592
+ session_id: "sess-stale-source-team",
6593
+ },
6594
+ { cwd },
6595
+ );
6596
+
6597
+ assert.equal(result.omxEventName, "stop");
6598
+ assert.equal(result.outputJson, null);
6599
+ } finally {
6600
+ if (typeof priorTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = priorTeamStateRoot;
6601
+ else delete process.env.OMX_TEAM_STATE_ROOT;
6602
+ await rm(cwd, { recursive: true, force: true });
6603
+ }
6604
+ });
6605
+
6256
6606
  it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
6257
6607
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
6258
6608
  const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
@@ -7288,6 +7638,79 @@ exit 0
7288
7638
  }
7289
7639
  });
7290
7640
 
7641
+ it("blocks Codex App Stop when Ralph is marked complete without completion-audit evidence", async () => {
7642
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-complete-audit-missing-"));
7643
+ try {
7644
+ const sessionId = "sess-ralph-complete-missing";
7645
+ const statePath = join(cwd, ".omx", "state", "sessions", sessionId, "ralph-state.json");
7646
+ await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd });
7647
+ await writeJson(statePath, {
7648
+ active: false,
7649
+ mode: "ralph",
7650
+ current_phase: "complete",
7651
+ session_id: sessionId,
7652
+ completed_at: "2026-05-10T12:00:00.000Z",
7653
+ });
7654
+
7655
+ const result = await dispatchCodexNativeHook(
7656
+ {
7657
+ hook_event_name: "Stop",
7658
+ cwd,
7659
+ session_id: sessionId,
7660
+ last_assistant_message: "Done. Ralph complete.",
7661
+ },
7662
+ { cwd },
7663
+ );
7664
+
7665
+ assert.equal(result.omxEventName, "stop");
7666
+ assert.match(String(result.outputJson?.reason), /Ralph completion audit is missing required evidence/);
7667
+ assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
7668
+ const reopened = JSON.parse(await readFile(statePath, "utf-8")) as Record<string, unknown>;
7669
+ assert.equal(reopened.active, true);
7670
+ assert.equal(reopened.current_phase, "verifying");
7671
+ assert.equal(reopened.completion_audit_gate, "blocked");
7672
+ assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
7673
+ assert.equal(typeof reopened.completed_at, "undefined");
7674
+ } finally {
7675
+ await rm(cwd, { recursive: true, force: true });
7676
+ }
7677
+ });
7678
+
7679
+ it("allows Codex App Stop when complete Ralph state carries checklist and verification evidence", async () => {
7680
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-complete-audit-present-"));
7681
+ try {
7682
+ const sessionId = "sess-ralph-complete-present";
7683
+ await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd });
7684
+ await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "ralph-state.json"), {
7685
+ active: false,
7686
+ mode: "ralph",
7687
+ current_phase: "complete",
7688
+ session_id: sessionId,
7689
+ completed_at: "2026-05-10T12:00:00.000Z",
7690
+ completion_audit: {
7691
+ passed: true,
7692
+ prompt_to_artifact_checklist: ["issue #2260 fixed", "tests added"],
7693
+ verification_evidence: ["node --test dist/scripts/__tests__/codex-native-hook.test.js"],
7694
+ },
7695
+ });
7696
+
7697
+ const result = await dispatchCodexNativeHook(
7698
+ {
7699
+ hook_event_name: "Stop",
7700
+ cwd,
7701
+ session_id: sessionId,
7702
+ last_assistant_message: "Done with completion audit evidence recorded.",
7703
+ },
7704
+ { cwd },
7705
+ );
7706
+
7707
+ assert.equal(result.omxEventName, "stop");
7708
+ assert.equal(result.outputJson, null);
7709
+ } finally {
7710
+ await rm(cwd, { recursive: true, force: true });
7711
+ }
7712
+ });
7713
+
7291
7714
  it("returns Stop continuation output while Ralph is active without an explicit session pin", async () => {
7292
7715
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-"));
7293
7716
  try {