oh-my-codex 0.16.2 → 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 (340) 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 +137 -6
  11. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  12. package/dist/cli/__tests__/index.test.js +303 -4
  13. package/dist/cli/__tests__/index.test.js.map +1 -1
  14. package/dist/cli/__tests__/launch-fallback.test.js +58 -0
  15. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  16. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +2 -0
  17. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  18. package/dist/cli/__tests__/ralph.test.js +48 -0
  19. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  20. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +8 -0
  21. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  22. package/dist/cli/__tests__/setup-install-mode.test.js +350 -27
  23. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  24. package/dist/cli/__tests__/setup-refresh.test.js +85 -3
  25. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  26. package/dist/cli/__tests__/setup-scope.test.js +1 -1
  27. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  28. package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -1
  29. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  30. package/dist/cli/__tests__/team.test.js +269 -0
  31. package/dist/cli/__tests__/team.test.js.map +1 -1
  32. package/dist/cli/__tests__/ultragoal.test.js +69 -0
  33. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  34. package/dist/cli/__tests__/uninstall.test.js +90 -6
  35. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  36. package/dist/cli/__tests__/update.test.js +109 -19
  37. package/dist/cli/__tests__/update.test.js.map +1 -1
  38. package/dist/cli/cleanup.d.ts.map +1 -1
  39. package/dist/cli/cleanup.js +8 -4
  40. package/dist/cli/cleanup.js.map +1 -1
  41. package/dist/cli/codex-feature-probe.d.ts +9 -0
  42. package/dist/cli/codex-feature-probe.d.ts.map +1 -0
  43. package/dist/cli/codex-feature-probe.js +28 -0
  44. package/dist/cli/codex-feature-probe.js.map +1 -0
  45. package/dist/cli/doctor.d.ts +1 -0
  46. package/dist/cli/doctor.d.ts.map +1 -1
  47. package/dist/cli/doctor.js +168 -16
  48. package/dist/cli/doctor.js.map +1 -1
  49. package/dist/cli/index.d.ts +9 -2
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +168 -20
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/cli/mcp-parity.js +8 -8
  54. package/dist/cli/mcp-parity.js.map +1 -1
  55. package/dist/cli/plugin-marketplace.d.ts +3 -0
  56. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  57. package/dist/cli/plugin-marketplace.js +88 -0
  58. package/dist/cli/plugin-marketplace.js.map +1 -1
  59. package/dist/cli/ralph.d.ts.map +1 -1
  60. package/dist/cli/ralph.js +21 -0
  61. package/dist/cli/ralph.js.map +1 -1
  62. package/dist/cli/setup-preferences.d.ts +4 -0
  63. package/dist/cli/setup-preferences.d.ts.map +1 -1
  64. package/dist/cli/setup-preferences.js +7 -0
  65. package/dist/cli/setup-preferences.js.map +1 -1
  66. package/dist/cli/setup.d.ts +5 -3
  67. package/dist/cli/setup.d.ts.map +1 -1
  68. package/dist/cli/setup.js +177 -43
  69. package/dist/cli/setup.js.map +1 -1
  70. package/dist/cli/team.d.ts.map +1 -1
  71. package/dist/cli/team.js +54 -15
  72. package/dist/cli/team.js.map +1 -1
  73. package/dist/cli/ultragoal.d.ts +1 -1
  74. package/dist/cli/ultragoal.d.ts.map +1 -1
  75. package/dist/cli/ultragoal.js +64 -5
  76. package/dist/cli/ultragoal.js.map +1 -1
  77. package/dist/cli/uninstall.d.ts +2 -0
  78. package/dist/cli/uninstall.d.ts.map +1 -1
  79. package/dist/cli/uninstall.js +76 -5
  80. package/dist/cli/uninstall.js.map +1 -1
  81. package/dist/cli/update.d.ts +10 -2
  82. package/dist/cli/update.d.ts.map +1 -1
  83. package/dist/cli/update.js +99 -5
  84. package/dist/cli/update.js.map +1 -1
  85. package/dist/config/__tests__/codex-feature-flags.test.d.ts +2 -0
  86. package/dist/config/__tests__/codex-feature-flags.test.d.ts.map +1 -0
  87. package/dist/config/__tests__/codex-feature-flags.test.js +35 -0
  88. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -0
  89. package/dist/config/__tests__/codex-hooks.test.js +188 -4
  90. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  91. package/dist/config/__tests__/generator-idempotent.test.js +129 -10
  92. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  93. package/dist/config/__tests__/generator-notify.test.js +148 -7
  94. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  95. package/dist/config/__tests__/wiki-config-contract.test.js +6 -3
  96. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -1
  97. package/dist/config/codex-feature-flags.d.ts +21 -0
  98. package/dist/config/codex-feature-flags.d.ts.map +1 -0
  99. package/dist/config/codex-feature-flags.js +56 -0
  100. package/dist/config/codex-feature-flags.js.map +1 -0
  101. package/dist/config/codex-hooks.d.ts +40 -4
  102. package/dist/config/codex-hooks.d.ts.map +1 -1
  103. package/dist/config/codex-hooks.js +204 -18
  104. package/dist/config/codex-hooks.js.map +1 -1
  105. package/dist/config/generator.d.ts +19 -1
  106. package/dist/config/generator.d.ts.map +1 -1
  107. package/dist/config/generator.js +319 -83
  108. package/dist/config/generator.js.map +1 -1
  109. package/dist/config/omx-first-party-mcp.d.ts +3 -1
  110. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  111. package/dist/config/omx-first-party-mcp.js +2 -2
  112. package/dist/config/omx-first-party-mcp.js.map +1 -1
  113. package/dist/hooks/__tests__/keyword-detector.test.js +92 -2
  114. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  115. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +29 -1
  116. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +10 -0
  118. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  119. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +1 -0
  120. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  121. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts +2 -0
  122. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts.map +1 -0
  123. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +176 -0
  124. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -0
  125. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +148 -0
  126. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +3 -0
  128. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  129. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts +2 -0
  130. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts.map +1 -0
  131. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +84 -0
  132. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -0
  133. package/dist/hooks/__tests__/wiki-docs-contract.test.js +1 -2
  134. package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -1
  135. package/dist/hooks/agents-overlay.js +2 -2
  136. package/dist/hooks/agents-overlay.js.map +1 -1
  137. package/dist/hooks/keyword-detector.d.ts +1 -0
  138. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  139. package/dist/hooks/keyword-detector.js +7 -5
  140. package/dist/hooks/keyword-detector.js.map +1 -1
  141. package/dist/hud/__tests__/state.test.js +164 -0
  142. package/dist/hud/__tests__/state.test.js.map +1 -1
  143. package/dist/hud/state.d.ts.map +1 -1
  144. package/dist/hud/state.js +4 -5
  145. package/dist/hud/state.js.map +1 -1
  146. package/dist/mcp/__tests__/state-paths.test.js +61 -0
  147. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  148. package/dist/mcp/__tests__/state-server.test.js +166 -0
  149. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  150. package/dist/mcp/state-paths.d.ts.map +1 -1
  151. package/dist/mcp/state-paths.js +23 -2
  152. package/dist/mcp/state-paths.js.map +1 -1
  153. package/dist/modes/__tests__/base-session-scope.test.js +22 -0
  154. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  155. package/dist/modes/__tests__/base-tmux-pane.test.js +57 -26
  156. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  157. package/dist/modes/base.d.ts.map +1 -1
  158. package/dist/modes/base.js +5 -0
  159. package/dist/modes/base.js.map +1 -1
  160. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts +2 -0
  161. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts.map +1 -0
  162. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +316 -0
  163. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -0
  164. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts +2 -0
  165. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts.map +1 -0
  166. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +481 -0
  167. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -0
  168. package/dist/planning/__tests__/artifacts.test.js +597 -4
  169. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  170. package/dist/planning/__tests__/context-pack-status.test.js +524 -0
  171. package/dist/planning/__tests__/context-pack-status.test.js.map +1 -1
  172. package/dist/planning/__tests__/markdown-structure.test.d.ts +2 -0
  173. package/dist/planning/__tests__/markdown-structure.test.d.ts.map +1 -0
  174. package/dist/planning/__tests__/markdown-structure.test.js +459 -0
  175. package/dist/planning/__tests__/markdown-structure.test.js.map +1 -0
  176. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +2 -0
  177. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +1 -0
  178. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +612 -0
  179. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -0
  180. package/dist/planning/artifacts.d.ts +7 -2
  181. package/dist/planning/artifacts.d.ts.map +1 -1
  182. package/dist/planning/artifacts.js +279 -26
  183. package/dist/planning/artifacts.js.map +1 -1
  184. package/dist/planning/context-pack-status.d.ts +31 -0
  185. package/dist/planning/context-pack-status.d.ts.map +1 -1
  186. package/dist/planning/context-pack-status.js +291 -25
  187. package/dist/planning/context-pack-status.js.map +1 -1
  188. package/dist/planning/markdown-structure.d.ts +20 -0
  189. package/dist/planning/markdown-structure.d.ts.map +1 -0
  190. package/dist/planning/markdown-structure.js +137 -0
  191. package/dist/planning/markdown-structure.js.map +1 -0
  192. package/dist/ralph/__tests__/completion-audit.test.d.ts +2 -0
  193. package/dist/ralph/__tests__/completion-audit.test.d.ts.map +1 -0
  194. package/dist/ralph/__tests__/completion-audit.test.js +121 -0
  195. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -0
  196. package/dist/ralph/completion-audit.d.ts +8 -0
  197. package/dist/ralph/completion-audit.d.ts.map +1 -0
  198. package/dist/ralph/completion-audit.js +99 -0
  199. package/dist/ralph/completion-audit.js.map +1 -0
  200. package/dist/ralph/persistence.d.ts +1 -1
  201. package/dist/ralph/persistence.d.ts.map +1 -1
  202. package/dist/ralph/persistence.js +8 -2
  203. package/dist/ralph/persistence.js.map +1 -1
  204. package/dist/scripts/__tests__/codex-native-hook.test.js +359 -24
  205. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  206. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts +2 -0
  207. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts.map +1 -0
  208. package/dist/scripts/__tests__/notify-dispatcher.test.js +126 -0
  209. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -0
  210. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  211. package/dist/scripts/codex-native-hook.js +142 -76
  212. package/dist/scripts/codex-native-hook.js.map +1 -1
  213. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  214. package/dist/scripts/codex-native-pre-post.js +4 -2
  215. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  216. package/dist/scripts/notify-dispatcher.d.ts +7 -0
  217. package/dist/scripts/notify-dispatcher.d.ts.map +1 -0
  218. package/dist/scripts/notify-dispatcher.js +87 -0
  219. package/dist/scripts/notify-dispatcher.js.map +1 -0
  220. package/dist/scripts/notify-fallback-watcher.js +4 -0
  221. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  222. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  223. package/dist/scripts/notify-hook/ralph-session-resume.js +96 -8
  224. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  225. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  226. package/dist/scripts/notify-hook/state-io.js +6 -2
  227. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  228. package/dist/scripts/notify-hook/visual-verdict.js +3 -3
  229. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  230. package/dist/scripts/notify-hook.js +127 -1
  231. package/dist/scripts/notify-hook.js.map +1 -1
  232. package/dist/state/__tests__/workflow-transition.test.js +102 -27
  233. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  234. package/dist/state/operations.d.ts.map +1 -1
  235. package/dist/state/operations.js +9 -3
  236. package/dist/state/operations.js.map +1 -1
  237. package/dist/state/skill-active.d.ts +7 -0
  238. package/dist/state/skill-active.d.ts.map +1 -1
  239. package/dist/state/skill-active.js +25 -8
  240. package/dist/state/skill-active.js.map +1 -1
  241. package/dist/state/workflow-transition-reconcile.d.ts +1 -0
  242. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  243. package/dist/state/workflow-transition-reconcile.js +22 -15
  244. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  245. package/dist/state/workflow-transition.js +3 -3
  246. package/dist/state/workflow-transition.js.map +1 -1
  247. package/dist/team/__tests__/approved-execution.test.js +84 -1
  248. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  249. package/dist/team/__tests__/runtime.test.js +178 -19
  250. package/dist/team/__tests__/runtime.test.js.map +1 -1
  251. package/dist/team/__tests__/scaling.test.js +497 -2
  252. package/dist/team/__tests__/scaling.test.js.map +1 -1
  253. package/dist/team/__tests__/state-root.test.js +1 -1
  254. package/dist/team/__tests__/state-root.test.js.map +1 -1
  255. package/dist/team/__tests__/worker-bootstrap.test.js +45 -0
  256. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  257. package/dist/team/approved-execution.d.ts +1 -0
  258. package/dist/team/approved-execution.d.ts.map +1 -1
  259. package/dist/team/approved-execution.js +53 -0
  260. package/dist/team/approved-execution.js.map +1 -1
  261. package/dist/team/delivery-log.d.ts.map +1 -1
  262. package/dist/team/delivery-log.js +8 -1
  263. package/dist/team/delivery-log.js.map +1 -1
  264. package/dist/team/runtime.d.ts.map +1 -1
  265. package/dist/team/runtime.js +104 -18
  266. package/dist/team/runtime.js.map +1 -1
  267. package/dist/team/scaling.d.ts.map +1 -1
  268. package/dist/team/scaling.js +43 -0
  269. package/dist/team/scaling.js.map +1 -1
  270. package/dist/team/state/mailbox.d.ts +1 -0
  271. package/dist/team/state/mailbox.d.ts.map +1 -1
  272. package/dist/team/state/mailbox.js +10 -1
  273. package/dist/team/state/mailbox.js.map +1 -1
  274. package/dist/team/state-root.d.ts.map +1 -1
  275. package/dist/team/state-root.js +5 -1
  276. package/dist/team/state-root.js.map +1 -1
  277. package/dist/team/state.d.ts.map +1 -1
  278. package/dist/team/state.js +3 -7
  279. package/dist/team/state.js.map +1 -1
  280. package/dist/team/worker-bootstrap.d.ts +7 -2
  281. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  282. package/dist/team/worker-bootstrap.js +17 -4
  283. package/dist/team/worker-bootstrap.js.map +1 -1
  284. package/dist/ultragoal/__tests__/artifacts.test.js +124 -1
  285. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  286. package/dist/ultragoal/__tests__/docs-contract.test.js +21 -0
  287. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  288. package/dist/ultragoal/artifacts.d.ts +44 -2
  289. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  290. package/dist/ultragoal/artifacts.js +197 -13
  291. package/dist/ultragoal/artifacts.js.map +1 -1
  292. package/dist/wiki/lifecycle.js +1 -1
  293. package/dist/wiki/lifecycle.js.map +1 -1
  294. package/package.json +1 -1
  295. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  296. package/plugins/oh-my-codex/.mcp.json +5 -5
  297. package/plugins/oh-my-codex/skills/analyze/SKILL.md +0 -2
  298. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +2 -2
  299. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -3
  300. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +5 -7
  301. package/plugins/oh-my-codex/skills/doctor/SKILL.md +2 -2
  302. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +3 -3
  303. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +3 -3
  304. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -6
  305. package/plugins/oh-my-codex/skills/ralph/SKILL.md +9 -10
  306. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +36 -3
  307. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +21 -24
  308. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +8 -8
  309. package/plugins/oh-my-codex/skills/wiki/SKILL.md +13 -13
  310. package/skills/analyze/SKILL.md +0 -2
  311. package/skills/ask-claude/SKILL.md +5 -3
  312. package/skills/ask-gemini/SKILL.md +5 -3
  313. package/skills/autopilot/SKILL.md +2 -2
  314. package/skills/code-review/SKILL.md +1 -3
  315. package/skills/deep-interview/SKILL.md +5 -7
  316. package/skills/doctor/SKILL.md +2 -2
  317. package/skills/ecomode/SKILL.md +105 -1
  318. package/skills/frontend-ui-ux/SKILL.md +4 -26
  319. package/skills/git-master/SKILL.md +2 -4
  320. package/skills/omx-setup/SKILL.md +3 -3
  321. package/skills/pipeline/SKILL.md +3 -3
  322. package/skills/plan/SKILL.md +3 -6
  323. package/skills/ralph/SKILL.md +9 -10
  324. package/skills/swarm/SKILL.md +5 -3
  325. package/skills/tdd/SKILL.md +95 -1
  326. package/skills/ultragoal/SKILL.md +36 -3
  327. package/skills/ultraqa/SKILL.md +21 -24
  328. package/skills/ultrawork/SKILL.md +8 -8
  329. package/skills/web-clone/SKILL.md +348 -1
  330. package/skills/wiki/SKILL.md +13 -13
  331. package/src/scripts/__tests__/codex-native-hook.test.ts +389 -24
  332. package/src/scripts/__tests__/notify-dispatcher.test.ts +153 -0
  333. package/src/scripts/codex-native-hook.ts +168 -64
  334. package/src/scripts/codex-native-pre-post.ts +4 -1
  335. package/src/scripts/notify-dispatcher.ts +113 -0
  336. package/src/scripts/notify-fallback-watcher.ts +6 -2
  337. package/src/scripts/notify-hook/ralph-session-resume.ts +117 -8
  338. package/src/scripts/notify-hook/state-io.ts +4 -2
  339. package/src/scripts/notify-hook/visual-verdict.ts +3 -3
  340. package/src/scripts/notify-hook.ts +119 -1
