oh-my-codex 0.16.3 → 0.16.4

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 (278) 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__/plugin-bundle-ssot.test.js +9 -0
  5. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  6. package/dist/cli/__tests__/cleanup.test.js +27 -0
  7. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  8. package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -5
  9. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  10. package/dist/cli/__tests__/doctor-warning-copy.test.js +101 -6
  11. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  12. package/dist/cli/__tests__/index.test.js +131 -2
  13. package/dist/cli/__tests__/index.test.js.map +1 -1
  14. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +2 -0
  15. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  16. package/dist/cli/__tests__/ralph.test.js +47 -0
  17. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  18. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +2 -2
  19. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  20. package/dist/cli/__tests__/setup-install-mode.test.js +272 -26
  21. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  22. package/dist/cli/__tests__/setup-refresh.test.js +85 -3
  23. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  24. package/dist/cli/__tests__/setup-scope.test.js +1 -1
  25. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  26. package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -1
  27. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  28. package/dist/cli/__tests__/team.test.js +108 -0
  29. package/dist/cli/__tests__/team.test.js.map +1 -1
  30. package/dist/cli/__tests__/ultragoal.test.js +69 -0
  31. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  32. package/dist/cli/__tests__/uninstall.test.js +54 -8
  33. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  34. package/dist/cli/cleanup.d.ts.map +1 -1
  35. package/dist/cli/cleanup.js +8 -4
  36. package/dist/cli/cleanup.js.map +1 -1
  37. package/dist/cli/codex-feature-probe.d.ts +9 -0
  38. package/dist/cli/codex-feature-probe.d.ts.map +1 -0
  39. package/dist/cli/codex-feature-probe.js +28 -0
  40. package/dist/cli/codex-feature-probe.js.map +1 -0
  41. package/dist/cli/doctor.d.ts +1 -0
  42. package/dist/cli/doctor.d.ts.map +1 -1
  43. package/dist/cli/doctor.js +152 -17
  44. package/dist/cli/doctor.js.map +1 -1
  45. package/dist/cli/index.d.ts +9 -2
  46. package/dist/cli/index.d.ts.map +1 -1
  47. package/dist/cli/index.js +135 -17
  48. package/dist/cli/index.js.map +1 -1
  49. package/dist/cli/mcp-parity.js +8 -8
  50. package/dist/cli/mcp-parity.js.map +1 -1
  51. package/dist/cli/plugin-marketplace.d.ts +3 -0
  52. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  53. package/dist/cli/plugin-marketplace.js +88 -0
  54. package/dist/cli/plugin-marketplace.js.map +1 -1
  55. package/dist/cli/ralph.d.ts.map +1 -1
  56. package/dist/cli/ralph.js +21 -0
  57. package/dist/cli/ralph.js.map +1 -1
  58. package/dist/cli/setup-preferences.d.ts +4 -0
  59. package/dist/cli/setup-preferences.d.ts.map +1 -1
  60. package/dist/cli/setup-preferences.js +7 -0
  61. package/dist/cli/setup-preferences.js.map +1 -1
  62. package/dist/cli/setup.d.ts +5 -3
  63. package/dist/cli/setup.d.ts.map +1 -1
  64. package/dist/cli/setup.js +114 -44
  65. package/dist/cli/setup.js.map +1 -1
  66. package/dist/cli/ultragoal.d.ts +1 -1
  67. package/dist/cli/ultragoal.d.ts.map +1 -1
  68. package/dist/cli/ultragoal.js +64 -5
  69. package/dist/cli/ultragoal.js.map +1 -1
  70. package/dist/cli/uninstall.d.ts +2 -0
  71. package/dist/cli/uninstall.d.ts.map +1 -1
  72. package/dist/cli/uninstall.js +12 -3
  73. package/dist/cli/uninstall.js.map +1 -1
  74. package/dist/config/__tests__/codex-feature-flags.test.d.ts +2 -0
  75. package/dist/config/__tests__/codex-feature-flags.test.d.ts.map +1 -0
  76. package/dist/config/__tests__/codex-feature-flags.test.js +35 -0
  77. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -0
  78. package/dist/config/__tests__/codex-hooks.test.js +7 -0
  79. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  80. package/dist/config/__tests__/generator-idempotent.test.js +70 -9
  81. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  82. package/dist/config/__tests__/generator-notify.test.js +116 -11
  83. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  84. package/dist/config/__tests__/wiki-config-contract.test.js +6 -3
  85. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -1
  86. package/dist/config/codex-feature-flags.d.ts +21 -0
  87. package/dist/config/codex-feature-flags.d.ts.map +1 -0
  88. package/dist/config/codex-feature-flags.js +56 -0
  89. package/dist/config/codex-feature-flags.js.map +1 -0
  90. package/dist/config/codex-hooks.d.ts +2 -0
  91. package/dist/config/codex-hooks.d.ts.map +1 -1
  92. package/dist/config/codex-hooks.js +25 -3
  93. package/dist/config/codex-hooks.js.map +1 -1
  94. package/dist/config/generator.d.ts +11 -2
  95. package/dist/config/generator.d.ts.map +1 -1
  96. package/dist/config/generator.js +221 -123
  97. package/dist/config/generator.js.map +1 -1
  98. package/dist/config/omx-first-party-mcp.d.ts +3 -1
  99. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  100. package/dist/config/omx-first-party-mcp.js +2 -2
  101. package/dist/config/omx-first-party-mcp.js.map +1 -1
  102. package/dist/hooks/__tests__/keyword-detector.test.js +92 -2
  103. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  104. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +125 -1
  105. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  106. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts +2 -0
  107. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts.map +1 -0
  108. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +84 -0
  109. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -0
  110. package/dist/hooks/agents-overlay.js +2 -2
  111. package/dist/hooks/agents-overlay.js.map +1 -1
  112. package/dist/hooks/keyword-detector.d.ts +1 -0
  113. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  114. package/dist/hooks/keyword-detector.js +7 -5
  115. package/dist/hooks/keyword-detector.js.map +1 -1
  116. package/dist/hud/__tests__/state.test.js +164 -0
  117. package/dist/hud/__tests__/state.test.js.map +1 -1
  118. package/dist/hud/state.d.ts.map +1 -1
  119. package/dist/hud/state.js +4 -5
  120. package/dist/hud/state.js.map +1 -1
  121. package/dist/mcp/__tests__/state-paths.test.js +61 -0
  122. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  123. package/dist/mcp/__tests__/state-server.test.js +166 -0
  124. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  125. package/dist/mcp/state-paths.d.ts.map +1 -1
  126. package/dist/mcp/state-paths.js +23 -2
  127. package/dist/mcp/state-paths.js.map +1 -1
  128. package/dist/modes/__tests__/base-session-scope.test.js +22 -0
  129. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  130. package/dist/modes/__tests__/base-tmux-pane.test.js +57 -26
  131. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  132. package/dist/modes/base.d.ts.map +1 -1
  133. package/dist/modes/base.js +5 -0
  134. package/dist/modes/base.js.map +1 -1
  135. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts +2 -0
  136. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts.map +1 -0
  137. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +316 -0
  138. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -0
  139. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts +2 -0
  140. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts.map +1 -0
  141. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +481 -0
  142. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -0
  143. package/dist/planning/__tests__/artifacts.test.js +533 -4
  144. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  145. package/dist/planning/__tests__/context-pack-status.test.js +524 -0
  146. package/dist/planning/__tests__/context-pack-status.test.js.map +1 -1
  147. package/dist/planning/__tests__/markdown-structure.test.d.ts +2 -0
  148. package/dist/planning/__tests__/markdown-structure.test.d.ts.map +1 -0
  149. package/dist/planning/__tests__/markdown-structure.test.js +459 -0
  150. package/dist/planning/__tests__/markdown-structure.test.js.map +1 -0
  151. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +523 -1
  152. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -1
  153. package/dist/planning/artifacts.d.ts +1 -1
  154. package/dist/planning/artifacts.d.ts.map +1 -1
  155. package/dist/planning/artifacts.js +227 -28
  156. package/dist/planning/artifacts.js.map +1 -1
  157. package/dist/planning/context-pack-status.d.ts +25 -0
  158. package/dist/planning/context-pack-status.d.ts.map +1 -1
  159. package/dist/planning/context-pack-status.js +272 -31
  160. package/dist/planning/context-pack-status.js.map +1 -1
  161. package/dist/planning/markdown-structure.d.ts +20 -0
  162. package/dist/planning/markdown-structure.d.ts.map +1 -0
  163. package/dist/planning/markdown-structure.js +137 -0
  164. package/dist/planning/markdown-structure.js.map +1 -0
  165. package/dist/ralph/__tests__/completion-audit.test.d.ts +2 -0
  166. package/dist/ralph/__tests__/completion-audit.test.d.ts.map +1 -0
  167. package/dist/ralph/__tests__/completion-audit.test.js +121 -0
  168. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -0
  169. package/dist/ralph/completion-audit.d.ts +8 -0
  170. package/dist/ralph/completion-audit.d.ts.map +1 -0
  171. package/dist/ralph/completion-audit.js +99 -0
  172. package/dist/ralph/completion-audit.js.map +1 -0
  173. package/dist/scripts/__tests__/codex-native-hook.test.js +220 -13
  174. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  175. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts +2 -0
  176. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts.map +1 -0
  177. package/dist/scripts/__tests__/notify-dispatcher.test.js +126 -0
  178. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -0
  179. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  180. package/dist/scripts/codex-native-hook.js +133 -54
  181. package/dist/scripts/codex-native-hook.js.map +1 -1
  182. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  183. package/dist/scripts/codex-native-pre-post.js +4 -2
  184. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  185. package/dist/scripts/notify-dispatcher.js +30 -1
  186. package/dist/scripts/notify-dispatcher.js.map +1 -1
  187. package/dist/scripts/notify-hook.js +3 -1
  188. package/dist/scripts/notify-hook.js.map +1 -1
  189. package/dist/state/__tests__/workflow-transition.test.js +102 -27
  190. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  191. package/dist/state/operations.d.ts.map +1 -1
  192. package/dist/state/operations.js +9 -3
  193. package/dist/state/operations.js.map +1 -1
  194. package/dist/state/skill-active.d.ts +7 -0
  195. package/dist/state/skill-active.d.ts.map +1 -1
  196. package/dist/state/skill-active.js +25 -8
  197. package/dist/state/skill-active.js.map +1 -1
  198. package/dist/state/workflow-transition-reconcile.d.ts +1 -0
  199. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  200. package/dist/state/workflow-transition-reconcile.js +22 -15
  201. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  202. package/dist/state/workflow-transition.js +3 -3
  203. package/dist/state/workflow-transition.js.map +1 -1
  204. package/dist/team/__tests__/approved-execution.test.js +39 -0
  205. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  206. package/dist/team/__tests__/runtime.test.js +5 -0
  207. package/dist/team/__tests__/runtime.test.js.map +1 -1
  208. package/dist/team/__tests__/scaling.test.js +497 -2
  209. package/dist/team/__tests__/scaling.test.js.map +1 -1
  210. package/dist/team/__tests__/state-root.test.js +1 -1
  211. package/dist/team/__tests__/state-root.test.js.map +1 -1
  212. package/dist/team/__tests__/worker-bootstrap.test.js +8 -0
  213. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  214. package/dist/team/approved-execution.d.ts.map +1 -1
  215. package/dist/team/approved-execution.js +3 -0
  216. package/dist/team/approved-execution.js.map +1 -1
  217. package/dist/team/scaling.d.ts.map +1 -1
  218. package/dist/team/scaling.js +43 -0
  219. package/dist/team/scaling.js.map +1 -1
  220. package/dist/team/state-root.d.ts.map +1 -1
  221. package/dist/team/state-root.js +4 -0
  222. package/dist/team/state-root.js.map +1 -1
  223. package/dist/team/state.d.ts.map +1 -1
  224. package/dist/team/state.js +2 -6
  225. package/dist/team/state.js.map +1 -1
  226. package/dist/ultragoal/__tests__/artifacts.test.js +124 -1
  227. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  228. package/dist/ultragoal/__tests__/docs-contract.test.js +21 -0
  229. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  230. package/dist/ultragoal/artifacts.d.ts +44 -2
  231. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  232. package/dist/ultragoal/artifacts.js +197 -13
  233. package/dist/ultragoal/artifacts.js.map +1 -1
  234. package/dist/wiki/lifecycle.js +1 -1
  235. package/dist/wiki/lifecycle.js.map +1 -1
  236. package/package.json +1 -1
  237. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  238. package/plugins/oh-my-codex/.mcp.json +5 -5
  239. package/plugins/oh-my-codex/skills/analyze/SKILL.md +0 -2
  240. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +2 -2
  241. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -3
  242. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +5 -7
  243. package/plugins/oh-my-codex/skills/doctor/SKILL.md +2 -2
  244. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +3 -3
  245. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +3 -3
  246. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -6
  247. package/plugins/oh-my-codex/skills/ralph/SKILL.md +9 -10
  248. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +36 -3
  249. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +21 -24
  250. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +8 -8
  251. package/plugins/oh-my-codex/skills/wiki/SKILL.md +13 -13
  252. package/skills/analyze/SKILL.md +0 -2
  253. package/skills/ask-claude/SKILL.md +5 -3
  254. package/skills/ask-gemini/SKILL.md +5 -3
  255. package/skills/autopilot/SKILL.md +2 -2
  256. package/skills/code-review/SKILL.md +1 -3
  257. package/skills/deep-interview/SKILL.md +5 -7
  258. package/skills/doctor/SKILL.md +2 -2
  259. package/skills/ecomode/SKILL.md +105 -1
  260. package/skills/frontend-ui-ux/SKILL.md +4 -26
  261. package/skills/git-master/SKILL.md +2 -4
  262. package/skills/omx-setup/SKILL.md +3 -3
  263. package/skills/pipeline/SKILL.md +3 -3
  264. package/skills/plan/SKILL.md +3 -6
  265. package/skills/ralph/SKILL.md +9 -10
  266. package/skills/swarm/SKILL.md +5 -3
  267. package/skills/tdd/SKILL.md +95 -1
  268. package/skills/ultragoal/SKILL.md +36 -3
  269. package/skills/ultraqa/SKILL.md +21 -24
  270. package/skills/ultrawork/SKILL.md +8 -8
  271. package/skills/web-clone/SKILL.md +348 -1
  272. package/skills/wiki/SKILL.md +13 -13
  273. package/src/scripts/__tests__/codex-native-hook.test.ts +231 -13
  274. package/src/scripts/__tests__/notify-dispatcher.test.ts +153 -0
  275. package/src/scripts/codex-native-hook.ts +160 -43
  276. package/src/scripts/codex-native-pre-post.ts +4 -1
  277. package/src/scripts/notify-dispatcher.ts +40 -1
  278. package/src/scripts/notify-hook.ts +3 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=completion-audit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-audit.test.d.ts","sourceRoot":"","sources":["../../../src/ralph/__tests__/completion-audit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ import { mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join, relative } from 'node:path';
