oh-my-codex 0.18.0 → 0.18.1

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 (293) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +43 -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/cli/__tests__/codex-plugin-layout.test.js +15 -7
  9. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  10. package/dist/cli/__tests__/doctor-warning-copy.test.js +76 -3
  11. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  12. package/dist/cli/__tests__/index.test.js +49 -1
  13. package/dist/cli/__tests__/index.test.js.map +1 -1
  14. package/dist/cli/__tests__/install-docs-contract.test.d.ts +2 -0
  15. package/dist/cli/__tests__/install-docs-contract.test.d.ts.map +1 -0
  16. package/dist/cli/__tests__/install-docs-contract.test.js +55 -0
  17. package/dist/cli/__tests__/install-docs-contract.test.js.map +1 -0
  18. package/dist/cli/__tests__/launch-fallback.test.js +115 -0
  19. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  20. package/dist/cli/__tests__/question.test.js +27 -41
  21. package/dist/cli/__tests__/question.test.js.map +1 -1
  22. package/dist/cli/__tests__/setup-install-mode.test.js +94 -35
  23. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  24. package/dist/cli/__tests__/sparkshell-cli.test.js +20 -1
  25. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  26. package/dist/cli/__tests__/sparkshell-packaging.test.js +1 -0
  27. package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
  28. package/dist/cli/__tests__/ultragoal.test.js +227 -4
  29. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  30. package/dist/cli/__tests__/update.test.js +72 -1
  31. package/dist/cli/__tests__/update.test.js.map +1 -1
  32. package/dist/cli/codex-feature-probe.d.ts +5 -0
  33. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  34. package/dist/cli/codex-feature-probe.js +13 -7
  35. package/dist/cli/codex-feature-probe.js.map +1 -1
  36. package/dist/cli/doctor.d.ts +7 -0
  37. package/dist/cli/doctor.d.ts.map +1 -1
  38. package/dist/cli/doctor.js +119 -10
  39. package/dist/cli/doctor.js.map +1 -1
  40. package/dist/cli/index.d.ts +3 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +345 -90
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/cli/plugin-marketplace.d.ts +2 -0
  45. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  46. package/dist/cli/plugin-marketplace.js +15 -1
  47. package/dist/cli/plugin-marketplace.js.map +1 -1
  48. package/dist/cli/setup.d.ts.map +1 -1
  49. package/dist/cli/setup.js +71 -11
  50. package/dist/cli/setup.js.map +1 -1
  51. package/dist/cli/sparkshell.d.ts +7 -1
  52. package/dist/cli/sparkshell.d.ts.map +1 -1
  53. package/dist/cli/sparkshell.js +13 -3
  54. package/dist/cli/sparkshell.js.map +1 -1
  55. package/dist/cli/ultragoal.d.ts +1 -1
  56. package/dist/cli/ultragoal.d.ts.map +1 -1
  57. package/dist/cli/ultragoal.js +184 -10
  58. package/dist/cli/ultragoal.js.map +1 -1
  59. package/dist/cli/update.d.ts +2 -0
  60. package/dist/cli/update.d.ts.map +1 -1
  61. package/dist/cli/update.js +14 -3
  62. package/dist/cli/update.js.map +1 -1
  63. package/dist/compat/__tests__/doctor-contract.test.js +3 -0
  64. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  65. package/dist/config/__tests__/codex-feature-flags.test.js +11 -1
  66. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
  67. package/dist/config/__tests__/codex-hooks.test.js +19 -8
  68. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  69. package/dist/config/__tests__/commit-lore-guard.test.d.ts +2 -0
  70. package/dist/config/__tests__/commit-lore-guard.test.d.ts.map +1 -0
  71. package/dist/config/__tests__/commit-lore-guard.test.js +20 -0
  72. package/dist/config/__tests__/commit-lore-guard.test.js.map +1 -0
  73. package/dist/config/codex-feature-flags.d.ts +4 -0
  74. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  75. package/dist/config/codex-feature-flags.js +4 -0
  76. package/dist/config/codex-feature-flags.js.map +1 -1
  77. package/dist/config/codex-hooks.js +6 -6
  78. package/dist/config/codex-hooks.js.map +1 -1
  79. package/dist/config/commit-lore-guard.d.ts +1 -0
  80. package/dist/config/commit-lore-guard.d.ts.map +1 -1
  81. package/dist/config/commit-lore-guard.js +29 -3
  82. package/dist/config/commit-lore-guard.js.map +1 -1
  83. package/dist/config/generator.d.ts +3 -1
  84. package/dist/config/generator.d.ts.map +1 -1
  85. package/dist/config/generator.js +24 -10
  86. package/dist/config/generator.js.map +1 -1
  87. package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -0
  88. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  89. package/dist/goal-workflows/codex-goal-snapshot.js +5 -1
  90. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  91. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +10 -6
  92. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  93. package/dist/hooks/__tests__/consensus-execution-handoff.test.d.ts +1 -1
  94. package/dist/hooks/__tests__/consensus-execution-handoff.test.js +13 -11
  95. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  96. package/dist/hooks/__tests__/deep-interview-contract.test.js +4 -3
  97. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  98. package/dist/hooks/__tests__/keyword-detector.test.js +4 -3
  99. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  100. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +33 -0
  101. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  102. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +26 -3
  103. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  104. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  105. package/dist/hooks/extensibility/dispatcher.js +29 -14
  106. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  107. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  108. package/dist/hooks/keyword-detector.js +8 -3
  109. package/dist/hooks/keyword-detector.js.map +1 -1
  110. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  111. package/dist/hooks/prompt-guidance-contract.js +3 -2
  112. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  113. package/dist/hud/__tests__/hud-tmux-injection.test.js +14 -8
  114. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  115. package/dist/hud/__tests__/reconcile.test.js +2 -2
  116. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  117. package/dist/hud/__tests__/resource-leak-watch.test.d.ts +2 -0
  118. package/dist/hud/__tests__/resource-leak-watch.test.d.ts.map +1 -0
  119. package/dist/hud/__tests__/resource-leak-watch.test.js +28 -0
  120. package/dist/hud/__tests__/resource-leak-watch.test.js.map +1 -0
  121. package/dist/hud/index.d.ts +1 -1
  122. package/dist/hud/index.d.ts.map +1 -1
  123. package/dist/hud/index.js +10 -4
  124. package/dist/hud/index.js.map +1 -1
  125. package/dist/hud/tmux.js +2 -2
  126. package/dist/hud/tmux.js.map +1 -1
  127. package/dist/notifications/__tests__/http-client-resource.test.d.ts +2 -0
  128. package/dist/notifications/__tests__/http-client-resource.test.d.ts.map +1 -0
  129. package/dist/notifications/__tests__/http-client-resource.test.js +41 -0
  130. package/dist/notifications/__tests__/http-client-resource.test.js.map +1 -0
  131. package/dist/notifications/__tests__/verbosity.test.js +20 -0
  132. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  133. package/dist/notifications/config.d.ts.map +1 -1
  134. package/dist/notifications/config.js +6 -3
  135. package/dist/notifications/config.js.map +1 -1
  136. package/dist/notifications/http-client.d.ts.map +1 -1
  137. package/dist/notifications/http-client.js +78 -27
  138. package/dist/notifications/http-client.js.map +1 -1
  139. package/dist/notifications/types.d.ts +2 -0
  140. package/dist/notifications/types.d.ts.map +1 -1
  141. package/dist/openclaw/__tests__/dispatcher.test.js +49 -1
  142. package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
  143. package/dist/openclaw/dispatcher.d.ts +7 -4
  144. package/dist/openclaw/dispatcher.d.ts.map +1 -1
  145. package/dist/openclaw/dispatcher.js +32 -69
  146. package/dist/openclaw/dispatcher.js.map +1 -1
  147. package/dist/pipeline/__tests__/orchestrator.test.js +65 -3
  148. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  149. package/dist/pipeline/__tests__/stages.test.js +50 -5
  150. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  151. package/dist/pipeline/index.d.ts +8 -2
  152. package/dist/pipeline/index.d.ts.map +1 -1
  153. package/dist/pipeline/index.js +5 -2
  154. package/dist/pipeline/index.js.map +1 -1
  155. package/dist/pipeline/orchestrator.d.ts +5 -4
  156. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  157. package/dist/pipeline/orchestrator.js +56 -15
  158. package/dist/pipeline/orchestrator.js.map +1 -1
  159. package/dist/pipeline/stages/code-review.d.ts +2 -2
  160. package/dist/pipeline/stages/code-review.d.ts.map +1 -1
  161. package/dist/pipeline/stages/code-review.js +5 -3
  162. package/dist/pipeline/stages/code-review.js.map +1 -1
  163. package/dist/pipeline/stages/deep-interview.d.ts +15 -0
  164. package/dist/pipeline/stages/deep-interview.d.ts.map +1 -0
  165. package/dist/pipeline/stages/deep-interview.js +32 -0
  166. package/dist/pipeline/stages/deep-interview.js.map +1 -0
  167. package/dist/pipeline/stages/ralph-verify.d.ts +5 -5
  168. package/dist/pipeline/stages/ralph-verify.d.ts.map +1 -1
  169. package/dist/pipeline/stages/ralph-verify.js +2 -2
  170. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  171. package/dist/pipeline/stages/ultragoal.d.ts +19 -0
  172. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -0
  173. package/dist/pipeline/stages/ultragoal.js +38 -0
  174. package/dist/pipeline/stages/ultragoal.js.map +1 -0
  175. package/dist/pipeline/stages/ultraqa.d.ts +30 -0
  176. package/dist/pipeline/stages/ultraqa.d.ts.map +1 -0
  177. package/dist/pipeline/stages/ultraqa.js +46 -0
  178. package/dist/pipeline/stages/ultraqa.js.map +1 -0
  179. package/dist/pipeline/types.d.ts +8 -6
  180. package/dist/pipeline/types.d.ts.map +1 -1
  181. package/dist/pipeline/types.js +2 -2
  182. package/dist/scripts/__tests__/codex-native-hook.test.js +705 -45
  183. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  184. package/dist/scripts/__tests__/smoke-packed-install.test.js +23 -1
  185. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  186. package/dist/scripts/__tests__/verify-native-agents.test.js +16 -1
  187. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  188. package/dist/scripts/cleanup-explore-harness.js +1 -0
  189. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  190. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  191. package/dist/scripts/codex-native-hook.js +158 -10
  192. package/dist/scripts/codex-native-hook.js.map +1 -1
  193. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  194. package/dist/scripts/codex-native-pre-post.js +9 -1
  195. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  196. package/dist/scripts/notify-hook/process-runner.d.ts.map +1 -1
  197. package/dist/scripts/notify-hook/process-runner.js +39 -17
  198. package/dist/scripts/notify-hook/process-runner.js.map +1 -1
  199. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  200. package/dist/scripts/notify-hook/team-dispatch.js +9 -5
  201. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  202. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -1
  203. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  204. package/dist/scripts/notify-hook/team-tmux-guard.js +7 -1
  205. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  206. package/dist/scripts/smoke-packed-install.d.ts +3 -0
  207. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  208. package/dist/scripts/smoke-packed-install.js +99 -1
  209. package/dist/scripts/smoke-packed-install.js.map +1 -1
  210. package/dist/scripts/sync-plugin-mirror.js +2 -2
  211. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  212. package/dist/scripts/verify-native-agents.js +2 -2
  213. package/dist/scripts/verify-native-agents.js.map +1 -1
  214. package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts +2 -0
  215. package/dist/sidecar/__tests__/resource-leak-watch.test.d.ts.map +1 -0
  216. package/dist/sidecar/__tests__/resource-leak-watch.test.js +38 -0
  217. package/dist/sidecar/__tests__/resource-leak-watch.test.js.map +1 -0
  218. package/dist/sidecar/index.d.ts +1 -1
  219. package/dist/sidecar/index.d.ts.map +1 -1
  220. package/dist/sidecar/index.js +29 -12
  221. package/dist/sidecar/index.js.map +1 -1
  222. package/dist/state/__tests__/operations-ralph-phase.test.js +88 -1
  223. package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
  224. package/dist/state/operations.d.ts.map +1 -1
  225. package/dist/state/operations.js +11 -0
  226. package/dist/state/operations.js.map +1 -1
  227. package/dist/team/__tests__/tmux-session.test.js +111 -3
  228. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  229. package/dist/team/tmux-session.d.ts.map +1 -1
  230. package/dist/team/tmux-session.js +39 -18
  231. package/dist/team/tmux-session.js.map +1 -1
  232. package/dist/ultragoal/__tests__/artifacts.test.js +714 -10
  233. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  234. package/dist/ultragoal/__tests__/docs-contract.test.js +57 -1
  235. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  236. package/dist/ultragoal/__tests__/steering-fixtures.d.ts +68 -0
  237. package/dist/ultragoal/__tests__/steering-fixtures.d.ts.map +1 -0
  238. package/dist/ultragoal/__tests__/steering-fixtures.js +259 -0
  239. package/dist/ultragoal/__tests__/steering-fixtures.js.map +1 -0
  240. package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts +2 -0
  241. package/dist/ultragoal/__tests__/steering-fixtures.test.d.ts.map +1 -0
  242. package/dist/ultragoal/__tests__/steering-fixtures.test.js +65 -0
  243. package/dist/ultragoal/__tests__/steering-fixtures.test.js.map +1 -0
  244. package/dist/ultragoal/artifacts.d.ts +97 -2
  245. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  246. package/dist/ultragoal/artifacts.js +811 -256
  247. package/dist/ultragoal/artifacts.js.map +1 -1
  248. package/dist/utils/__tests__/sleep-resource.test.d.ts +2 -0
  249. package/dist/utils/__tests__/sleep-resource.test.d.ts.map +1 -0
  250. package/dist/utils/__tests__/sleep-resource.test.js +39 -0
  251. package/dist/utils/__tests__/sleep-resource.test.js.map +1 -0
  252. package/dist/utils/sleep.d.ts.map +1 -1
  253. package/dist/utils/sleep.js +17 -6
  254. package/dist/utils/sleep.js.map +1 -1
  255. package/package.json +2 -1
  256. package/plugins/oh-my-codex/.codex-plugin/plugin.json +4 -3
  257. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +56 -0
  258. package/plugins/oh-my-codex/hooks/hooks.json +77 -0
  259. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +77 -47
  260. package/plugins/oh-my-codex/skills/cancel/SKILL.md +2 -2
  261. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +8 -8
  262. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
  263. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +22 -11
  264. package/plugins/oh-my-codex/skills/plan/SKILL.md +8 -8
  265. package/plugins/oh-my-codex/skills/ralph/SKILL.md +7 -0
  266. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +4 -4
  267. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  268. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +38 -4
  269. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +1 -1
  270. package/prompts/planner.md +1 -1
  271. package/skills/autopilot/SKILL.md +77 -47
  272. package/skills/cancel/SKILL.md +2 -2
  273. package/skills/deep-interview/SKILL.md +8 -8
  274. package/skills/omx-setup/SKILL.md +1 -1
  275. package/skills/pipeline/SKILL.md +22 -11
  276. package/skills/plan/SKILL.md +8 -8
  277. package/skills/ralph/SKILL.md +7 -0
  278. package/skills/ralplan/SKILL.md +4 -4
  279. package/skills/team/SKILL.md +1 -1
  280. package/skills/ultragoal/SKILL.md +38 -4
  281. package/skills/ultrawork/SKILL.md +1 -1
  282. package/src/scripts/__tests__/codex-native-hook.test.ts +867 -81
  283. package/src/scripts/__tests__/smoke-packed-install.test.ts +31 -0
  284. package/src/scripts/__tests__/verify-native-agents.test.ts +21 -1
  285. package/src/scripts/cleanup-explore-harness.ts +1 -0
  286. package/src/scripts/codex-native-hook.ts +156 -10
  287. package/src/scripts/codex-native-pre-post.ts +16 -1
  288. package/src/scripts/notify-hook/process-runner.ts +40 -16
  289. package/src/scripts/notify-hook/team-dispatch.ts +9 -5
  290. package/src/scripts/notify-hook/team-tmux-guard.ts +7 -0
  291. package/src/scripts/smoke-packed-install.ts +105 -0
  292. package/src/scripts/sync-plugin-mirror.ts +3 -3
  293. package/src/scripts/verify-native-agents.ts +2 -2