@@ -1,10 +1,11 @@
1
- import { afterEach, beforeEach, describe, it } from 'node:test';
1
+ import { afterEach, beforeEach, describe, it, mock } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
- import { existsSync } from 'node:fs';
4
+ import fs, { existsSync } from 'node:fs';
5
5
  import { createHash } from 'node:crypto';
6
+ import { syncBuiltinESMExports } from 'node:module';
6
7
  import { tmpdir } from 'node:os';
7
- import { join, relative } from 'node:path';
8
+ import { basename, join, relative } from 'node:path';
8
9
  import { decodeApprovedExecutionQuotedValue, isPlanningComplete, readApprovedExecutionLaunchHint, readApprovedExecutionLaunchHintOutcome, readLatestPlanningArtifacts, readPlanningArtifacts, readTeamDagArtifactResolution, } from '../artifacts.js';
9
10
  import { readTeamDagHandoffForLatestPlan } from '../../team/dag-schema.js';
10
11
  let tempDir;
@@ -56,6 +57,28 @@ async function writeContextPack(slug, prdPath, testSpecPath, roles) {
56
57
  }, null, 2));
57
58
  return packPath;
58
59
  }
60
+ async function writeContextPackWithEntries(slug, prdPath, testSpecPath, entries) {
61
+ const contextDir = join(tempDir, '.omx', 'context');
62
+ await mkdir(contextDir, { recursive: true });
63
+ const packPath = join(tempDir, canonicalContextPackRelativePath(slug));
64
+ const prdContent = await readFile(prdPath, 'utf-8');
65
+ const testSpecContent = await readFile(testSpecPath, 'utf-8');
66
+ await writeFile(packPath, JSON.stringify({
67
+ slug,
68
+ basis: {
69
+ prd: {
70
+ path: relativeToRepo(prdPath),
71
+ sha1: computeGitBlobSha1(prdContent),
72
+ },
73
+ testSpecs: [{
74
+ path: relativeToRepo(testSpecPath),
75
+ sha1: computeGitBlobSha1(testSpecContent),
76
+ }],
77
+ },
78
+ entries,
79
+ }, null, 2));
80
+ return packPath;
81
+ }
59
82
  async function setup() {
60
83
  tempDir = await mkdtemp(join(tmpdir(), 'omx-planning-artifacts-'));
61
84
  }