4
+ import { test } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { evaluateRalphCompletionAuditEvidence } from '../completion-audit.js';
7
+ test('rejects absolute completion-audit paths', async () => {
8
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-absolute-'));
9
+ try {
10
+ const result = evaluateRalphCompletionAuditEvidence({ completion_audit_path: '/etc/hosts' }, cwd);
11
+ assert.equal(result.complete, false);
12
+ assert.equal(result.reason, 'missing_completion_audit');
13
+ assert.equal(result.source, 'missing');
14
+ }
15
+ finally {
16
+ await rm(cwd, { recursive: true, force: true });
17
+ }
18
+ });
19
+ test('rejects non-JSON completion-audit artifacts', async () => {
20
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-non-json-'));
21
+ try {
22
+ await writeFile(join(cwd, 'audit.md'), 'passed with checklist and tests', 'utf-8');
23
+ const result = evaluateRalphCompletionAuditEvidence({ completion_audit_path: 'audit.md' }, cwd);
24
+ assert.equal(result.complete, false);
25
+ assert.equal(result.reason, 'missing_completion_audit');
26
+ assert.equal(result.source, 'missing');
27
+ }
28
+ finally {
29
+ await rm(cwd, { recursive: true, force: true });
30
+ }
31
+ });
32
+ test('rejects completion-audit artifacts outside the workspace', async () => {
33
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-traversal-'));
34
+ const outside = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-outside-'));
35
+ try {
36
+ await writeFile(join(outside, 'audit.json'), JSON.stringify({
37
+ passed: true,
38
+ prompt_to_artifact_checklist: ['mapped'],
39
+ verification_evidence: ['tested'],
40
+ }), 'utf-8');
41
+ const result = evaluateRalphCompletionAuditEvidence({ completion_audit_path: relative(cwd, join(outside, 'audit.json')) }, cwd);
42
+ assert.equal(result.complete, false);
43
+ assert.equal(result.reason, 'missing_completion_audit');
44
+ assert.equal(result.source, 'missing');
45
+ }
46
+ finally {
47
+ await rm(cwd, { recursive: true, force: true });
48
+ await rm(outside, { recursive: true, force: true });
49
+ }
50
+ });
51
+ test('rejects completion-audit symlinks that resolve outside the workspace', async () => {
52
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-symlink-cwd-'));
53
+ const outside = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-symlink-outside-'));
54
+ try {
55
+ await writeFile(join(outside, 'audit.json'), JSON.stringify({
56
+ passed: true,
57
+ prompt_to_artifact_checklist: ['mapped'],
58
+ verification_evidence: ['tested'],
59
+ }), 'utf-8');
60
+ await symlink(join(outside, 'audit.json'), join(cwd, 'audit.json'));
61
+ const result = evaluateRalphCompletionAuditEvidence({ completion_audit_path: 'audit.json' }, cwd);
62
+ assert.equal(result.complete, false);
63
+ assert.equal(result.reason, 'missing_completion_audit');
64
+ assert.equal(result.source, 'missing');
65
+ }
66
+ finally {
67
+ await rm(cwd, { recursive: true, force: true });
68
+ await rm(outside, { recursive: true, force: true });
69
+ }
70
+ });
71
+ test('rejects malformed and empty completion-audit JSON artifacts', async () => {
72
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-invalid-json-'));
73
+ try {
74
+ await writeFile(join(cwd, 'empty.json'), '', 'utf-8');
75
+ await writeFile(join(cwd, 'invalid.json'), '{not json', 'utf-8');
76
+ for (const path of ['empty.json', 'invalid.json']) {
77
+ const result = evaluateRalphCompletionAuditEvidence({ completion_audit_path: path }, cwd);
78
+ assert.equal(result.complete, false);
79
+ assert.equal(result.reason, 'missing_completion_audit');
80
+ assert.equal(result.source, 'missing');
81
+ }
82
+ }
83
+ finally {
84
+ await rm(cwd, { recursive: true, force: true });
85
+ }
86
+ });
87
+ test('requires explicit passed=true in completion-audit evidence', async () => {
88
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-explicit-pass-'));
89
+ try {
90
+ await writeFile(join(cwd, 'audit.json'), JSON.stringify({
91
+ status: 'passed',
92
+ prompt_to_artifact_checklist: ['mapped'],
93
+ verification_evidence: ['tested'],
94
+ }), 'utf-8');
95
+ const result = evaluateRalphCompletionAuditEvidence({ completion_audit_path: 'audit.json' }, cwd);
96
+ assert.equal(result.complete, false);
97
+ assert.equal(result.reason, 'completion_audit_not_passing');
98
+ assert.equal(result.source, 'artifact');
99
+ }
100
+ finally {
101
+ await rm(cwd, { recursive: true, force: true });
102
+ }
103
+ });
104
+ test('accepts structured relative JSON completion-audit artifacts with evidence', async () => {
105
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-ralph-audit-valid-json-'));
106
+ try {
107
+ await writeFile(join(cwd, 'audit.json'), JSON.stringify({
108
+ passed: true,
109
+ prompt_to_artifact_checklist: ['mapped prompt to artifact'],
110
+ verification_evidence: ['node --test dist/ralph/__tests__/completion-audit.test.js'],
111
+ }), 'utf-8');
112
+ const result = evaluateRalphCompletionAuditEvidence({ completion_audit_path: 'audit.json' }, cwd);
113
+ assert.equal(result.complete, true);
114
+ assert.equal(result.reason, 'completion_audit_passed');
115
+ assert.equal(result.source, 'artifact');
116
+ }
117
+ finally {
118
+ await rm(cwd, { recursive: true, force: true });
119
+ }
120
+ });
121
+ //# sourceMappingURL=completion-audit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-audit.test.js","sourceRoot":"","sources":["../../../src/ralph/__tests__/completion-audit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface RalphCompletionAuditResult {
2
+ complete: boolean;
3
+ reason: string;
4
+ source: 'state' | 'artifact' | 'missing';
5
+ }
6
+ export declare function evaluateRalphCompletionAuditEvidence(state: Record<string, unknown>, cwd: string): RalphCompletionAuditResult;
7
+ export declare function isRalphCompletePhase(value: unknown): boolean;
8
+ //# sourceMappingURL=completion-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-audit.d.ts","sourceRoot":"","sources":["../../src/ralph/completion-audit.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CAC1C;AAgED,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,EAAE,MAAM,GACV,0BAA0B,CA+B5B;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG5D"}
@@ -0,0 +1,99 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
+ import { extname, isAbsolute, relative, resolve } from 'node:path';
3
+ const JSON_EXTENSION = '.json';
4
+ function isRecord(value) {
5
+ return value != null && typeof value === 'object' && !Array.isArray(value);
6
+ }
7
+ function safeString(value) {
8
+ return typeof value === 'string' ? value.trim() : '';
9
+ }
10
+ function hasPassingVerdict(value) {
11
+ return value.passed === true;
12
+ }
13
+ function isInsideDirectory(parent, child) {
14
+ const childRelativePath = relative(parent, child);
15
+ return childRelativePath === '' || (!childRelativePath.startsWith('..') && !isAbsolute(childRelativePath));
16
+ }
17
+ function hasSubstantiveValue(value) {
18
+ if (typeof value === 'string')
19
+ return value.trim().length > 0;
20
+ if (Array.isArray(value))
21
+ return value.length > 0;
22
+ if (isRecord(value))
23
+ return Object.keys(value).length > 0;
24
+ return value === true;
25
+ }
26
+ function readAuditArtifact(cwd, rawPath) {
27
+ const auditPath = safeString(rawPath);
28
+ if (!auditPath)
29
+ return null;
30
+ if (isAbsolute(auditPath))
31
+ return null;
32
+ const root = resolve(cwd);
33
+ const resolvedPath = resolve(root, auditPath);
34
+ if (!isInsideDirectory(root, resolvedPath))
35
+ return null;
36
+ if (!existsSync(resolvedPath))
37
+ return null;
38
+ if (extname(resolvedPath) !== JSON_EXTENSION)
39
+ return null;
40
+ const rootRealPath = realpathSync(root);
41
+ const artifactRealPath = realpathSync(resolvedPath);
42
+ if (!isInsideDirectory(rootRealPath, artifactRealPath))
43
+ return null;
44
+ try {
45
+ const content = readFileSync(resolvedPath, 'utf-8').trim();
46
+ if (!content)
47
+ return null;
48
+ const parsed = JSON.parse(content);
49
+ return isRecord(parsed) ? parsed : null;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ function selectAuditCandidate(state, cwd) {
56
+ for (const key of ['completion_audit', 'completionAudit', 'completion_audit_evidence', 'completionAuditEvidence']) {
57
+ if (isRecord(state[key]))
58
+ return { audit: state[key], source: 'state' };
59
+ }
60
+ for (const key of ['completion_audit_path', 'completionAuditPath', 'completion_audit_evidence_path', 'completionAuditEvidencePath']) {
61
+ const artifact = readAuditArtifact(cwd, state[key]);
62
+ if (artifact)
63
+ return { audit: artifact, source: 'artifact' };
64
+ }
65
+ return { audit: null, source: 'missing' };
66
+ }
67
+ export function evaluateRalphCompletionAuditEvidence(state, cwd) {
68
+ const { audit, source } = selectAuditCandidate(state, cwd);
69
+ if (!audit) {
70
+ return { complete: false, reason: 'missing_completion_audit', source };
71
+ }
72
+ if (!hasPassingVerdict(audit)) {
73
+ return { complete: false, reason: 'completion_audit_not_passing', source };
74
+ }
75
+ const checklist = audit.prompt_to_artifact_checklist
76
+ ?? audit.promptToArtifactChecklist
77
+ ?? audit.checklist
78
+ ?? audit.requirements_checklist
79
+ ?? audit.requirementsChecklist;
80
+ if (!hasSubstantiveValue(checklist)) {
81
+ return { complete: false, reason: 'missing_completion_checklist', source };
82
+ }
83
+ const evidence = audit.verification_evidence
84
+ ?? audit.verificationEvidence
85
+ ?? audit.evidence
86
+ ?? audit.validation_evidence
87
+ ?? audit.validationEvidence
88
+ ?? audit.commands
89
+ ?? audit.tests;
90
+ if (!hasSubstantiveValue(evidence)) {
91
+ return { complete: false, reason: 'missing_verification_evidence', source };
92
+ }
93
+ return { complete: true, reason: 'completion_audit_passed', source };
94
+ }
95
+ export function isRalphCompletePhase(value) {
96
+ const phase = safeString(value).toLowerCase();
97
+ return phase === 'complete' || phase === 'completed';
98
+ }
99
+ //# sourceMappingURL=completion-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-audit.js","sourceRoot":"","sources":["../../src/ralph/completion-audit.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ import { writeSessionStart } from "../../hooks/session.js";
14
14
  import { resetTriageConfigCache } from "../../hooks/triage-config.js";
15
15
  import { executeStateOperation } from "../../state/operations.js";
16
16
  import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
17
+ import { readAllState } from "../../hud/state.js";
17
18
  import { writePage } from "../../wiki/storage.js";
18
19
  import { WIKI_SCHEMA_VERSION } from "../../wiki/types.js";
19
20
  function nativeHookScriptPath() {
@@ -183,7 +184,7 @@ describe("codex native hook config", () => {
183
184
  "Stop",
184
185
  ]);
185
186
  const sessionStart = config.hooks.SessionStart[0];
186
- assert.equal(sessionStart.matcher, "startup|resume");
187
+ assert.equal(sessionStart.matcher, "startup|resume|clear");
187
188
  assert.equal(sessionStart.hooks?.[0]?.statusMessage, undefined);
188
189
  const preToolUse = config.hooks.PreToolUse[0];
189
190
  assert.equal(preToolUse.matcher, "Bash");
@@ -962,7 +963,7 @@ describe("codex native hook dispatch", () => {
962
963
  assert.equal(result.omxEventName, "keyword-detector");
963
964
  assert.equal(result.skillState?.skill, "ralplan");
964
965
  assert.ok(result.outputJson, "UserPromptSubmit should emit developer context");
965
- 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\./);
966
+ assert.match(JSON.stringify(result.outputJson), /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
966
967
  assert.equal(existsSync(join(cwd, ".omx", "state", "skill-active-state.json")), false, "session-scoped keyword activation should not write root skill-active-state.json");
967
968
  const statePath = join(cwd, ".omx", "state", "sessions", "sess-1", "skill-active-state.json");
968
969
  assert.equal(existsSync(statePath), true);
@@ -976,6 +977,114 @@ describe("codex native hook dispatch", () => {
976
977
  await rm(cwd, { recursive: true, force: true });
977
978
  }
978
979
  });
980
+ it("records boxed keyword activation mode detail and skill state under OMX_ROOT", async () => {
981
+ const root = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-"));
982
+ const cwd = join(root, "source");
983
+ const omxRoot = join(root, "box");
984
+ const sessionId = "sess-boxed-ralplan";
985
+ const previousOmxRoot = process.env.OMX_ROOT;
986
+ const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
987
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
988
+ const previousOmxSessionId = process.env.OMX_SESSION_ID;
989
+ try {
990
+ await mkdir(cwd, { recursive: true });
991
+ process.env.OMX_ROOT = omxRoot;
992
+ delete process.env.OMX_STATE_ROOT;
993
+ delete process.env.OMX_TEAM_STATE_ROOT;
994
+ process.env.OMX_SESSION_ID = sessionId;
995
+ const result = await dispatchCodexNativeHook({
996
+ hook_event_name: "UserPromptSubmit",
997
+ cwd,
998
+ session_id: sessionId,
999
+ thread_id: "thread-boxed",
1000
+ turn_id: "turn-boxed",
1001
+ prompt: "$ralplan implement issue #1307",
1002
+ }, { cwd });
1003
+ assert.equal(result.omxEventName, "keyword-detector");
1004
+ assert.equal(result.skillState?.skill, "ralplan");
1005
+ const boxedSessionDir = join(omxRoot, ".omx", "state", "sessions", sessionId);
1006
+ assert.equal(existsSync(join(boxedSessionDir, "skill-active-state.json")), true);
1007
+ assert.equal(existsSync(join(boxedSessionDir, "ralplan-state.json")), true);
1008
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json")), false);
1009
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "ralplan-state.json")), false);
1010
+ const hudState = await readAllState(cwd);
1011
+ assert.equal(hudState.ralplan?.active, true);
1012
+ assert.equal(hudState.ralplan?.current_phase, "planning");
1013
+ }
1014
+ finally {
1015
+ if (typeof previousOmxRoot === "string")
1016
+ process.env.OMX_ROOT = previousOmxRoot;
1017
+ else
1018
+ delete process.env.OMX_ROOT;
1019
+ if (typeof previousOmxStateRoot === "string")
1020
+ process.env.OMX_STATE_ROOT = previousOmxStateRoot;
1021
+ else
1022
+ delete process.env.OMX_STATE_ROOT;
1023
+ if (typeof previousTeamStateRoot === "string")
1024
+ process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
1025
+ else
1026
+ delete process.env.OMX_TEAM_STATE_ROOT;
1027
+ if (typeof previousOmxSessionId === "string")
1028
+ process.env.OMX_SESSION_ID = previousOmxSessionId;
1029
+ else
1030
+ delete process.env.OMX_SESSION_ID;
1031
+ await rm(root, { recursive: true, force: true });
1032
+ }
1033
+ });
1034
+ it("records native keyword activation mode detail and skill state under OMX_TEAM_STATE_ROOT", async () => {
1035
+ const root = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-"));
1036
+ const cwd = join(root, "source");
1037
+ const teamStateRoot = join(root, "team-state");
1038
+ const sessionId = "sess-team-root-ralplan";
1039
+ const previousOmxRoot = process.env.OMX_ROOT;
1040
+ const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
1041
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
1042
+ const previousOmxSessionId = process.env.OMX_SESSION_ID;
1043
+ try {
1044
+ await mkdir(cwd, { recursive: true });
1045
+ delete process.env.OMX_ROOT;
1046
+ delete process.env.OMX_STATE_ROOT;
1047
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
1048
+ process.env.OMX_SESSION_ID = sessionId;
1049
+ const result = await dispatchCodexNativeHook({
1050
+ hook_event_name: "UserPromptSubmit",
1051
+ cwd,
1052
+ session_id: sessionId,
1053
+ thread_id: "thread-team-root",
1054
+ turn_id: "turn-team-root",
1055
+ prompt: "$ralplan implement issue #1307",
1056
+ }, { cwd });
1057
+ assert.equal(result.omxEventName, "keyword-detector");
1058
+ assert.equal(result.skillState?.skill, "ralplan");
1059
+ const teamSessionDir = join(teamStateRoot, "sessions", sessionId);
1060
+ assert.equal(existsSync(join(teamSessionDir, "skill-active-state.json")), true);
1061
+ assert.equal(existsSync(join(teamSessionDir, "ralplan-state.json")), true);
1062
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json")), false);
1063
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "ralplan-state.json")), false);
1064
+ const hudState = await readAllState(cwd);
1065
+ assert.equal(hudState.ralplan?.active, true);
1066
+ assert.equal(hudState.ralplan?.current_phase, "planning");
1067
+ }
1068
+ finally {
1069
+ if (typeof previousOmxRoot === "string")
1070
+ process.env.OMX_ROOT = previousOmxRoot;
1071
+ else
1072
+ delete process.env.OMX_ROOT;
1073
+ if (typeof previousOmxStateRoot === "string")
1074
+ process.env.OMX_STATE_ROOT = previousOmxStateRoot;
1075
+ else
1076
+ delete process.env.OMX_STATE_ROOT;
1077
+ if (typeof previousTeamStateRoot === "string")
1078
+ process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
1079
+ else
1080
+ delete process.env.OMX_TEAM_STATE_ROOT;
1081
+ if (typeof previousOmxSessionId === "string")
1082
+ process.env.OMX_SESSION_ID = previousOmxSessionId;
1083
+ else
1084
+ delete process.env.OMX_SESSION_ID;
1085
+ await rm(root, { recursive: true, force: true });
1086
+ }
1087
+ });
979
1088
  it("warns completion-like prompts when active goal workflows need Codex snapshot reconciliation", async () => {
980
1089
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-goal-warning-"));
981
1090
  try {
@@ -1188,7 +1297,7 @@ describe("codex native hook dispatch", () => {
1188
1297
  assert.equal(result.skillState?.skill, "ralplan");
1189
1298
  const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
1190
1299
  assert.match(message, /\$oh-my-codex:ralplan" -> ralplan/);
1191
- 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\./);
1300
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1192
1301
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-plugin-1", "ralplan-state.json")), true);
1193
1302
  }
1194
1303
  finally {
@@ -1362,7 +1471,7 @@ describe("codex native hook dispatch", () => {
1362
1471
  assert.equal(result.skillState?.skill, "ralph");
1363
1472
  const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
1364
1473
  assert.match(message, /\$ralph" -> ralph/);
1365
- 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\./);
1474
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1366
1475
  assert.match(message, /Prompt-side `\$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`\./);
1367
1476
  assert.match(message, /Use `omx ralph --prd \.\.\.` only when you explicitly want the PRD-gated CLI startup path\./);
1368
1477
  }
@@ -1386,7 +1495,7 @@ describe("codex native hook dispatch", () => {
1386
1495
  assert.equal(result.skillState?.skill, "ralph");
1387
1496
  const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
1388
1497
  assert.match(message, /\$oh-my-codex:ralph" -> ralph/);
1389
- 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\./);
1498
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1390
1499
  assert.match(message, /Prompt-side `\$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`\./);
1391
1500
  }
1392
1501
  finally {
@@ -1455,7 +1564,7 @@ describe("codex native hook dispatch", () => {
1455
1564
  assert.equal(result.skillState?.skill, "deep-interview");
1456
1565
  const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
1457
1566
  assert.match(message, /\$deep-interview" -> deep-interview/);
1458
- 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\./);
1567
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1459
1568
  assert.match(message, /Deep-interview is active, but this session is not attached to tmux/);
1460
1569
  assert.match(message, /Do not invoke `omx question`, `omx hud`, or `omx team`/);
1461
1570
  assert.match(message, /native structured question tool when available/);
@@ -1845,8 +1954,11 @@ export async function onHookEvent(event) {
1845
1954
  }, { cwd });
1846
1955
  assert.match(JSON.stringify(denied.outputJson), /denied workflow keyword/i);
1847
1956
  assert.match(JSON.stringify(denied.outputJson), /Unsupported workflow overlap: team \+ autopilot\./);
1848
- assert.match(JSON.stringify(denied.outputJson), /`omx state clear --mode <mode>`/);
1849
- assert.match(JSON.stringify(denied.outputJson), /`omx_state\.\*` MCP tools/);
1957
+ assert.match(JSON.stringify(denied.outputJson), /omx state clear --input/);
1958
+ assert.match(JSON.stringify(denied.outputJson), /mode\\":\\"<mode>/);
1959
+ assert.match(JSON.stringify(denied.outputJson), /--json/);
1960
+ assert.match(JSON.stringify(denied.outputJson), /explicit MCP compatibility is enabled/);
1961
+ assert.match(JSON.stringify(denied.outputJson), /`omx_state\.\*` tools/);
1850
1962
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-deny-1", "autopilot-state.json")), false);
1851
1963
  }
1852
1964
  finally {
@@ -1905,7 +2017,7 @@ export async function onHookEvent(event) {
1905
2017
  assert.match(message, /\$ralph" -> ralph/);
1906
2018
  assert.doesNotMatch(message, /mode transiting:/);
1907
2019
  assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./);
1908
- 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\./);
2020
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1909
2021
  assert.doesNotMatch(message, /Use the durable OMX team runtime via `omx team \.\.\.`/);
1910
2022
  }
1911
2023
  finally {
@@ -1930,7 +2042,7 @@ export async function onHookEvent(event) {
1930
2042
  assert.match(message, /\$oh-my-codex:ralph" -> ralph/);
1931
2043
  assert.doesNotMatch(message, /mode transiting:/);
1932
2044
  assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./);
1933
- 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\./);
2045
+ assert.match(message, /use CLI-first state updates via `omx state write\/read\/clear --input '<json>' --json`/);
1934
2046
  }
1935
2047
  finally {
1936
2048
  await rm(cwd, { recursive: true, force: true });
@@ -3700,7 +3812,7 @@ exit 0
3700
3812
  assert.equal(output?.decision, "block");
3701
3813
  assert.equal(output?.reason, "The MCP tool appears to have lost its transport/server connection. Preserve state, debug the transport failure, and use OMX CLI/file-backed fallbacks instead of retrying blindly.");
3702
3814
  const additionalContext = String(output?.hookSpecificOutput?.additionalContext ?? "");
3703
- assert.match(additionalContext, /omx state state_write --input/);
3815
+ assert.match(additionalContext, /omx state write --input/);
3704
3816
  assert.match(additionalContext, /plain Node stdio processes/i);
3705
3817
  assert.match(additionalContext, /read-stall-state/);
3706
3818
  assert.match(additionalContext, /OMX_MCP_TRANSPORT_DEBUG=1/);
@@ -3870,7 +3982,7 @@ exit 0
3870
3982
  assert.match(String(result.outputJson?.reason || ""), /lost its transport\/server connection/);
3871
3983
  const hookSpecificOutput = result.outputJson?.hookSpecificOutput;
3872
3984
  assert.equal(hookSpecificOutput?.hookEventName, "PostToolUse");
3873
- assert.match(String(hookSpecificOutput?.additionalContext || ""), /Retry via CLI parity with `omx state state_write --input '\{\}' --json`\./);
3985
+ assert.match(String(hookSpecificOutput?.additionalContext || ""), /Retry via CLI parity with `omx state write --input '\{\}' --json`\./);
3874
3986
  assert.match(String(hookSpecificOutput?.additionalContext || ""), /omx team api read-stall-state/);
3875
3987
  const phase = JSON.parse(await readFile(join(cwd, ".omx", "state", "team", "mcp-transport-dead-team", "phase.json"), "utf-8"));
3876
3988
  assert.equal(phase.current_phase, "failed");
@@ -3928,7 +4040,7 @@ exit 0
3928
4040
  reason: "The MCP tool appears to have lost its transport/server connection. Preserve state, debug the transport failure, and use OMX CLI/file-backed fallbacks instead of retrying blindly.",
3929
4041
  hookSpecificOutput: {
3930
4042
  hookEventName: "PostToolUse",
3931
- additionalContext: "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.",
4043
+ additionalContext: "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.",
3932
4044
  },
3933
4045
  });
3934
4046
  const phase = await readTeamPhase("transport-team", cwd);
@@ -5093,6 +5205,38 @@ exit 0
5093
5205
  await rm(cwd, { recursive: true, force: true });
5094
5206
  }
5095
5207
  });
5208
+ it("ignores stale source-root team Stop fallback when OMX_TEAM_STATE_ROOT is authoritative", async () => {
5209
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-stale-source-root-"));
5210
+ const teamStateRoot = join(cwd, "shared-team-state");
5211
+ const priorTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
5212
+ try {
5213
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
5214
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
5215
+ await mkdir(join(teamStateRoot, "team", "stale-source-team"), { recursive: true });
5216
+ await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
5217
+ active: true,
5218
+ team_name: "stale-source-team",
5219
+ current_phase: "team-exec",
5220
+ });
5221
+ await writeJson(join(teamStateRoot, "team", "stale-source-team", "phase.json"), {
5222
+ current_phase: "team-exec",
5223
+ });
5224
+ const result = await dispatchCodexNativeHook({
5225
+ hook_event_name: "Stop",
5226
+ cwd,
5227
+ session_id: "sess-stale-source-team",
5228
+ }, { cwd });
5229
+ assert.equal(result.omxEventName, "stop");
5230
+ assert.equal(result.outputJson, null);
5231
+ }
5232
+ finally {
5233
+ if (typeof priorTeamStateRoot === "string")
5234
+ process.env.OMX_TEAM_STATE_ROOT = priorTeamStateRoot;
5235
+ else
5236
+ delete process.env.OMX_TEAM_STATE_ROOT;
5237
+ await rm(cwd, { recursive: true, force: true });
5238
+ }
5239
+ });
5096
5240
  it("returns Stop continuation output from canonical team state rooted via OMX_TEAM_STATE_ROOT", async () => {
5097
5241
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-env-root-"));
5098
5242
  const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
@@ -5984,6 +6128,69 @@ exit 0
5984
6128
  await rm(cwd, { recursive: true, force: true });
5985
6129
  }
5986
6130
  });
6131
+ it("blocks Codex App Stop when Ralph is marked complete without completion-audit evidence", async () => {
6132
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-complete-audit-missing-"));
6133
+ try {
6134
+ const sessionId = "sess-ralph-complete-missing";
6135
+ const statePath = join(cwd, ".omx", "state", "sessions", sessionId, "ralph-state.json");
6136
+ await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd });
6137
+ await writeJson(statePath, {
6138
+ active: false,
6139
+ mode: "ralph",
6140
+ current_phase: "complete",
6141
+ session_id: sessionId,
6142
+ completed_at: "2026-05-10T12:00:00.000Z",
6143
+ });
6144
+ const result = await dispatchCodexNativeHook({
6145
+ hook_event_name: "Stop",
6146
+ cwd,
6147
+ session_id: sessionId,
6148
+ last_assistant_message: "Done. Ralph complete.",
6149
+ }, { cwd });
6150
+ assert.equal(result.omxEventName, "stop");
6151
+ assert.match(String(result.outputJson?.reason), /Ralph completion audit is missing required evidence/);
6152
+ assert.equal(result.outputJson?.stopReason, "ralph_completion_audit_missing_completion_audit");
6153
+ const reopened = JSON.parse(await readFile(statePath, "utf-8"));
6154
+ assert.equal(reopened.active, true);
6155
+ assert.equal(reopened.current_phase, "verifying");
6156
+ assert.equal(reopened.completion_audit_gate, "blocked");
6157
+ assert.equal(reopened.completion_audit_missing_reason, "missing_completion_audit");
6158
+ assert.equal(typeof reopened.completed_at, "undefined");
6159
+ }
6160
+ finally {
6161
+ await rm(cwd, { recursive: true, force: true });
6162
+ }
6163
+ });
6164
+ it("allows Codex App Stop when complete Ralph state carries checklist and verification evidence", async () => {
6165
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-complete-audit-present-"));
6166
+ try {
6167
+ const sessionId = "sess-ralph-complete-present";
6168
+ await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, native_session_id: sessionId, cwd });
6169
+ await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "ralph-state.json"), {
6170
+ active: false,
6171
+ mode: "ralph",
6172
+ current_phase: "complete",
6173
+ session_id: sessionId,
6174
+ completed_at: "2026-05-10T12:00:00.000Z",
6175
+ completion_audit: {
6176
+ passed: true,
6177
+ prompt_to_artifact_checklist: ["issue #2260 fixed", "tests added"],
6178
+ verification_evidence: ["node --test dist/scripts/__tests__/codex-native-hook.test.js"],
6179
+ },
6180
+ });
6181
+ const result = await dispatchCodexNativeHook({
6182
+ hook_event_name: "Stop",
6183
+ cwd,
6184
+ session_id: sessionId,
6185
+ last_assistant_message: "Done with completion audit evidence recorded.",
6186
+ }, { cwd });
6187
+ assert.equal(result.omxEventName, "stop");
6188
+ assert.equal(result.outputJson, null);
6189
+ }
6190
+ finally {
6191
+ await rm(cwd, { recursive: true, force: true });
6192
+ }
6193
+ });
5987
6194
  it("returns Stop continuation output while Ralph is active without an explicit session pin", async () => {
5988
6195
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-"));
5989
6196
  try {