@@ -28,6 +28,7 @@ import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
28
28
  import { readAllState } from "../../hud/state.js";
29
29
  import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
30
30
  import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
31
+ import { createUltragoalPlan, readUltragoalPlan } from "../../ultragoal/artifacts.js";
31
32
 
32
33
  function nativeHookScriptPath(): string {
33
34
  return join(process.cwd(), "dist", "scripts", "codex-native-hook.js");
@@ -62,6 +63,40 @@ async function writeJson(path: string, value: unknown): Promise<void> {
62
63
  await writeFile(path, JSON.stringify(value, null, 2));
63
64
  }
64
65
 
66
+ async function withLoreGuardConfig<T>(
67
+ value: string,
68
+ prefix: string,
69
+ run: (cwd: string) => Promise<T>,
70
+ ): Promise<T> {
71
+ const cwd = await mkdtemp(join(tmpdir(), `omx-native-hook-pretool-git-commit-lore-${prefix}-`));
72
+ const codexHome = await mkdtemp(join(tmpdir(), `omx-native-hook-codex-home-lore-${prefix}-`));
73
+ const defaultHome = await mkdtemp(join(tmpdir(), `omx-native-hook-home-lore-${prefix}-`));
74
+ const originalGuard = process.env.OMX_LORE_COMMIT_GUARD;
75
+ const originalCodexHome = process.env.CODEX_HOME;
76
+ const originalHome = process.env.HOME;
77
+ try {
78
+ delete process.env.OMX_LORE_COMMIT_GUARD;
79
+ process.env.CODEX_HOME = codexHome;
80
+ process.env.HOME = defaultHome;
81
+ await writeFile(
82
+ join(codexHome, "config.toml"),
83
+ `[shell_environment_policy.set]\nOMX_LORE_COMMIT_GUARD = "${value}"\n`,
84
+ "utf-8",
85
+ );
86
+ return await run(cwd);
87
+ } finally {
88
+ if (originalGuard === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
89
+ else process.env.OMX_LORE_COMMIT_GUARD = originalGuard;
90
+ if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
91
+ else process.env.CODEX_HOME = originalCodexHome;
92
+ if (originalHome === undefined) delete process.env.HOME;
93
+ else process.env.HOME = originalHome;
94
+ await rm(cwd, { recursive: true, force: true });
95
+ await rm(codexHome, { recursive: true, force: true });
96
+ await rm(defaultHome, { recursive: true, force: true });
97
+ }
98
+ }
99
+
65
100
  function buildWorkerStopFakeTmux(
66
101
  tmuxLogPath: string,
67
102
  options: { failSend?: boolean; busyLeader?: boolean } = {},
@@ -729,7 +764,16 @@ describe("codex native hook dispatch", () => {
729
764
 
730
765
  it("keeps subagent SessionStart from replacing the canonical leader session", async () => {
731
766
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-start-"));
767
+ const originalCodexHome = process.env.CODEX_HOME;
732
768
  try {
769
+ process.env.CODEX_HOME = join(cwd, "codex-home");
770
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
771
+ notifications: {
772
+ enabled: true,
773
+ verbosity: "session",
774
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
775
+ },
776
+ });
733
777
  const stateDir = join(cwd, ".omx", "state");
734
778
  const canonicalSessionId = "omx-leader-session";
735
779
  const leaderNativeSessionId = "codex-leader-thread";
@@ -745,6 +789,16 @@ describe("codex native hook dispatch", () => {
745
789
  iteration: 1,
746
790
  max_iterations: 5,
747
791
  });
792
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
793
+ await writeFile(
794
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
795
+ [
796
+ "import { appendFileSync } from 'node:fs';",
797
+ "export async function onHookEvent(event) {",
798
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event, context: event.context })}\\n`);",
799
+ "}",
800
+ ].join("\n"),
801
+ );
748
802
  const transcriptPath = join(cwd, "subagent-rollout.jsonl");
749
803
  await writeFile(
750
804
  transcriptPath,
@@ -794,6 +848,11 @@ describe("codex native hook dispatch", () => {
794
848
  ) as { active?: boolean; current_phase?: string };
795
849
  assert.equal(leaderRalph.active, true);
796
850
  assert.equal(leaderRalph.current_phase, "executing");
851
+ assert.equal(
852
+ existsSync(join(cwd, "hook-events.jsonl")),
853
+ false,
854
+ "subagent SessionStart must not independently dispatch session-start hook notifications",
855
+ );
797
856
 
798
857
  const tracking = JSON.parse(
799
858
  await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"),
@@ -809,7 +868,257 @@ describe("codex native hook dispatch", () => {
809
868
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.leader_thread_id, leaderNativeSessionId);
810
869
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.kind, "subagent");
811
870
  assert.equal(tracking.sessions?.[leaderNativeSessionId]?.threads?.[childNativeSessionId]?.mode, "critic");
871
+
872
+ await dispatchCodexNativeHook(
873
+ {
874
+ hook_event_name: "Stop",
875
+ cwd,
876
+ session_id: childNativeSessionId,
877
+ thread_id: childNativeSessionId,
878
+ turn_id: "child-stop-turn",
879
+ },
880
+ { cwd },
881
+ );
882
+ assert.equal(
883
+ existsSync(join(cwd, "hook-events.jsonl")),
884
+ false,
885
+ "subagent Stop must not independently dispatch stop hook notifications",
886
+ );
887
+ } finally {
888
+ if (originalCodexHome === undefined) {
889
+ delete process.env.CODEX_HOME;
890
+ } else {
891
+ process.env.CODEX_HOME = originalCodexHome;
892
+ }
893
+ await rm(cwd, { recursive: true, force: true });
894
+ }
895
+ });
896
+
897
+ it("suppresses child-agent SessionStart hook dispatch at minimal verbosity", async () => {
898
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-minimal-"));
899
+ const originalCodexHome = process.env.CODEX_HOME;
900
+ try {
901
+ process.env.CODEX_HOME = join(cwd, "codex-home");
902
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
903
+ notifications: {
904
+ enabled: true,
905
+ verbosity: "minimal",
906
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
907
+ },
908
+ });
909
+ const stateDir = join(cwd, ".omx", "state");
910
+ const canonicalSessionId = "omx-leader-session-minimal";
911
+ const leaderNativeSessionId = "codex-leader-thread-minimal";
912
+ const childNativeSessionId = "codex-child-thread-minimal";
913
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
914
+ await writeSessionStart(cwd, canonicalSessionId, {
915
+ nativeSessionId: leaderNativeSessionId,
916
+ });
917
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
918
+ await writeFile(
919
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
920
+ [
921
+ "import { appendFileSync } from 'node:fs';",
922
+ "export async function onHookEvent(event) {",
923
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
924
+ "}",
925
+ ].join("\n"),
926
+ );
927
+ const transcriptPath = join(cwd, "minimal-subagent-rollout.jsonl");
928
+ await writeFile(
929
+ transcriptPath,
930
+ `${JSON.stringify({
931
+ type: "session_meta",
932
+ payload: {
933
+ id: childNativeSessionId,
934
+ source: {
935
+ subagent: {
936
+ thread_spawn: {
937
+ parent_thread_id: leaderNativeSessionId,
938
+ agent_role: "verifier",
939
+ },
940
+ },
941
+ },
942
+ },
943
+ })}\n`,
944
+ );
945
+
946
+ await dispatchCodexNativeHook(
947
+ {
948
+ hook_event_name: "SessionStart",
949
+ cwd,
950
+ session_id: childNativeSessionId,
951
+ transcript_path: transcriptPath,
952
+ },
953
+ { cwd, sessionOwnerPid: process.pid },
954
+ );
955
+
956
+ assert.equal(
957
+ existsSync(join(cwd, "hook-events.jsonl")),
958
+ false,
959
+ "subagent SessionStart must be suppressed at minimal verbosity",
960
+ );
961
+ } finally {
962
+ if (originalCodexHome === undefined) {
963
+ delete process.env.CODEX_HOME;
964
+ } else {
965
+ process.env.CODEX_HOME = originalCodexHome;
966
+ }
967
+ await rm(cwd, { recursive: true, force: true });
968
+ }
969
+ });
970
+
971
+ it("allows explicit child-agent lifecycle hook dispatch when includeChildAgents is enabled", async () => {
972
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-include-"));
973
+ const originalCodexHome = process.env.CODEX_HOME;
974
+ try {
975
+ process.env.CODEX_HOME = join(cwd, "codex-home");
976
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
977
+ notifications: {
978
+ enabled: true,
979
+ verbosity: "session",
980
+ includeChildAgents: true,
981
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
982
+ },
983
+ });
984
+ const stateDir = join(cwd, ".omx", "state");
985
+ const canonicalSessionId = "omx-leader-session-include";
986
+ const leaderNativeSessionId = "codex-leader-thread-include";
987
+ const childNativeSessionId = "codex-child-thread-include";
988
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
989
+ await writeSessionStart(cwd, canonicalSessionId, {
990
+ nativeSessionId: leaderNativeSessionId,
991
+ });
992
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
993
+ await writeFile(
994
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
995
+ [
996
+ "import { appendFileSync } from 'node:fs';",
997
+ "export async function onHookEvent(event) {",
998
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
999
+ "}",
1000
+ ].join("\n"),
1001
+ );
1002
+ const transcriptPath = join(cwd, "included-subagent-rollout.jsonl");
1003
+ await writeFile(
1004
+ transcriptPath,
1005
+ `${JSON.stringify({
1006
+ type: "session_meta",
1007
+ payload: {
1008
+ id: childNativeSessionId,
1009
+ source: {
1010
+ subagent: {
1011
+ thread_spawn: {
1012
+ parent_thread_id: leaderNativeSessionId,
1013
+ agent_role: "verifier",
1014
+ },
1015
+ },
1016
+ },
1017
+ },
1018
+ })}\n`,
1019
+ );
1020
+
1021
+ await dispatchCodexNativeHook(
1022
+ {
1023
+ hook_event_name: "SessionStart",
1024
+ cwd,
1025
+ session_id: childNativeSessionId,
1026
+ transcript_path: transcriptPath,
1027
+ },
1028
+ { cwd, sessionOwnerPid: process.pid },
1029
+ );
1030
+
1031
+ await dispatchCodexNativeHook(
1032
+ {
1033
+ hook_event_name: "Stop",
1034
+ cwd,
1035
+ session_id: childNativeSessionId,
1036
+ thread_id: childNativeSessionId,
1037
+ turn_id: "included-child-stop-turn",
1038
+ },
1039
+ { cwd },
1040
+ );
1041
+
1042
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
1043
+ assert.match(hookEvents, /"event":"session-start"/);
1044
+ assert.match(hookEvents, /"event":"stop"/);
1045
+ } finally {
1046
+ if (originalCodexHome === undefined) {
1047
+ delete process.env.CODEX_HOME;
1048
+ } else {
1049
+ process.env.CODEX_HOME = originalCodexHome;
1050
+ }
1051
+ await rm(cwd, { recursive: true, force: true });
1052
+ }
1053
+ });
1054
+
1055
+ it("allows child-agent lifecycle hook dispatch at agent verbosity", async () => {
1056
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-session-agent-"));
1057
+ const originalCodexHome = process.env.CODEX_HOME;
1058
+ try {
1059
+ process.env.CODEX_HOME = join(cwd, "codex-home");
1060
+ await writeJson(join(process.env.CODEX_HOME, ".omx-config.json"), {
1061
+ notifications: {
1062
+ enabled: true,
1063
+ verbosity: "agent",
1064
+ telegram: { enabled: true, botToken: "123:abc", chatId: "456" },
1065
+ },
1066
+ });
1067
+ const stateDir = join(cwd, ".omx", "state");
1068
+ const canonicalSessionId = "omx-leader-session-agent";
1069
+ const leaderNativeSessionId = "codex-leader-thread-agent";
1070
+ const childNativeSessionId = "codex-child-thread-agent";
1071
+ await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
1072
+ await writeSessionStart(cwd, canonicalSessionId, {
1073
+ nativeSessionId: leaderNativeSessionId,
1074
+ });
1075
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
1076
+ await writeFile(
1077
+ join(cwd, ".omx", "hooks", "record-lifecycle.mjs"),
1078
+ [
1079
+ "import { appendFileSync } from 'node:fs';",
1080
+ "export async function onHookEvent(event) {",
1081
+ " appendFileSync('hook-events.jsonl', `${JSON.stringify({ event: event.event })}\\n`);",
1082
+ "}",
1083
+ ].join("\n"),
1084
+ );
1085
+ const transcriptPath = join(cwd, "agent-verbosity-subagent-rollout.jsonl");
1086
+ await writeFile(
1087
+ transcriptPath,
1088
+ `${JSON.stringify({
1089
+ type: "session_meta",
1090
+ payload: {
1091
+ id: childNativeSessionId,
1092
+ source: {
1093
+ subagent: {
1094
+ thread_spawn: {
1095
+ parent_thread_id: leaderNativeSessionId,
1096
+ agent_role: "verifier",
1097
+ },
1098
+ },
1099
+ },
1100
+ },
1101
+ })}\n`,
1102
+ );
1103
+
1104
+ await dispatchCodexNativeHook(
1105
+ {
1106
+ hook_event_name: "SessionStart",
1107
+ cwd,
1108
+ session_id: childNativeSessionId,
1109
+ transcript_path: transcriptPath,
1110
+ },
1111
+ { cwd, sessionOwnerPid: process.pid },
1112
+ );
1113
+
1114
+ const hookEvents = await readFile(join(cwd, "hook-events.jsonl"), "utf-8");
1115
+ assert.match(hookEvents, /"event":"session-start"/);
812
1116
  } finally {
1117
+ if (originalCodexHome === undefined) {
1118
+ delete process.env.CODEX_HOME;
1119
+ } else {
1120
+ process.env.CODEX_HOME = originalCodexHome;
1121
+ }
813
1122
  await rm(cwd, { recursive: true, force: true });
814
1123
  }
815
1124
  });
@@ -1498,6 +1807,66 @@ describe("codex native hook dispatch", () => {
1498
1807
  }
1499
1808
  });
1500
1809
 
1810
+ it("does not repeat performance-goal reconciliation after a recorded objective mismatch blocker", async () => {
1811
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-mismatch-blocked-stop-"));
1812
+ try {
1813
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1814
+ version: 1,
1815
+ workflow: "performance-goal",
1816
+ slug: "latency",
1817
+ objective: "Reduce latency",
1818
+ status: "blocked",
1819
+ lastValidation: {
1820
+ status: "blocked",
1821
+ evidence: "omx performance-goal complete rejected the fresh get_goal snapshot: Codex goal objective mismatch: expected \"reduce latency\", got \"legacy objective\".",
1822
+ recordedAt: "2026-05-20T00:00:00.000Z",
1823
+ },
1824
+ });
1825
+
1826
+ const result = await dispatchCodexNativeHook({
1827
+ hook_event_name: "Stop",
1828
+ cwd,
1829
+ session_id: "sess-performance-mismatch-blocked-stop",
1830
+ thread_id: "thread-performance-mismatch-blocked-stop",
1831
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1832
+ }, { cwd });
1833
+
1834
+ assert.notEqual(result.outputJson?.decision, "block");
1835
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1836
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1837
+ } finally {
1838
+ await rm(cwd, { recursive: true, force: true });
1839
+ }
1840
+ });
1841
+
1842
+ it("does not block Stop for an already complete performance-goal state", async () => {
1843
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-performance-complete-stop-"));
1844
+ try {
1845
+ await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
1846
+ version: 1,
1847
+ workflow: "performance-goal",
1848
+ slug: "latency",
1849
+ objective: "Reduce latency",
1850
+ status: "complete",
1851
+ completedAt: "2026-05-20T00:00:00.000Z",
1852
+ });
1853
+
1854
+ const result = await dispatchCodexNativeHook({
1855
+ hook_event_name: "Stop",
1856
+ cwd,
1857
+ session_id: "sess-performance-complete-stop",
1858
+ thread_id: "thread-performance-complete-stop",
1859
+ last_assistant_message: "Performance goal complete; next call update_goal({status: \"complete\"}).",
1860
+ }, { cwd });
1861
+
1862
+ assert.notEqual(result.outputJson?.decision, "block");
1863
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /omx performance-goal complete --slug latency/);
1864
+ assert.doesNotMatch(JSON.stringify(result.outputJson), /get_goal snapshot reconciliation/);
1865
+ } finally {
1866
+ await rm(cwd, { recursive: true, force: true });
1867
+ }
1868
+ });
1869
+
1501
1870
  it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
1502
1871
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
1503
1872
  try {
@@ -1546,7 +1915,7 @@ describe("codex native hook dispatch", () => {
1546
1915
  }
1547
1916
  });
1548
1917
 
1549
- it("blocks ultragoal Stop with blocked checkpoint and fresh-thread remediation for completed legacy snapshots", async () => {
1918
+ it("blocks ultragoal Stop with blocked checkpoint and available-goal-context remediation for completed legacy snapshots", async () => {
1550
1919
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-legacy-stop-"));
1551
1920
  try {
1552
1921
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
@@ -1567,7 +1936,8 @@ describe("codex native hook dispatch", () => {
1567
1936
  assert.equal(result.outputJson?.decision, "block");
1568
1937
  assert.match(output, /omx ultragoal checkpoint --goal-id G001-demo --status complete/);
1569
1938
  assert.match(output, /--status blocked/);
1570
- assert.match(output, /fresh Codex thread/);
1939
+ assert.match(output, /Codex goal context/);
1940
+ assert.doesNotMatch(output, /fresh (?:Codex )?(?:thread|session)s?/i);
1571
1941
  assert.match(output, /Hooks must not mutate Codex goal state/);
1572
1942
  } finally {
1573
1943
  await rm(cwd, { recursive: true, force: true });
@@ -1581,7 +1951,7 @@ describe("codex native hook dispatch", () => {
1581
1951
  await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
1582
1952
  version: 1,
1583
1953
  codexGoalMode: "aggregate",
1584
- codexObjective: "Complete all ultragoal stories in .omx/ultragoal/goals.json: many micro goals",
1954
+ 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
1955
  activeGoalId: "G001-micro",
1586
1956
  aggregateCompletion: {
1587
1957
  status: "complete",
@@ -1842,55 +2212,233 @@ describe("codex native hook dispatch", () => {
1842
2212
  {
1843
2213
  hook_event_name: "UserPromptSubmit",
1844
2214
  cwd,
1845
- session_id: "sess-plugin-1",
1846
- thread_id: "thread-plugin-1",
1847
- turn_id: "turn-plugin-1",
1848
- prompt: "$oh-my-codex:ralplan implement issue #1307",
2215
+ session_id: "sess-plugin-1",
2216
+ thread_id: "thread-plugin-1",
2217
+ turn_id: "turn-plugin-1",
2218
+ prompt: "$oh-my-codex:ralplan implement issue #1307",
2219
+ },
2220
+ { cwd },
2221
+ );
2222
+
2223
+ assert.equal(result.omxEventName, "keyword-detector");
2224
+ assert.equal(result.skillState?.skill, "ralplan");
2225
+ const message = String(
2226
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2227
+ );
2228
+ assert.match(message, /\$oh-my-codex:ralplan" -> ralplan/);
2229
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
2230
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-plugin-1", "ralplan-state.json")), true);
2231
+ } finally {
2232
+ await rm(cwd, { recursive: true, force: true });
2233
+ }
2234
+ });
2235
+
2236
+ it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
2237
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
2238
+ try {
2239
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
2240
+ const result = await dispatchCodexNativeHook(
2241
+ {
2242
+ hook_event_name: "UserPromptSubmit",
2243
+ cwd,
2244
+ session_id: "sess-ultragoal-1",
2245
+ thread_id: "thread-ultragoal-1",
2246
+ turn_id: "turn-ultragoal-1",
2247
+ prompt: "$ultragoal split this launch into durable goals",
2248
+ },
2249
+ { cwd },
2250
+ );
2251
+
2252
+ assert.equal(result.omxEventName, "keyword-detector");
2253
+ assert.equal(result.skillState?.skill, "ultragoal");
2254
+ assert.equal(result.skillState?.initialized_mode, undefined);
2255
+ const message = String(
2256
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2257
+ );
2258
+ assert.match(message, /"\$ultragoal" -> ultragoal/);
2259
+ assert.match(message, /Ultragoal protocol:/);
2260
+ assert.match(message, /get_goal/);
2261
+ assert.match(message, /create_goal/);
2262
+ assert.match(message, /update_goal/);
2263
+ assert.match(message, /does not call `\/goal clear`/);
2264
+ assert.match(message, /multiple sequential ultragoal runs/);
2265
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ultragoal-1", "ultragoal-state.json")), false);
2266
+ } finally {
2267
+ await rm(cwd, { recursive: true, force: true });
2268
+ }
2269
+ });
2270
+
2271
+ it("applies only explicit structured UserPromptSubmit ultragoal steering directives", async () => {
2272
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-"));
2273
+ try {
2274
+ await createUltragoalPlan(cwd, {
2275
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal hook steering fixture",
2276
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2277
+ });
2278
+
2279
+ const prose = await dispatchCodexNativeHook(
2280
+ {
2281
+ hook_event_name: "UserPromptSubmit",
2282
+ cwd,
2283
+ session_id: "sess-ultragoal-steer-1",
2284
+ prompt: "Please add a subgoal for docs later; this is normal prose, not a directive.",
2285
+ },
2286
+ { cwd },
2287
+ );
2288
+ assert.equal(prose.outputJson, null);
2289
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
2290
+
2291
+ const jsonExample = await dispatchCodexNativeHook(
2292
+ {
2293
+ hook_event_name: "UserPromptSubmit",
2294
+ cwd,
2295
+ session_id: "sess-ultragoal-steer-1",
2296
+ prompt: `Here is an inert example:\n\`\`\`json\n${JSON.stringify({
2297
+ kind: "add_subgoal",
2298
+ source: "user_prompt_submit",
2299
+ evidence: "Example JSON should not mutate .omx/ultragoal.",
2300
+ rationale: "Only explicit steering fences or labels are executable.",
2301
+ title: "Inert JSON example",
2302
+ objective: "This example must not be added.",
2303
+ })}\n\`\`\``,
2304
+ },
2305
+ { cwd },
2306
+ );
2307
+ assert.equal(jsonExample.outputJson, null);
2308
+ assert.equal((await readUltragoalPlan(cwd)).goals.length, 1);
2309
+
2310
+ const result = await dispatchCodexNativeHook(
2311
+ {
2312
+ hook_event_name: "UserPromptSubmit",
2313
+ cwd,
2314
+ session_id: "sess-ultragoal-steer-1",
2315
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2316
+ kind: "add_subgoal",
2317
+ source: "user_prompt_submit",
2318
+ evidence: "Prompt-submit supplied a structured .omx/ultragoal directive for G002-cli-and-prompt-submit-bridge.",
2319
+ rationale: "Add bounded hook regression work while preserving all completion gates.",
2320
+ title: "Prompt bridge regression",
2321
+ objective: "Verify UserPromptSubmit bounded steering bridge with tests.",
2322
+ })}`,
2323
+ },
2324
+ { cwd },
2325
+ );
2326
+
2327
+ const message = String(
2328
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2329
+ );
2330
+ assert.match(message, /bounded \.omx\/ultragoal steering/);
2331
+ assert.match(message, /G002-cli-and-prompt-submit-bridge/);
2332
+ assert.match(message, /accepted/);
2333
+ const plan = await readUltragoalPlan(cwd);
2334
+ assert.equal(plan.goals.length, 2);
2335
+ assert.equal(plan.goals[1]?.title, "Prompt bridge regression");
2336
+ } finally {
2337
+ await rm(cwd, { recursive: true, force: true });
2338
+ }
2339
+ });
2340
+
2341
+ it("does not apply UserPromptSubmit ultragoal steering from native subagent prompts", async () => {
2342
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-subagent-"));
2343
+ try {
2344
+ await createUltragoalPlan(cwd, {
2345
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal subagent steering fixture",
2346
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2347
+ });
2348
+ const stateDir = join(cwd, ".omx", "state");
2349
+ const canonicalSessionId = "sess-ultragoal-parent";
2350
+ const leaderNativeSessionId = "native-ultragoal-parent";
2351
+ const childNativeSessionId = "native-ultragoal-child";
2352
+ const nowIso = new Date().toISOString();
2353
+ await writeJson(join(stateDir, "session.json"), {
2354
+ session_id: canonicalSessionId,
2355
+ native_session_id: leaderNativeSessionId,
2356
+ });
2357
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
2358
+ schemaVersion: 1,
2359
+ sessions: {
2360
+ [canonicalSessionId]: {
2361
+ session_id: canonicalSessionId,
2362
+ leader_thread_id: leaderNativeSessionId,
2363
+ updated_at: nowIso,
2364
+ threads: {
2365
+ [leaderNativeSessionId]: {
2366
+ thread_id: leaderNativeSessionId,
2367
+ kind: "leader",
2368
+ first_seen_at: nowIso,
2369
+ last_seen_at: nowIso,
2370
+ turn_count: 1,
2371
+ },
2372
+ [childNativeSessionId]: {
2373
+ thread_id: childNativeSessionId,
2374
+ kind: "subagent",
2375
+ first_seen_at: nowIso,
2376
+ last_seen_at: nowIso,
2377
+ turn_count: 1,
2378
+ mode: "architect",
2379
+ },
2380
+ },
2381
+ },
2382
+ },
2383
+ });
2384
+
2385
+ const result = await dispatchCodexNativeHook(
2386
+ {
2387
+ hook_event_name: "UserPromptSubmit",
2388
+ cwd,
2389
+ session_id: childNativeSessionId,
2390
+ thread_id: childNativeSessionId,
2391
+ turn_id: "turn-ultragoal-child-1",
2392
+ prompt: `OMX_ULTRAGOAL_STEER: ${JSON.stringify({
2393
+ kind: "add_subgoal",
2394
+ source: "user_prompt_submit",
2395
+ evidence: "Subagent prompt text must be literal delegated context.",
2396
+ rationale: "Subagent prompts should not mutate the parent .omx/ultragoal ledger.",
2397
+ title: "Subagent should not add this",
2398
+ objective: "This must remain literal prompt text.",
2399
+ })}`,
1849
2400
  },
1850
2401
  { cwd },
1851
2402
  );
1852
2403
 
1853
- assert.equal(result.omxEventName, "keyword-detector");
1854
- assert.equal(result.skillState?.skill, "ralplan");
1855
- const message = String(
1856
- (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
1857
- );
1858
- assert.match(message, /\$oh-my-codex:ralplan" -> ralplan/);
1859
- assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1860
- assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-plugin-1", "ralplan-state.json")), true);
2404
+ assert.equal(result.outputJson, null);
2405
+ const plan = await readUltragoalPlan(cwd);
2406
+ assert.equal(plan.goals.length, 1);
2407
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2408
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 0);
2409
+ assert.equal((ledger.match(/"event":"steering_rejected"/g) ?? []).length, 0);
1861
2410
  } finally {
1862
2411
  await rm(cwd, { recursive: true, force: true });
1863
2412
  }
1864
2413
  });
1865
2414
 
1866
- it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
1867
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
2415
+ it("dedupes repeated UserPromptSubmit ultragoal steering directives by prompt signature", async () => {
2416
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-dedupe-"));
1868
2417
  try {
1869
- await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1870
- const result = await dispatchCodexNativeHook(
1871
- {
1872
- hook_event_name: "UserPromptSubmit",
1873
- 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",
1878
- },
1879
- { cwd },
1880
- );
1881
-
1882
- assert.equal(result.omxEventName, "keyword-detector");
1883
- assert.equal(result.skillState?.skill, "ultragoal");
1884
- assert.equal(result.skillState?.initialized_mode, undefined);
2418
+ await createUltragoalPlan(cwd, {
2419
+ brief: "G002-cli-and-prompt-submit-bridge .omx/ultragoal dedupe fixture",
2420
+ goals: [{ title: "First", objective: "Complete first milestone with tests." }],
2421
+ });
2422
+ const prompt = `\`\`\`omx-ultragoal-steer
2423
+ ${JSON.stringify({
2424
+ kind: "add_subgoal",
2425
+ source: "user_prompt_submit",
2426
+ evidence: "Structured prompt-submit directive adds exactly one deduped goal.",
2427
+ rationale: "Use idempotent bridge semantics for repeated hook delivery.",
2428
+ title: "Deduped bridge regression",
2429
+ objective: "Verify repeated UserPromptSubmit steering does not duplicate goals.",
2430
+ })}
2431
+ \`\`\``;
2432
+ await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
2433
+ const second = await dispatchCodexNativeHook({ hook_event_name: "UserPromptSubmit", cwd, session_id: "sess-dedupe", prompt }, { cwd });
1885
2434
  const message = String(
1886
- (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
2435
+ (second.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
1887
2436
  );
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);
2437
+ assert.match(message, /deduped/);
2438
+ const plan = await readUltragoalPlan(cwd);
2439
+ assert.equal(plan.goals.filter((goal) => goal.title === "Deduped bridge regression").length, 1);
2440
+ const ledger = await readFile(join(cwd, ".omx/ultragoal/ledger.jsonl"), "utf-8");
2441
+ assert.equal((ledger.match(/"event":"steering_accepted"/g) ?? []).length, 1);
1894
2442
  } finally {
1895
2443
  await rm(cwd, { recursive: true, force: true });
1896
2444
  }
@@ -3768,7 +4316,7 @@ exit 0
3768
4316
  cwd,
3769
4317
  tool_name: "Bash",
3770
4318
  tool_use_id: "tool-slop-git-priority",
3771
- tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
4319
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "quick hack fallback if it fails"' },
3772
4320
  },
3773
4321
  { cwd },
3774
4322
  );