@@ -66,7 +89,11 @@ async function cleanup() {
66
89
  }
67
90
  describe('planning artifacts', () => {
68
91
  beforeEach(async () => { await setup(); });
69
- afterEach(async () => { await cleanup(); });
92
+ afterEach(async () => {
93
+ mock.restoreAll();
94
+ syncBuiltinESMExports();
95
+ await cleanup();
96
+ });
70
97
  it('round-trips single-quoted approved execution tasks with only escaped apostrophes normalized', () => {
71
98
  const task = String.raw `Fix Bob's regression in C:\\tmp`;
72
99
  assert.equal(decodeApprovedExecutionQuotedValue(encodeApprovedExecutionTask(task, 'single')), task);
@@ -201,6 +228,7 @@ describe('planning artifacts', () => {
201
228
  assert.equal(selection.prdPath, join(plansDir, 'prd-20260427T153100Z-alpha.md'));
202
229
  assert.deepEqual(selection.testSpecPaths, []);
203
230
  assert.equal(selection.contextPackStatus, 'missing-baseline');
231
+ assert.equal(selection.contextPackRoleRefs, null);
204
232
  assert.deepEqual(selection.missingRequiredContextPackRoles, []);
205
233
  assert.deepEqual(selection.contextPackIssues, ['Approved plan is missing a matching test spec.']);
206
234
  const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph');
@@ -209,6 +237,7 @@ describe('planning artifacts', () => {
209
237
  throw new Error('expected missing-baseline approved hint outcome');
210
238
  }
211
239
  assert.equal(outcome.hint.contextPackStatus, 'missing-baseline');
240
+ assert.equal(outcome.hint.contextPackRoleRefs, null);
212
241
  assert.deepEqual(outcome.hint.testSpecPaths, []);
213
242
  assert.deepEqual(outcome.hint.contextPackIssues, ['Approved plan is missing a matching test spec.']);
214
243
  assert.equal(readApprovedExecutionLaunchHint(tempDir, 'ralph'), null);
@@ -227,11 +256,13 @@ describe('planning artifacts', () => {
227
256
  const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
228
257
  assert.equal(selection.contextPack, null);
229
258
  assert.equal(selection.contextPackStatus, 'plan-only');
259
+ assert.equal(selection.contextPackRoleRefs, null);
230
260
  assert.deepEqual(selection.missingRequiredContextPackRoles, []);
231
261
  assert.deepEqual(selection.contextPackIssues, []);
232
262
  assert.ok(hint);
233
263
  assert.equal(hint?.contextPack, null);
234
264
  assert.equal(hint?.contextPackStatus, 'plan-only');
265
+ assert.equal(hint?.contextPackRoleRefs, null);
235
266
  assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
236
267
  assert.deepEqual(hint?.contextPackIssues, []);
237
268
  });
@@ -249,10 +280,129 @@ describe('planning artifacts', () => {
249
280
  ].join('\n'));
250
281
  await writeFile(testSpecPath, '# Test Spec\n');
251
282
  const packPath = await writeContextPack('context-ready', prdPath, testSpecPath, ['scope', 'build', 'verify']);
283
+ const selection = readLatestPlanningArtifacts(tempDir);
284
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
285
+ assert.deepEqual(selection.contextPack, { path: packPath });
286
+ assert.equal(selection.contextPackStatus, 'ready');
287
+ assert.deepEqual(selection.contextPackRoleRefs, {
288
+ scope: ['src/scope-0.ts'],
289
+ build: ['src/build-1.ts'],
290
+ verify: ['src/verify-2.ts'],
291
+ });
292
+ assert.ok(hint);
293
+ assert.deepEqual(hint?.contextPack, { path: packPath });
294
+ assert.equal(hint?.contextPackStatus, 'ready');
295
+ assert.deepEqual(hint?.contextPackRoleRefs, {
296
+ scope: ['src/scope-0.ts'],
297
+ build: ['src/build-1.ts'],
298
+ verify: ['src/verify-2.ts'],
299
+ });
300
+ assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
301
+ assert.deepEqual(hint?.contextPackIssues, []);
302
+ });
303
+ it('keeps approved hint role refs unchanged when ready packs carry private entry metadata', async () => {
304
+ const plansDir = join(tempDir, '.omx', 'plans');
305
+ await mkdir(plansDir, { recursive: true });
306
+ const prdPath = join(plansDir, 'prd-context-ready-private.md');
307
+ const testSpecPath = join(plansDir, 'test-spec-context-ready-private.md');
308
+ await writeFile(prdPath, [
309
+ '# PRD',
310
+ '',
311
+ buildContextPackOutcome(canonicalContextPackRelativePath('context-ready-private')),
312
+ '',
313
+ 'Launch via omx ralph "Execute context-ready private handoff"',
314
+ ].join('\n'));
315
+ await writeFile(testSpecPath, '# Test Spec\n');
316
+ const packPath = await writeContextPackWithEntries('context-ready-private', prdPath, testSpecPath, [
317
+ {
318
+ path: 'src/scope-ready.ts',
319
+ roles: ['scope'],
320
+ },
321
+ {
322
+ path: 'src/build-ready.ts',
323
+ roles: ['build'],
324
+ label: 'Build Focus',
325
+ tags: ['runtime', 'build'],
326
+ selector: { type: 'heading', value: ' ## Build Focus ', maxWords: 120 },
327
+ relationPath: [
328
+ { tag: 'Plan', target: ' context-ready-private ' },
329
+ { tag: 'Implements', target: ' src/build-ready.ts#build-focus ' },
330
+ ],
331
+ },
332
+ {
333
+ path: 'src/verify-ready.ts',
334
+ roles: ['verify'],
335
+ relationPath: [
336
+ { tag: 'Plan', target: ' context-ready-private ' },
337
+ { tag: 'Verifies', target: ' src/verify-ready.ts ' },
338
+ ],
339
+ },
340
+ ]);
341
+ const selection = readLatestPlanningArtifacts(tempDir);
342
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
343
+ assert.deepEqual(selection.contextPack, { path: packPath });
344
+ assert.equal(selection.contextPackStatus, 'ready');
345
+ assert.deepEqual(selection.contextPackRoleRefs, {
346
+ scope: ['src/scope-ready.ts'],
347
+ build: ['src/build-ready.ts'],
348
+ verify: ['src/verify-ready.ts'],
349
+ });
350
+ assert.ok(hint);
351
+ assert.deepEqual(hint?.contextPack, { path: packPath });
352
+ assert.equal(hint?.contextPackStatus, 'ready');
353
+ assert.deepEqual(hint?.contextPackRoleRefs, {
354
+ scope: ['src/scope-ready.ts'],
355
+ build: ['src/build-ready.ts'],
356
+ verify: ['src/verify-ready.ts'],
357
+ });
358
+ assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
359
+ assert.deepEqual(hint?.contextPackIssues, []);
360
+ });
361
+ it('keeps approved hint role refs unchanged when ready packs carry malformed private metadata', async () => {
362
+ const plansDir = join(tempDir, '.omx', 'plans');
363
+ await mkdir(plansDir, { recursive: true });
364
+ const prdPath = join(plansDir, 'prd-context-ready-private-counterfactual.md');
365
+ const testSpecPath = join(plansDir, 'test-spec-context-ready-private-counterfactual.md');
366
+ await writeFile(prdPath, [
367
+ '# PRD',
368
+ '',
369
+ buildContextPackOutcome(canonicalContextPackRelativePath('context-ready-private-counterfactual')),
370
+ '',
371
+ 'Launch via omx ralph "Execute context-ready private counterfactual handoff"',
372
+ ].join('\n'));
373
+ await writeFile(testSpecPath, '# Test Spec\n');
374
+ const packPath = await writeContextPackWithEntries('context-ready-private-counterfactual', prdPath, testSpecPath, [
375
+ {
376
+ path: 'src/scope-counterfactual.ts',
377
+ roles: ['scope'],
378
+ },
379
+ {
380
+ path: 'src/build-counterfactual.ts',
381
+ roles: ['build'],
382
+ selector: { type: 'heading', value: 'Build Focus', maxWords: 20 },
383
+ },
384
+ {
385
+ path: 'src/verify-counterfactual.ts',
386
+ roles: ['verify'],
387
+ },
388
+ ]);
389
+ const selection = readLatestPlanningArtifacts(tempDir);
252
390
  const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
391
+ assert.deepEqual(selection.contextPack, { path: packPath });
392
+ assert.equal(selection.contextPackStatus, 'ready');
393
+ assert.deepEqual(selection.contextPackRoleRefs, {
394
+ scope: ['src/scope-counterfactual.ts'],
395
+ build: ['src/build-counterfactual.ts'],
396
+ verify: ['src/verify-counterfactual.ts'],
397
+ });
253
398
  assert.ok(hint);
254
399
  assert.deepEqual(hint?.contextPack, { path: packPath });
255
400
  assert.equal(hint?.contextPackStatus, 'ready');
401
+ assert.deepEqual(hint?.contextPackRoleRefs, {
402
+ scope: ['src/scope-counterfactual.ts'],
403
+ build: ['src/build-counterfactual.ts'],
404
+ verify: ['src/verify-counterfactual.ts'],
405
+ });
256
406
  assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
257
407
  assert.deepEqual(hint?.contextPackIssues, []);
258
408
  });
@@ -274,6 +424,7 @@ describe('planning artifacts', () => {
274
424
  const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
275
425
  assert.ok(hint);
276
426
  assert.equal(hint?.contextPackStatus, 'invalid');
427
+ assert.equal(hint?.contextPackRoleRefs, null);
277
428
  assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
278
429
  assert.ok(hint?.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
279
430
  });
@@ -295,6 +446,7 @@ describe('planning artifacts', () => {
295
446
  const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
296
447
  assert.ok(hint);
297
448
  assert.equal(hint?.contextPackStatus, 'invalid');
449
+ assert.equal(hint?.contextPackRoleRefs, null);
298
450
  assert.deepEqual(hint?.missingRequiredContextPackRoles, ['build', 'verify']);
299
451
  assert.ok(hint?.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
300
452
  });
@@ -418,6 +570,49 @@ describe('planning artifacts', () => {
418
570
  assert.equal(hint?.sourcePath, alphaPrdPath);
419
571
  assert.deepEqual(hint?.testSpecPaths, [join(plansDir, 'test-spec-alpha.md')]);
420
572
  });
573
+ it('binds approved launch hints through canonical-equivalent requested PRD aliases', async () => {
574
+ const plansDir = join(tempDir, '.omx', 'plans');
575
+ await mkdir(plansDir, { recursive: true });
576
+ const alphaPrdPath = join(plansDir, 'prd-alpha.md');
577
+ await writeFile(alphaPrdPath, '# Alpha\n\nLaunch via omx ralph "Execute alpha"\n');
578
+ await writeFile(join(plansDir, 'test-spec-alpha.md'), '# Alpha Test Spec\n');
579
+ await writeFile(join(plansDir, 'prd-zeta.md'), '# Zeta\n\nLaunch via omx ralph "Execute zeta"\n');
580
+ await writeFile(join(plansDir, 'test-spec-zeta.md'), '# Zeta Test Spec\n');
581
+ const aliases = [
582
+ '.omx/plans/prd-alpha.md',
583
+ 'prd-alpha.md',
584
+ alphaPrdPath,
585
+ '.omx/plans/../plans/prd-alpha.md',
586
+ '../plans/prd-alpha.md',
587
+ join(tempDir, '.omx', 'plans', '..', 'plans', 'prd-alpha.md'),
588
+ ];
589
+ for (const prdPath of aliases) {
590
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { prdPath });
591
+ assert.equal(outcome.status, 'resolved');
592
+ if (outcome.status !== 'resolved') {
593
+ throw new Error(`expected resolved hint for alias ${prdPath}`);
594
+ }
595
+ assert.equal(outcome.hint.task, 'Execute alpha');
596
+ assert.equal(outcome.hint.sourcePath, alphaPrdPath);
597
+ assert.deepEqual(outcome.hint.testSpecPaths, [join(plansDir, 'test-spec-alpha.md')]);
598
+ }
599
+ });
600
+ it('does not bind requested PRD aliases that do not resolve to a discovered canonical PRD', async () => {
601
+ const plansDir = join(tempDir, '.omx', 'plans');
602
+ await mkdir(plansDir, { recursive: true });
603
+ await writeFile(join(plansDir, 'prd-alpha.md'), '# Alpha\n\nLaunch via omx ralph "Execute alpha"\n');
604
+ await writeFile(join(plansDir, 'test-spec-alpha.md'), '# Alpha Test Spec\n');
605
+ const rejectedAliases = [
606
+ '.omx/plans/prd-missing.md',
607
+ '../prd-alpha.md',
608
+ join('..', basename(tempDir), '.omx', 'plans', 'prd-alpha.md'),
609
+ join(tempDir, '.omx', 'prd-alpha.md'),
610
+ relative(process.cwd(), join(plansDir, 'prd-alpha.md')),
611
+ ];
612
+ for (const prdPath of rejectedAliases) {
613
+ assert.equal(readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { prdPath }).status, 'absent');
614
+ }
615
+ });
421
616
  it('honors the requested Ralph task when a single plan lists multiple Ralph launch hints', async () => {
422
617
  const plansDir = join(tempDir, '.omx', 'plans');
423
618
  await mkdir(plansDir, { recursive: true });
@@ -433,6 +628,30 @@ describe('planning artifacts', () => {
433
628
  assert.equal(hint?.task, 'Execute alpha');
434
629
  assert.equal(hint?.command, 'omx ralph "Execute alpha"');
435
630
  });
631
+ it('reuses one planning artifact scan while task lookup checks older same-lineage PRDs', async () => {
632
+ const plansDir = join(tempDir, '.omx', 'plans');
633
+ const task = 'Execute cached planning artifact lineage lookup';
634
+ await mkdir(plansDir, { recursive: true });
635
+ await writeFile(join(plansDir, 'prd-alpha.md'), `# Alpha\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
636
+ await writeFile(join(plansDir, 'test-spec-alpha.md'), '# Alpha Test Spec\n');
637
+ await writeFile(join(plansDir, 'prd-beta.md'), `# Beta\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
638
+ await writeFile(join(plansDir, 'prd-gamma.md'), `# Gamma\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
639
+ await writeFile(join(plansDir, 'prd-zeta.md'), `# Zeta\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
640
+ let readdirCount = 0;
641
+ const originalReaddirSync = fs.readdirSync;
642
+ mock.method(fs, 'readdirSync', ((...args) => {
643
+ readdirCount += 1;
644
+ return Reflect.apply(originalReaddirSync, fs, args);
645
+ }));
646
+ syncBuiltinESMExports();
647
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
648
+ assert.equal(outcome.status, 'resolved');
649
+ if (outcome.status !== 'resolved') {
650
+ return;
651
+ }
652
+ assert.equal(outcome.hint.sourcePath, join(plansDir, 'prd-alpha.md'));
653
+ assert.equal(readdirCount, 2);
654
+ });
436
655
  it('fails closed for bare Ralph lookups when a single plan lists multiple Ralph launch hints', async () => {
437
656
  const plansDir = join(tempDir, '.omx', 'plans');
438
657
  await mkdir(plansDir, { recursive: true });
@@ -446,6 +665,335 @@ describe('planning artifacts', () => {
446
665
  const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
447
666
  assert.equal(hint, null);
448
667
  });
668
+ it('ignores Ralph launch hints that appear only inside indented code blocks', async () => {
669
+ const plansDir = join(tempDir, '.omx', 'plans');
670
+ const task = 'Execute hidden indented ralph plan';
671
+ await mkdir(plansDir, { recursive: true });
672
+ await writeFile(join(plansDir, 'prd-hidden-indented-ralph.md'), [
673
+ '# PRD',
674
+ '',
675
+ ` omx ralph ${JSON.stringify(task)}`,
676
+ ].join('\n'));
677
+ await writeFile(join(plansDir, 'test-spec-hidden-indented-ralph.md'), '# Test Spec\n');
678
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
679
+ assert.equal(outcome.status, 'absent');
680
+ });
681
+ it('resolves Ralph launch hints wrapped across visible markdown lines', async () => {
682
+ const plansDir = join(tempDir, '.omx', 'plans');
683
+ const task = 'Execute wrapped visible ralph plan';
684
+ const command = `omx ralph ${JSON.stringify(task)}`;
685
+ await mkdir(plansDir, { recursive: true });
686
+ await writeFile(join(plansDir, 'prd-wrapped-visible-ralph.md'), [
687
+ '# PRD',
688
+ '',
689
+ 'Launch via omx ralph',
690
+ JSON.stringify(task),
691
+ ].join('\n'));
692
+ await writeFile(join(plansDir, 'test-spec-wrapped-visible-ralph.md'), '# Test Spec\n');
693
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph', { task });
694
+ assert.ok(hint);
695
+ assert.equal(hint?.task, task);
696
+ assert.equal(hint?.command, command);
697
+ assert.equal(hint?.contextPackStatus, 'plan-only');
698
+ const exactHint = readApprovedExecutionLaunchHint(tempDir, 'ralph', { command });
699
+ assert.ok(exactHint);
700
+ assert.equal(exactHint?.command, command);
701
+ });
702
+ it('does not let Ralph launch hints span hidden markdown gaps', async () => {
703
+ const plansDir = join(tempDir, '.omx', 'plans');
704
+ await mkdir(plansDir, { recursive: true });
705
+ const scenarios = [
706
+ {
707
+ name: 'fenced',
708
+ buildHiddenLines: (task) => [
709
+ '```md',
710
+ JSON.stringify(task),
711
+ '```',
712
+ ],
713
+ },
714
+ {
715
+ name: 'commented',
716
+ buildHiddenLines: (task) => [
717
+ '<!--',
718
+ JSON.stringify(task),
719
+ '-->',
720
+ ],
721
+ },
722
+ {
723
+ name: 'indented',
724
+ buildHiddenLines: (task) => [
725
+ ` ${JSON.stringify(task)}`,
726
+ ],
727
+ },
728
+ ];
729
+ for (const scenario of scenarios) {
730
+ const task = `Execute hidden-gap ${scenario.name} ralph plan`;
731
+ await writeFile(join(plansDir, `prd-hidden-gap-${scenario.name}-ralph.md`), [
732
+ '# PRD',
733
+ '',
734
+ 'Launch via omx ralph',
735
+ ...scenario.buildHiddenLines(task),
736
+ ].join('\n'));
737
+ await writeFile(join(plansDir, `test-spec-hidden-gap-${scenario.name}-ralph.md`), '# Test Spec\n');
738
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
739
+ assert.equal(outcome.status, 'absent', scenario.name);
740
+ }
741
+ });
742
+ it('resolves Team launch hints wrapped across visible markdown lines', async () => {
743
+ const plansDir = join(tempDir, '.omx', 'plans');
744
+ const task = 'Execute wrapped visible team plan';
745
+ const command = `omx team 2:executor ${JSON.stringify(task)}`;
746
+ await mkdir(plansDir, { recursive: true });
747
+ await writeFile(join(plansDir, 'prd-wrapped-visible-team.md'), [
748
+ '# PRD',
749
+ '',
750
+ 'Launch via omx team',
751
+ '2:executor',
752
+ JSON.stringify(task),
753
+ ].join('\n'));
754
+ await writeFile(join(plansDir, 'test-spec-wrapped-visible-team.md'), '# Test Spec\n');
755
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'team', { task });
756
+ assert.ok(hint);
757
+ assert.equal(hint?.task, task);
758
+ assert.equal(hint?.workerCount, 2);
759
+ assert.equal(hint?.agentType, 'executor');
760
+ assert.equal(hint?.command, command);
761
+ assert.equal(hint?.contextPackStatus, 'plan-only');
762
+ const exactHint = readApprovedExecutionLaunchHint(tempDir, 'team', { command });
763
+ assert.ok(exactHint);
764
+ assert.equal(exactHint?.command, command);
765
+ });
766
+ it('normalizes wrapped linked-Ralph team launch hints for exact command matching', async () => {
767
+ const plansDir = join(tempDir, '.omx', 'plans');
768
+ const task = 'Execute wrapped linked ralph team plan';
769
+ const command = `$team ralph 5:debugger ${JSON.stringify(task)}`;
770
+ await mkdir(plansDir, { recursive: true });
771
+ await writeFile(join(plansDir, 'prd-wrapped-visible-team-linked-ralph.md'), [
772
+ '# PRD',
773
+ '',
774
+ 'Launch via $team',
775
+ 'ralph',
776
+ '5:debugger',
777
+ JSON.stringify(task),
778
+ ].join('\n'));
779
+ await writeFile(join(plansDir, 'test-spec-wrapped-visible-team-linked-ralph.md'), '# Test Spec\n');
780
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'team', { command });
781
+ assert.ok(hint);
782
+ assert.equal(hint?.command, command);
783
+ assert.equal(hint?.workerCount, 5);
784
+ assert.equal(hint?.agentType, 'debugger');
785
+ assert.equal(hint?.linkedRalph, true);
786
+ });
787
+ it('keeps exact-command normalization bounded to visible whitespace-only variants', async () => {
788
+ const plansDir = join(tempDir, '.omx', 'plans');
789
+ await mkdir(plansDir, { recursive: true });
790
+ const task = 'Execute exact-command normalization state table plan';
791
+ const command = `omx team 2:executor ${JSON.stringify(task)}`;
792
+ const cases = [
793
+ {
794
+ name: 'visible-whitespace-only-variant',
795
+ prdPath: 'prd-exact-command-visible.md',
796
+ content: [
797
+ '# PRD',
798
+ '',
799
+ 'Launch via omx team',
800
+ '2:executor',
801
+ JSON.stringify(task),
802
+ ].join('\n'),
803
+ expectedStatus: 'resolved',
804
+ },
805
+ {
806
+ name: 'hidden-gap-counterfactual',
807
+ prdPath: 'prd-exact-command-hidden-gap.md',
808
+ content: [
809
+ '# PRD',
810
+ '',
811
+ 'Launch via omx team',
812
+ '```md',
813
+ 'hidden',
814
+ '```',
815
+ '2:executor',
816
+ JSON.stringify(task),
817
+ ].join('\n'),
818
+ expectedStatus: 'absent',
819
+ },
820
+ {
821
+ name: 'formatting-only-duplicate',
822
+ prdPath: 'prd-exact-command-duplicate.md',
823
+ content: [
824
+ '# PRD',
825
+ '',
826
+ `Launch via ${command}`,
827
+ 'Launch via omx team',
828
+ '2:executor',
829
+ JSON.stringify(task),
830
+ ].join('\n'),
831
+ expectedStatus: 'ambiguous',
832
+ },
833
+ ];
834
+ for (const scenario of cases) {
835
+ await writeFile(join(plansDir, scenario.prdPath), scenario.content);
836
+ await writeFile(join(plansDir, scenario.prdPath.replace(/^prd-/, 'test-spec-')), '# Test Spec\n');
837
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', { command });
838
+ assert.equal(outcome.status, scenario.expectedStatus, scenario.name);
839
+ await rm(join(plansDir, scenario.prdPath), { force: true });
840
+ await rm(join(plansDir, scenario.prdPath.replace(/^prd-/, 'test-spec-')), { force: true });
841
+ }
842
+ });
843
+ it('does not normalize whitespace that changes the quoted task payload', async () => {
844
+ const plansDir = join(tempDir, '.omx', 'plans');
845
+ await mkdir(plansDir, { recursive: true });
846
+ const task = 'Execute embedded\nnewline task';
847
+ const exactCommand = [
848
+ 'omx ralph "Execute embedded',
849
+ 'newline task"',
850
+ ].join('\n');
851
+ const collapsedCommand = 'omx ralph "Execute embedded newline task"';
852
+ await writeFile(join(plansDir, 'prd-exact-command-embedded-newline-task.md'), [
853
+ '# PRD',
854
+ '',
855
+ 'Launch via omx ralph "Execute embedded',
856
+ 'newline task"',
857
+ ].join('\n'));
858
+ await writeFile(join(plansDir, 'test-spec-exact-command-embedded-newline-task.md'), '# Test Spec\n');
859
+ const exactOutcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { command: exactCommand });
860
+ assert.equal(exactOutcome.status, 'resolved');
861
+ if (exactOutcome.status !== 'resolved') {
862
+ return;
863
+ }
864
+ assert.equal(exactOutcome.hint.command, exactCommand);
865
+ assert.equal(exactOutcome.hint.task, task);
866
+ assert.equal(readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { command: collapsedCommand }).status, 'absent');
867
+ });
868
+ it('ignores Team launch hints inside fenced code blocks', async () => {
869
+ const plansDir = join(tempDir, '.omx', 'plans');
870
+ const task = 'Execute approved issue 1314 fenced team plan';
871
+ await mkdir(plansDir, { recursive: true });
872
+ await writeFile(join(plansDir, 'prd-issue-1314-fenced-team.md'), [
873
+ '# PRD',
874
+ '',
875
+ '```sh',
876
+ `omx team 5:reviewer ${JSON.stringify(task)}`,
877
+ '```',
878
+ '',
879
+ `Launch via omx team 2:executor ${JSON.stringify(task)}`,
880
+ ].join('\n'));
881
+ await writeFile(join(plansDir, 'test-spec-issue-1314-fenced-team.md'), '# Test Spec\n');
882
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'team');
883
+ assert.ok(hint);
884
+ assert.equal(hint?.task, task);
885
+ assert.equal(hint?.workerCount, 2);
886
+ assert.equal(hint?.agentType, 'executor');
887
+ assert.equal(hint?.command, `omx team 2:executor ${JSON.stringify(task)}`);
888
+ assert.equal(hint?.contextPackStatus, 'plan-only');
889
+ });
890
+ it('ignores Team launch hints that appear only inside fenced code blocks', async () => {
891
+ const plansDir = join(tempDir, '.omx', 'plans');
892
+ const task = 'Execute hidden fenced team plan';
893
+ await mkdir(plansDir, { recursive: true });
894
+ await writeFile(join(plansDir, 'prd-hidden-fenced-team.md'), [
895
+ '# PRD',
896
+ '',
897
+ '```sh',
898
+ `omx team 2:executor ${JSON.stringify(task)}`,
899
+ '```',
900
+ ].join('\n'));
901
+ await writeFile(join(plansDir, 'test-spec-hidden-fenced-team.md'), '# Test Spec\n');
902
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', { task });
903
+ assert.equal(outcome.status, 'absent');
904
+ });
905
+ it('ignores adversarial hidden Team launch hints that try to break out with deceptive fence lines', async () => {
906
+ const plansDir = join(tempDir, '.omx', 'plans');
907
+ const task = 'Execute adversarial fenced team plan';
908
+ await mkdir(plansDir, { recursive: true });
909
+ await writeFile(join(plansDir, 'prd-adversarial-fenced-team.md'), [
910
+ '# PRD',
911
+ '',
912
+ '```md',
913
+ `omx team 9:reviewer ${JSON.stringify(task)}`,
914
+ ' ```',
915
+ '```still-open',
916
+ '~~~',
917
+ `omx team 7:critic ${JSON.stringify(task)}`,
918
+ '```',
919
+ '',
920
+ `Launch via omx team 2:executor ${JSON.stringify(task)}`,
921
+ ].join('\n'));
922
+ await writeFile(join(plansDir, 'test-spec-adversarial-fenced-team.md'), '# Test Spec\n');
923
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'team');
924
+ assert.ok(hint);
925
+ assert.equal(hint?.task, task);
926
+ assert.equal(hint?.workerCount, 2);
927
+ assert.equal(hint?.agentType, 'executor');
928
+ assert.equal(hint?.command, `omx team 2:executor ${JSON.stringify(task)}`);
929
+ assert.equal(hint?.contextPackStatus, 'plan-only');
930
+ });
931
+ it('ignores Team launch hints inside nested commented blocks with inner fence lookalikes', async () => {
932
+ const plansDir = join(tempDir, '.omx', 'plans');
933
+ const task = 'Execute commented nested team plan';
934
+ await mkdir(plansDir, { recursive: true });
935
+ await writeFile(join(plansDir, 'prd-commented-nested-team.md'), [
936
+ '# PRD',
937
+ '',
938
+ '<!--',
939
+ `Launch via omx team 9:reviewer ${JSON.stringify(task)}`,
940
+ '<!--',
941
+ `omx team 7:critic ${JSON.stringify(task)}`,
942
+ '```md',
943
+ `Launch via omx team 6:debugger ${JSON.stringify(task)}`,
944
+ '-->',
945
+ '~~~',
946
+ '-->',
947
+ '',
948
+ `Launch via omx team 2:executor ${JSON.stringify(task)}`,
949
+ ].join('\n'));
950
+ await writeFile(join(plansDir, 'test-spec-commented-nested-team.md'), '# Test Spec\n');
951
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'team');
952
+ assert.ok(hint);
953
+ assert.equal(hint?.task, task);
954
+ assert.equal(hint?.workerCount, 2);
955
+ assert.equal(hint?.agentType, 'executor');
956
+ assert.equal(hint?.command, `omx team 2:executor ${JSON.stringify(task)}`);
957
+ assert.equal(hint?.contextPackStatus, 'plan-only');
958
+ });
959
+ it('ignores Ralph launch hints inside indented code blocks', async () => {
960
+ const plansDir = join(tempDir, '.omx', 'plans');
961
+ const task = 'Execute approved issue 1314 indented ralph plan';
962
+ await mkdir(plansDir, { recursive: true });
963
+ await writeFile(join(plansDir, 'prd-issue-1314-indented-ralph.md'), [
964
+ '# PRD',
965
+ '',
966
+ ` omx ralph ${JSON.stringify(task)}`,
967
+ '',
968
+ `Launch via omx ralph ${JSON.stringify(task)}`,
969
+ ].join('\n'));
970
+ await writeFile(join(plansDir, 'test-spec-issue-1314-indented-ralph.md'), '# Test Spec\n');
971
+ const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
972
+ assert.ok(hint);
973
+ assert.equal(hint?.task, task);
974
+ assert.equal(hint?.command, `omx ralph ${JSON.stringify(task)}`);
975
+ assert.equal(hint?.contextPackStatus, 'plan-only');
976
+ });
977
+ it('ignores Ralph launch hints that appear only inside nested commented blocks', async () => {
978
+ const plansDir = join(tempDir, '.omx', 'plans');
979
+ const task = 'Execute hidden commented ralph plan';
980
+ await mkdir(plansDir, { recursive: true });
981
+ await writeFile(join(plansDir, 'prd-hidden-commented-ralph.md'), [
982
+ '# PRD',
983
+ '',
984
+ '<!--',
985
+ `Launch via omx ralph ${JSON.stringify(task)}`,
986
+ '<!--',
987
+ `omx ralph ${JSON.stringify(task)}`,
988
+ '```md',
989
+ `Launch via omx ralph ${JSON.stringify(task)}`,
990
+ '-->',
991
+ '-->',
992
+ ].join('\n'));
993
+ await writeFile(join(plansDir, 'test-spec-hidden-commented-ralph.md'), '# Test Spec\n');
994
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
995
+ assert.equal(outcome.status, 'absent');
996
+ });
449
997
  it('honors the requested team task when a single plan lists multiple team launch hints', async () => {
450
998
  const plansDir = join(tempDir, '.omx', 'plans');
451
999
  await mkdir(plansDir, { recursive: true });
@@ -480,6 +1028,51 @@ describe('planning artifacts', () => {
480
1028
  });
481
1029
  assert.equal(hint, null);
482
1030
  });
1031
+ it('uses the requested team launch signature to disambiguate same-task launch hints', async () => {
1032
+ const plansDir = join(tempDir, '.omx', 'plans');
1033
+ const sharedTask = 'Execute shared team handoff';
1034
+ await mkdir(plansDir, { recursive: true });
1035
+ await writeFile(join(plansDir, 'prd-issue-910-signature.md'), [
1036
+ '# PRD',
1037
+ '',
1038
+ `Launch via omx team 2:executor ${JSON.stringify(sharedTask)}`,
1039
+ `Launch via $team ralph 5:debugger ${JSON.stringify(sharedTask)}`,
1040
+ ].join('\n'));
1041
+ await writeFile(join(plansDir, 'test-spec-issue-910-signature.md'), '# Test Spec\n');
1042
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', {
1043
+ task: sharedTask,
1044
+ workerCount: 2,
1045
+ agentType: 'executor',
1046
+ linkedRalph: false,
1047
+ });
1048
+ assert.equal(outcome.status, 'resolved');
1049
+ if (outcome.status !== 'resolved') {
1050
+ throw new Error('expected a resolved team launch-hint outcome');
1051
+ }
1052
+ assert.equal(outcome.hint.command, `omx team 2:executor ${JSON.stringify(sharedTask)}`);
1053
+ assert.equal(outcome.hint.workerCount, 2);
1054
+ assert.equal(outcome.hint.agentType, 'executor');
1055
+ assert.equal(outcome.hint.linkedRalph, false);
1056
+ });
1057
+ it('keeps same-task team launch-hint selection ambiguous when the full signature repeats', async () => {
1058
+ const plansDir = join(tempDir, '.omx', 'plans');
1059
+ const sharedTask = 'Execute shared duplicate team handoff';
1060
+ await mkdir(plansDir, { recursive: true });
1061
+ await writeFile(join(plansDir, 'prd-issue-910-same-signature.md'), [
1062
+ '# PRD',
1063
+ '',
1064
+ `Launch via omx team 2:executor ${JSON.stringify(sharedTask)}`,
1065
+ `Launch via $team 2:executor ${JSON.stringify(sharedTask)}`,
1066
+ ].join('\n'));
1067
+ await writeFile(join(plansDir, 'test-spec-issue-910-same-signature.md'), '# Test Spec\n');
1068
+ const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', {
1069
+ task: sharedTask,
1070
+ workerCount: 2,
1071
+ agentType: 'executor',
1072
+ linkedRalph: false,
1073
+ });
1074
+ assert.equal(outcome.status, 'ambiguous');
1075
+ });
483
1076
  it('rehydrates the exact team launch hint by command when one PRD repeats the same task', async () => {
484
1077
  const plansDir = join(tempDir, '.omx', 'plans');
485
1078
  const sharedTask = 'Ship feature';