@@ -3791,7 +4339,7 @@ exit 0
3791
4339
  cwd,
3792
4340
  tool_name: "Bash",
3793
4341
  tool_use_id: "tool-git-commit-invalid",
3794
- tool_input: { command: 'git commit -m "fix tests"' },
4342
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
3795
4343
  },
3796
4344
  { cwd },
3797
4345
  );
@@ -3820,11 +4368,38 @@ exit 0
3820
4368
  }
3821
4369
  });
3822
4370
 
3823
- it("allows non-Lore git commit messages when the Lore commit guard is explicitly disabled", async () => {
4371
+
4372
+ it("blocks PreToolUse git commit when process env explicitly enables the Lore commit guard", async () => {
4373
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-enabled-"));
4374
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4375
+ try {
4376
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
4377
+ const result = await dispatchCodexNativeHook(
4378
+ {
4379
+ hook_event_name: "PreToolUse",
4380
+ cwd,
4381
+ tool_name: "Bash",
4382
+ tool_use_id: "tool-git-commit-lore-env-enabled",
4383
+ tool_input: { command: 'git commit -m "fix tests"' },
4384
+ },
4385
+ { cwd },
4386
+ );
4387
+
4388
+ assert.equal(result.omxEventName, "pre-tool-use");
4389
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4390
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4391
+ } finally {
4392
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4393
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4394
+ await rm(cwd, { recursive: true, force: true });
4395
+ }
4396
+ });
4397
+
4398
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled by default", async () => {
3824
4399
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-disabled-"));
3825
4400
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3826
4401
  try {
3827
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4402
+ delete process.env.OMX_LORE_COMMIT_GUARD;
3828
4403
  const result = await dispatchCodexNativeHook(
3829
4404
  {
3830
4405
  hook_event_name: "PreToolUse",
@@ -3845,6 +4420,80 @@ exit 0
3845
4420
  }
3846
4421
  });
3847
4422
 
4423
+ it("blocks non-Lore git commit messages when the Lore commit guard is enabled in CODEX_HOME config.toml", async () => {
4424
+ await withLoreGuardConfig("1", "config-enabled", async (cwd) => {
4425
+ const result = await dispatchCodexNativeHook(
4426
+ {
4427
+ hook_event_name: "PreToolUse",
4428
+ cwd,
4429
+ tool_name: "Bash",
4430
+ tool_use_id: "tool-git-commit-lore-config-enabled",
4431
+ tool_input: { command: 'git commit -m "fix: conventional"' },
4432
+ },
4433
+ { cwd },
4434
+ );
4435
+
4436
+ assert.equal(result.omxEventName, "pre-tool-use");
4437
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4438
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4439
+ });
4440
+ });
4441
+
4442
+ it("allows non-Lore git commit messages when the Lore commit guard is disabled in CODEX_HOME config.toml", async () => {
4443
+ await withLoreGuardConfig("0", "config-disabled", async (cwd) => {
4444
+ const result = await dispatchCodexNativeHook(
4445
+ {
4446
+ hook_event_name: "PreToolUse",
4447
+ cwd,
4448
+ tool_name: "Bash",
4449
+ tool_use_id: "tool-git-commit-lore-config-disabled",
4450
+ tool_input: { command: 'git commit -m "fix: use conventional commit"' },
4451
+ },
4452
+ { cwd },
4453
+ );
4454
+
4455
+ assert.equal(result.omxEventName, "pre-tool-use");
4456
+ assert.equal(result.outputJson, null);
4457
+ });
4458
+ });
4459
+
4460
+ it("lets inline Lore commit guard values override a disabled CODEX_HOME config.toml", async () => {
4461
+ await withLoreGuardConfig("0", "config-inline-enabled", async (cwd) => {
4462
+ const result = await dispatchCodexNativeHook(
4463
+ {
4464
+ hook_event_name: "PreToolUse",
4465
+ cwd,
4466
+ tool_name: "Bash",
4467
+ tool_use_id: "tool-git-commit-lore-config-inline-enabled",
4468
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix: conventional"' },
4469
+ },
4470
+ { cwd },
4471
+ );
4472
+
4473
+ assert.equal(result.omxEventName, "pre-tool-use");
4474
+ assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
4475
+ assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4476
+ });
4477
+ });
4478
+
4479
+ it("restores default-off Lore guard when env -u removes a disabled CODEX_HOME config source", async () => {
4480
+ await withLoreGuardConfig("0", "config-codex-home-unset", async (cwd) => {
4481
+ const result = await dispatchCodexNativeHook(
4482
+ {
4483
+ hook_event_name: "PreToolUse",
4484
+ cwd,
4485
+ tool_name: "Bash",
4486
+ tool_use_id: "tool-git-commit-lore-config-codex-home-unset",
4487
+ tool_input: { command: 'env -u CODEX_HOME git commit -m "fix: conventional"' },
4488
+ },
4489
+ { cwd },
4490
+ );
4491
+
4492
+ assert.equal(result.omxEventName, "pre-tool-use");
4493
+ assert.equal(result.outputJson, null);
4494
+ });
4495
+ });
4496
+
3848
4497
  it("allows non-Lore git commit messages when the Lore commit guard is disabled inline", async () => {
3849
4498
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-disabled-"));
3850
4499
  try {
@@ -3866,7 +4515,33 @@ exit 0
3866
4515
  }
3867
4516
  });
3868
4517
 
3869
- it("does not treat newline-separated Lore guard assignment as inline git commit env", async () => {
4518
+
4519
+ it("allows inline disabled guard to override an enabled process env", async () => {
4520
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-override-disabled-"));
4521
+ const original = process.env.OMX_LORE_COMMIT_GUARD;
4522
+ try {
4523
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
4524
+ const result = await dispatchCodexNativeHook(
4525
+ {
4526
+ hook_event_name: "PreToolUse",
4527
+ cwd,
4528
+ tool_name: "Bash",
4529
+ tool_use_id: "tool-git-commit-lore-inline-override-disabled",
4530
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0 git commit -m "fix: conventional"' },
4531
+ },
4532
+ { cwd },
4533
+ );
4534
+
4535
+ assert.equal(result.omxEventName, "pre-tool-use");
4536
+ assert.equal(result.outputJson, null);
4537
+ } finally {
4538
+ if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4539
+ else process.env.OMX_LORE_COMMIT_GUARD = original;
4540
+ await rm(cwd, { recursive: true, force: true });
4541
+ }
4542
+ });
4543
+
4544
+ it("does not treat newline-separated Lore guard assignment as inline git commit opt-in", async () => {
3870
4545
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-newline-assignment-"));
3871
4546
  try {
3872
4547
  const result = await dispatchCodexNativeHook(
@@ -3875,24 +4550,41 @@ exit 0
3875
4550
  cwd,
3876
4551
  tool_name: "Bash",
3877
4552
  tool_use_id: "tool-git-commit-lore-newline-assignment",
3878
- tool_input: { command: 'OMX_LORE_COMMIT_GUARD=0\ngit commit -m "fix: conventional"' },
4553
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1\ngit commit -m "fix: conventional"' },
3879
4554
  },
3880
4555
  { cwd },
3881
4556
  );
3882
4557
 
3883
4558
  assert.equal(result.omxEventName, "pre-tool-use");
3884
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3885
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4559
+ assert.equal(result.outputJson, null);
3886
4560
  } finally {
3887
4561
  await rm(cwd, { recursive: true, force: true });
3888
4562
  }
3889
4563
  });
3890
4564
 
3891
- it("restores default-on Lore guard when env -u unsets a disabled process env", async () => {
4565
+ it("restores default-off Lore guard when env -u unsets a config.toml fallback", async () => {
4566
+ await withLoreGuardConfig("1", "config-env-unset", async (cwd) => {
4567
+ const result = await dispatchCodexNativeHook(
4568
+ {
4569
+ hook_event_name: "PreToolUse",
4570
+ cwd,
4571
+ tool_name: "Bash",
4572
+ tool_use_id: "tool-git-commit-lore-config-env-unset",
4573
+ tool_input: { command: 'env -u OMX_LORE_COMMIT_GUARD git commit -m "fix: conventional"' },
4574
+ },
4575
+ { cwd },
4576
+ );
4577
+
4578
+ assert.equal(result.omxEventName, "pre-tool-use");
4579
+ assert.equal(result.outputJson, null);
4580
+ });
4581
+ });
4582
+
4583
+ it("restores default-off Lore guard when env -u unsets an enabled process env", async () => {
3892
4584
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-unset-"));
3893
4585
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3894
4586
  try {
3895
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4587
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3896
4588
  const result = await dispatchCodexNativeHook(
3897
4589
  {
3898
4590
  hook_event_name: "PreToolUse",
@@ -3905,8 +4597,7 @@ exit 0
3905
4597
  );
3906
4598
 
3907
4599
  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/);
4600
+ assert.equal(result.outputJson, null);
3910
4601
  } finally {
3911
4602
  if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3912
4603
  else process.env.OMX_LORE_COMMIT_GUARD = original;
@@ -3914,11 +4605,11 @@ exit 0
3914
4605
  }
3915
4606
  });
3916
4607
 
3917
- it("restores default-on Lore guard when env -i clears a disabled process env", async () => {
4608
+ it("restores default-off Lore guard when env -i clears an enabled process env", async () => {
3918
4609
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-env-ignore-"));
3919
4610
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3920
4611
  try {
3921
- process.env.OMX_LORE_COMMIT_GUARD = "0";
4612
+ process.env.OMX_LORE_COMMIT_GUARD = "1";
3922
4613
  const result = await dispatchCodexNativeHook(
3923
4614
  {
3924
4615
  hook_event_name: "PreToolUse",
@@ -3931,8 +4622,7 @@ exit 0
3931
4622
  );
3932
4623
 
3933
4624
  assert.equal(result.omxEventName, "pre-tool-use");
3934
- assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
3935
- assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
4625
+ assert.equal(result.outputJson, null);
3936
4626
  } finally {
3937
4627
  if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
3938
4628
  else process.env.OMX_LORE_COMMIT_GUARD = original;
@@ -3940,7 +4630,7 @@ exit 0
3940
4630
  }
3941
4631
  });
3942
4632
 
3943
- it("keeps Lore commit enforcement enabled for unknown inline guard values", async () => {
4633
+ it("keeps Lore commit enforcement disabled for unknown inline guard values", async () => {
3944
4634
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-inline-unknown-"));
3945
4635
  try {
3946
4636
  const result = await dispatchCodexNativeHook(
@@ -3955,8 +4645,7 @@ exit 0
3955
4645
  );
3956
4646
 
3957
4647
  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/);
4648
+ assert.equal(result.outputJson, null);
3960
4649
  } finally {
3961
4650
  await rm(cwd, { recursive: true, force: true });
3962
4651
  }
@@ -3987,7 +4676,7 @@ exit 0
3987
4676
  }
3988
4677
  });
3989
4678
 
3990
- it("keeps Lore commit enforcement enabled for unknown guard values", async () => {
4679
+ it("keeps Lore commit enforcement disabled for unknown guard values", async () => {
3991
4680
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-lore-unknown-"));
3992
4681
  const original = process.env.OMX_LORE_COMMIT_GUARD;
3993
4682
  try {
@@ -4004,8 +4693,7 @@ exit 0
4004
4693
  );
4005
4694
 
4006
4695
  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/);
4696
+ assert.equal(result.outputJson, null);
4009
4697
  } finally {
4010
4698
  if (original === undefined) delete process.env.OMX_LORE_COMMIT_GUARD;
4011
4699
  else process.env.OMX_LORE_COMMIT_GUARD = original;
@@ -4111,7 +4799,7 @@ exit 0
4111
4799
  cwd,
4112
4800
  tool_name: "Bash",
4113
4801
  tool_use_id: "tool-git-commit-env-invalid",
4114
- tool_input: { command: 'HUSKY=0 git commit -m "fix tests"' },
4802
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 HUSKY=0 git commit -m "fix tests"' },
4115
4803
  },
4116
4804
  { cwd },
4117
4805
  );
@@ -4146,7 +4834,7 @@ exit 0
4146
4834
  cwd,
4147
4835
  tool_name: "Bash",
4148
4836
  tool_use_id: "tool-git-commit-option-invalid",
4149
- tool_input: { command: 'git -c core.editor=true commit -m "fix tests"' },
4837
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git -c core.editor=true commit -m "fix tests"' },
4150
4838
  },
4151
4839
  { cwd },
4152
4840
  );
@@ -4181,7 +4869,7 @@ exit 0
4181
4869
  cwd,
4182
4870
  tool_name: "Bash",
4183
4871
  tool_use_id: "tool-git-exe-commit-env-wrapper-invalid",
4184
- tool_input: { command: 'env git.exe commit -m "fix tests"' },
4872
+ tool_input: { command: 'env OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4185
4873
  },
4186
4874
  { cwd },
4187
4875
  );
@@ -4216,7 +4904,7 @@ exit 0
4216
4904
  cwd,
4217
4905
  tool_name: "Bash",
4218
4906
  tool_use_id: "tool-git-exe-commit-invalid",
4219
- tool_input: { command: 'git.exe commit -m "fix tests"' },
4907
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4220
4908
  },
4221
4909
  { cwd },
4222
4910
  );
@@ -4251,7 +4939,7 @@ exit 0
4251
4939
  cwd,
4252
4940
  tool_name: "Bash",
4253
4941
  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"' },
4942
+ tool_input: { command: 'env -i PATH=/usr/bin OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4255
4943
  },
4256
4944
  { cwd },
4257
4945
  );
@@ -4286,7 +4974,7 @@ exit 0
4286
4974
  cwd,
4287
4975
  tool_name: "Bash",
4288
4976
  tool_use_id: "tool-git-exe-commit-env-value-wrapper-invalid",
4289
- tool_input: { command: 'env -u FOO git.exe commit -m "fix tests"' },
4977
+ tool_input: { command: 'env -u FOO OMX_LORE_COMMIT_GUARD=1 git.exe commit -m "fix tests"' },
4290
4978
  },
4291
4979
  { cwd },
4292
4980
  );
@@ -4321,7 +5009,7 @@ exit 0
4321
5009
  cwd,
4322
5010
  tool_name: "Bash",
4323
5011
  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"' },
5012
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:/Program Files/Git/cmd/git.exe" commit -m "fix tests"' },
4325
5013
  },
4326
5014
  { cwd },
4327
5015
  );
@@ -4356,7 +5044,7 @@ exit 0
4356
5044
  cwd,
4357
5045
  tool_name: "Bash",
4358
5046
  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"' },
5047
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 "C:\\Program Files\\Git\\cmd\\git.exe" commit -m "fix tests"' },
4360
5048
  },
4361
5049
  { cwd },
4362
5050
  );
@@ -4391,7 +5079,7 @@ exit 0
4391
5079
  cwd,
4392
5080
  tool_name: "Bash",
4393
5081
  tool_use_id: "tool-git-commit-path-invalid",
4394
- tool_input: { command: '/usr/bin/git commit -m "fix tests"' },
5082
+ tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 /usr/bin/git commit -m "fix tests"' },
4395
5083
  },
4396
5084
  { cwd },
4397
5085
  );
@@ -4426,7 +5114,7 @@ exit 0
4426
5114
  cwd,
4427
5115
  tool_name: "Bash",
4428
5116
  tool_use_id: "tool-git-commit-file",
4429
- tool_input: { command: "git commit -F .git/COMMIT_EDITMSG" },
5117
+ tool_input: { command: "OMX_LORE_COMMIT_GUARD=1 git commit -F .git/COMMIT_EDITMSG" },
4430
5118
  },
4431
5119
  { cwd },
4432
5120
  );
@@ -4460,7 +5148,7 @@ exit 0
4460
5148
  tool_use_id: "tool-git-commit-missing-omx-coauthor",
4461
5149
  tool_input: {
4462
5150
  command: [
4463
- 'git commit',
5151
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4464
5152
  '-m "Prevent invalid history from bypassing Lore enforcement"',
4465
5153
  '-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
5154
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -4500,7 +5188,7 @@ exit 0
4500
5188
  tool_use_id: "tool-git-commit-valid",
4501
5189
  tool_input: {
4502
5190
  command: [
4503
- 'git commit',
5191
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4504
5192
  '-m "Prevent invalid history from bypassing Lore enforcement"',
4505
5193
  '-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
5194
  '-m "Constraint: Native PreToolUse can only inspect the Bash command text"',
@@ -4530,7 +5218,7 @@ exit 0
4530
5218
  tool_use_id: "tool-git-commit-compact-coauthor",
4531
5219
  tool_input: {
4532
5220
  command: [
4533
- 'git commit',
5221
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4534
5222
  '-m "Launch lvisai.xyz intro site"',
4535
5223
  '-m "Co-authored-by: OmX <omx@oh-my-codex.dev>"',
4536
5224
  ].join(" "),
@@ -4557,7 +5245,7 @@ exit 0
4557
5245
  tool_use_id: "tool-git-commit-compact-trailers",
4558
5246
  tool_input: {
4559
5247
  command: [
4560
- 'git commit',
5248
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4561
5249
  '-m "Launch lvisai.xyz intro site"',
4562
5250
  '-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
5251
  ].join(" "),
@@ -4584,7 +5272,7 @@ exit 0
4584
5272
  tool_use_id: "tool-git-commit-compact-no-separator",
4585
5273
  tool_input: {
4586
5274
  command: [
4587
- 'git commit',
5275
+ 'OMX_LORE_COMMIT_GUARD=1 git commit',
4588
5276
  '--message="Launch lvisai.xyz intro site\nCo-authored-by: OmX <omx@oh-my-codex.dev>"',
4589
5277
  ].join(" "),
4590
5278
  },
@@ -5617,6 +6305,91 @@ exit 0
5617
6305
  }
5618
6306
  });
5619
6307
 
6308
+ it("allows Stop when terminal Autopilot run-state shadows stale session ralplan state", async () => {
6309
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-terminal-run-state-"));
6310
+ try {
6311
+ const stateDir = join(cwd, ".omx", "state");
6312
+ const sessionId = "sess-stop-autopilot-terminal-run-state";
6313
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6314
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
6315
+ active: true,
6316
+ mode: "autopilot",
6317
+ current_phase: "ralplan",
6318
+ });
6319
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
6320
+ version: 1,
6321
+ active: false,
6322
+ mode: "autopilot",
6323
+ outcome: "finish",
6324
+ lifecycle_outcome: "finished",
6325
+ current_phase: "complete",
6326
+ completed_at: "2026-05-20T11:00:00.000Z",
6327
+ updated_at: "2026-05-20T11:00:00.000Z",
6328
+ });
6329
+
6330
+ const result = await dispatchCodexNativeHook(
6331
+ {
6332
+ hook_event_name: "Stop",
6333
+ cwd,
6334
+ session_id: sessionId,
6335
+ thread_id: "thread-stop-autopilot-terminal-run-state",
6336
+ turn_id: "turn-stop-autopilot-terminal-run-state-1",
6337
+ last_assistant_message: "Done. Verification passed.",
6338
+ },
6339
+ { cwd },
6340
+ );
6341
+
6342
+ assert.equal(result.omxEventName, "stop");
6343
+ assert.equal(result.outputJson, null);
6344
+ } finally {
6345
+ await rm(cwd, { recursive: true, force: true });
6346
+ }
6347
+ });
6348
+
6349
+ it("still blocks Stop while Autopilot ralplan state is genuinely non-terminal", async () => {
6350
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-autopilot-active-ralplan-"));
6351
+ try {
6352
+ const stateDir = join(cwd, ".omx", "state");
6353
+ const sessionId = "sess-stop-autopilot-active-ralplan";
6354
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
6355
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
6356
+ active: true,
6357
+ mode: "autopilot",
6358
+ current_phase: "ralplan",
6359
+ });
6360
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
6361
+ version: 1,
6362
+ active: true,
6363
+ mode: "autopilot",
6364
+ outcome: "continue",
6365
+ current_phase: "ralplan",
6366
+ updated_at: "2026-05-20T11:00:00.000Z",
6367
+ });
6368
+
6369
+ const result = await dispatchCodexNativeHook(
6370
+ {
6371
+ hook_event_name: "Stop",
6372
+ cwd,
6373
+ session_id: sessionId,
6374
+ thread_id: "thread-stop-autopilot-active-ralplan",
6375
+ turn_id: "turn-stop-autopilot-active-ralplan-1",
6376
+ },
6377
+ { cwd },
6378
+ );
6379
+
6380
+ assert.equal(result.omxEventName, "stop");
6381
+ assert.deepEqual(result.outputJson, {
6382
+ decision: "block",
6383
+ reason:
6384
+ "OMX autopilot is still active (phase: ralplan); continue the task and gather fresh verification evidence before stopping.",
6385
+ stopReason: "autopilot_ralplan",
6386
+ systemMessage: "OMX autopilot is still active (phase: ralplan).",
6387
+ });
6388
+ } finally {
6389
+ await rm(cwd, { recursive: true, force: true });
6390
+ }
6391
+ });
6392
+
5620
6393
  it("does not block Stop from stale root Autopilot planning state when the explicit session has no scoped state", async () => {
5621
6394
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-autopilot-planning-"));
5622
6395
  try {
@@ -8381,16 +9154,29 @@ exit 0
8381
9154
  assert.equal(result.omxEventName, "stop");
8382
9155
  const reason = String(result.outputJson?.reason);
8383
9156
  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: \[\.\.\.\] \}/);
9157
+ assert.match(reason, /set "completion_audit" on the Ralph state object/);
9158
+ assert.doesNotMatch(reason, /state\.completion_audit/);
8385
9159
  assert.match(reason, /repo-relative JSON file/);
8386
9160
  assert.match(reason, /Markdown artifacts and flat top-level checklist\/evidence fields are not accepted/);
8387
9161
  assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
8388
9162
  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");
9163
+ assert.equal(reopened.active, false);
9164
+ assert.equal(reopened.current_phase, "complete");
8391
9165
  assert.equal(reopened.completion_audit_gate, "blocked");
8392
9166
  assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
8393
- assert.equal(typeof reopened.completed_at, "undefined");
9167
+ assert.equal(reopened.completed_at, "2026-05-10T12:00:00.000Z");
9168
+
9169
+ const repeat = await dispatchCodexNativeHook(
9170
+ {
9171
+ hook_event_name: "Stop",
9172
+ cwd,
9173
+ session_id: sessionId,
9174
+ last_assistant_message: "Done. Ralph complete.",
9175
+ },
9176
+ { cwd },
9177
+ );
9178
+ assert.equal(repeat.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
9179
+ assert.doesNotMatch(String(repeat.outputJson?.reason), /Ralph is still active/);
8394
9180
  } finally {
8395
9181
  await rm(cwd, { recursive: true, force: true });
8396
9182
  }