oh-my-codex 0.20.1 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (654) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +10 -1
  4. package/dist/adapt/__tests__/hermes.test.js.map +1 -1
  5. package/dist/adapt/contracts.d.ts.map +1 -1
  6. package/dist/agents/native-config.js.map +1 -1
  7. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  8. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  9. package/dist/auth/__tests__/hotswap.test.js +183 -0
  10. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  11. package/dist/auth/hotswap.d.ts.map +1 -1
  12. package/dist/auth/hotswap.js +17 -4
  13. package/dist/auth/hotswap.js.map +1 -1
  14. package/dist/auth/paths.js.map +1 -1
  15. package/dist/auth/storage.js.map +1 -1
  16. package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
  17. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  18. package/dist/autopilot/completion-gate.js.map +1 -1
  19. package/dist/autopilot/fsm.d.ts +1 -1
  20. package/dist/autopilot/fsm.d.ts.map +1 -1
  21. package/dist/autoresearch/goal.js.map +1 -1
  22. package/dist/autoresearch/runtime.js.map +1 -1
  23. package/dist/capabilities/lockfile.js.map +1 -1
  24. package/dist/catalog/reader.js.map +1 -1
  25. package/dist/catalog/skill-mirror.js.map +1 -1
  26. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  27. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  28. package/dist/cli/__tests__/agents.test.js.map +1 -1
  29. package/dist/cli/__tests__/api.test.js.map +1 -1
  30. package/dist/cli/__tests__/ask.test.js.map +1 -1
  31. package/dist/cli/__tests__/auth.test.js.map +1 -1
  32. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  33. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  34. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  35. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  36. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
  38. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  40. package/dist/cli/__tests__/doctor-warning-copy.test.js +650 -15
  41. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  42. package/dist/cli/__tests__/exec.test.js.map +1 -1
  43. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  44. package/dist/cli/__tests__/index.test.js +216 -11
  45. package/dist/cli/__tests__/index.test.js.map +1 -1
  46. package/dist/cli/__tests__/launch-fallback.test.js +49 -3
  47. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  48. package/dist/cli/__tests__/mcp-parity.test.js +84 -12
  49. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  50. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  51. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  52. package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
  53. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  54. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  55. package/dist/cli/__tests__/question.test.js.map +1 -1
  56. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  57. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  58. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  59. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  60. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  61. package/dist/cli/__tests__/ralph.test.js +8 -3
  62. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  63. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  64. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  65. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
  66. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  67. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  68. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  69. package/dist/cli/__tests__/ralplan.test.js +129 -0
  70. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  71. package/dist/cli/__tests__/resume.test.js +6 -1
  72. package/dist/cli/__tests__/resume.test.js.map +1 -1
  73. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  74. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  75. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  76. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  77. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  78. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  79. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  80. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
  81. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  82. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  83. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  84. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  85. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  86. package/dist/cli/__tests__/setup-install-mode.test.js +2261 -14
  87. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  88. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  89. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  90. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  91. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  92. package/dist/cli/__tests__/setup-refresh.test.js +33 -2
  93. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  94. package/dist/cli/__tests__/setup-scope.test.js +46 -3
  95. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  96. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  97. package/dist/cli/__tests__/state.test.js +37 -0
  98. package/dist/cli/__tests__/state.test.js.map +1 -1
  99. package/dist/cli/__tests__/team.test.js.map +1 -1
  100. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  101. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  102. package/dist/cli/__tests__/uninstall.test.js +1771 -24
  103. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  104. package/dist/cli/__tests__/update.test.js +58 -0
  105. package/dist/cli/__tests__/update.test.js.map +1 -1
  106. package/dist/cli/__tests__/url.test.js.map +1 -1
  107. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  108. package/dist/cli/adapt.js.map +1 -1
  109. package/dist/cli/agents-init.js.map +1 -1
  110. package/dist/cli/agents.js.map +1 -1
  111. package/dist/cli/api.js.map +1 -1
  112. package/dist/cli/ask.d.ts +1 -1
  113. package/dist/cli/ask.d.ts.map +1 -1
  114. package/dist/cli/ask.js.map +1 -1
  115. package/dist/cli/auth.js.map +1 -1
  116. package/dist/cli/autoresearch-guided.js.map +1 -1
  117. package/dist/cli/autoresearch-intake.js.map +1 -1
  118. package/dist/cli/capabilities.js.map +1 -1
  119. package/dist/cli/cleanup.js.map +1 -1
  120. package/dist/cli/codex-feature-probe.js.map +1 -1
  121. package/dist/cli/codex-home.js.map +1 -1
  122. package/dist/cli/doctor.d.ts +18 -0
  123. package/dist/cli/doctor.d.ts.map +1 -1
  124. package/dist/cli/doctor.js +594 -130
  125. package/dist/cli/doctor.js.map +1 -1
  126. package/dist/cli/explore.js.map +1 -1
  127. package/dist/cli/index.d.ts +11 -3
  128. package/dist/cli/index.d.ts.map +1 -1
  129. package/dist/cli/index.js +108 -20
  130. package/dist/cli/index.js.map +1 -1
  131. package/dist/cli/mcp-serve.js.map +1 -1
  132. package/dist/cli/mission.js.map +1 -1
  133. package/dist/cli/native-assets.js.map +1 -1
  134. package/dist/cli/native-hook-claim-journal.d.ts +18 -0
  135. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  136. package/dist/cli/native-hook-claim-journal.js +239 -0
  137. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  138. package/dist/cli/omx.js.map +1 -1
  139. package/dist/cli/plugin-marketplace.js.map +1 -1
  140. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  141. package/dist/cli/question.js.map +1 -1
  142. package/dist/cli/ralph.d.ts.map +1 -1
  143. package/dist/cli/ralph.js +3 -2
  144. package/dist/cli/ralph.js.map +1 -1
  145. package/dist/cli/ralplan.d.ts +14 -0
  146. package/dist/cli/ralplan.d.ts.map +1 -0
  147. package/dist/cli/ralplan.js +203 -0
  148. package/dist/cli/ralplan.js.map +1 -0
  149. package/dist/cli/setup-preferences.d.ts +11 -0
  150. package/dist/cli/setup-preferences.d.ts.map +1 -1
  151. package/dist/cli/setup-preferences.js +32 -0
  152. package/dist/cli/setup-preferences.js.map +1 -1
  153. package/dist/cli/setup.d.ts +53 -0
  154. package/dist/cli/setup.d.ts.map +1 -1
  155. package/dist/cli/setup.js +1401 -398
  156. package/dist/cli/setup.js.map +1 -1
  157. package/dist/cli/sparkshell.js.map +1 -1
  158. package/dist/cli/star-prompt.js.map +1 -1
  159. package/dist/cli/state.d.ts.map +1 -1
  160. package/dist/cli/state.js +2 -1
  161. package/dist/cli/state.js.map +1 -1
  162. package/dist/cli/team.js.map +1 -1
  163. package/dist/cli/ultragoal.d.ts +5 -1
  164. package/dist/cli/ultragoal.d.ts.map +1 -1
  165. package/dist/cli/ultragoal.js +34 -2
  166. package/dist/cli/ultragoal.js.map +1 -1
  167. package/dist/cli/uninstall.d.ts +8 -0
  168. package/dist/cli/uninstall.d.ts.map +1 -1
  169. package/dist/cli/uninstall.js +944 -95
  170. package/dist/cli/uninstall.js.map +1 -1
  171. package/dist/cli/update.d.ts.map +1 -1
  172. package/dist/cli/update.js +8 -1
  173. package/dist/cli/update.js.map +1 -1
  174. package/dist/cli/url.js.map +1 -1
  175. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  176. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  177. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  178. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  179. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  180. package/dist/config/__tests__/generator-idempotent.test.js +850 -32
  181. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  182. package/dist/config/__tests__/generator-notify.test.js +6 -1
  183. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  184. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  185. package/dist/config/codex-feature-flags.js.map +1 -1
  186. package/dist/config/codex-hooks.d.ts +124 -1
  187. package/dist/config/codex-hooks.d.ts.map +1 -1
  188. package/dist/config/codex-hooks.js +2142 -265
  189. package/dist/config/codex-hooks.js.map +1 -1
  190. package/dist/config/commit-lore-guard.js.map +1 -1
  191. package/dist/config/generator.d.ts +21 -16
  192. package/dist/config/generator.d.ts.map +1 -1
  193. package/dist/config/generator.js +991 -137
  194. package/dist/config/generator.js.map +1 -1
  195. package/dist/config/mcp-registry.js.map +1 -1
  196. package/dist/config/models.d.ts +2 -2
  197. package/dist/config/models.d.ts.map +1 -1
  198. package/dist/config/models.js.map +1 -1
  199. package/dist/config/omx-first-party-mcp.js.map +1 -1
  200. package/dist/config/team-mode.d.ts.map +1 -1
  201. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  202. package/dist/document-refresh/enforcer.js.map +1 -1
  203. package/dist/exec/followup.js.map +1 -1
  204. package/dist/goal-workflows/artifacts.js.map +1 -1
  205. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  206. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  207. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  208. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  209. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  210. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  211. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  212. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  213. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  214. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  215. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  216. package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
  217. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  218. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
  219. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  220. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  221. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
  222. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  223. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  224. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  225. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  226. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  227. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  228. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  229. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  230. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  231. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  232. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  233. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  234. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  235. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  236. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  237. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  238. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
  239. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  240. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  241. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  242. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  243. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  244. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  245. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  246. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  247. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  248. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  249. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  250. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  251. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  252. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  253. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  254. package/dist/hooks/__tests__/session.test.js +532 -11
  255. package/dist/hooks/__tests__/session.test.js.map +1 -1
  256. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  257. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  258. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  259. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  260. package/dist/hooks/agents-overlay.js +5 -5
  261. package/dist/hooks/agents-overlay.js.map +1 -1
  262. package/dist/hooks/code-simplifier/index.js.map +1 -1
  263. package/dist/hooks/explore-routing.js.map +1 -1
  264. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  265. package/dist/hooks/extensibility/events.js.map +1 -1
  266. package/dist/hooks/extensibility/loader.js.map +1 -1
  267. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  268. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  269. package/dist/hooks/keyword-detector.d.ts +33 -7
  270. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  271. package/dist/hooks/keyword-detector.js +2228 -119
  272. package/dist/hooks/keyword-detector.js.map +1 -1
  273. package/dist/hooks/keyword-registry.d.ts +12 -0
  274. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  275. package/dist/hooks/keyword-registry.js +41 -0
  276. package/dist/hooks/keyword-registry.js.map +1 -1
  277. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  278. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  279. package/dist/hooks/prompt-session-provenance.js +180 -0
  280. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  281. package/dist/hooks/session.d.ts +139 -34
  282. package/dist/hooks/session.d.ts.map +1 -1
  283. package/dist/hooks/session.js +1239 -317
  284. package/dist/hooks/session.js.map +1 -1
  285. package/dist/hooks/task-size-detector.js.map +1 -1
  286. package/dist/hud/__tests__/state.test.js.map +1 -1
  287. package/dist/hud/authority.js.map +1 -1
  288. package/dist/hud/constants.js.map +1 -1
  289. package/dist/hud/index.js.map +1 -1
  290. package/dist/hud/reconcile.js.map +1 -1
  291. package/dist/hud/render.js.map +1 -1
  292. package/dist/hud/state.js.map +1 -1
  293. package/dist/hud/tmux.js.map +1 -1
  294. package/dist/leader/__tests__/contract.test.js +103 -1
  295. package/dist/leader/__tests__/contract.test.js.map +1 -1
  296. package/dist/leader/contract.d.ts +26 -5
  297. package/dist/leader/contract.d.ts.map +1 -1
  298. package/dist/leader/contract.js +109 -3
  299. package/dist/leader/contract.js.map +1 -1
  300. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  301. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  302. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  303. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  304. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  305. package/dist/mcp/bootstrap.js.map +1 -1
  306. package/dist/mcp/code-intel-server.d.ts +58 -58
  307. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  308. package/dist/mcp/code-intel-server.js.map +1 -1
  309. package/dist/mcp/hermes-bridge.js.map +1 -1
  310. package/dist/mcp/hermes-server.d.ts +70 -70
  311. package/dist/mcp/hermes-server.d.ts.map +1 -1
  312. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  313. package/dist/mcp/memory-server.d.ts +34 -34
  314. package/dist/mcp/memory-server.d.ts.map +1 -1
  315. package/dist/mcp/memory-server.js.map +1 -1
  316. package/dist/mcp/state-paths.d.ts +14 -0
  317. package/dist/mcp/state-paths.d.ts.map +1 -1
  318. package/dist/mcp/state-paths.js +96 -20
  319. package/dist/mcp/state-paths.js.map +1 -1
  320. package/dist/mcp/state-server.d.ts +47 -47
  321. package/dist/mcp/state-server.d.ts.map +1 -1
  322. package/dist/mcp/trace-server.d.ts +4 -4
  323. package/dist/mcp/trace-server.d.ts.map +1 -1
  324. package/dist/mcp/trace-server.js.map +1 -1
  325. package/dist/mcp/wiki-server.d.ts +23 -23
  326. package/dist/mcp/wiki-server.d.ts.map +1 -1
  327. package/dist/mcp/wiki-server.js.map +1 -1
  328. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  329. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  330. package/dist/modes/base.d.ts.map +1 -1
  331. package/dist/modes/base.js +5 -5
  332. package/dist/modes/base.js.map +1 -1
  333. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  334. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  335. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  336. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  337. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  338. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  339. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  340. package/dist/notifications/http-client.js.map +1 -1
  341. package/dist/notifications/index.d.ts +4 -1
  342. package/dist/notifications/index.d.ts.map +1 -1
  343. package/dist/notifications/index.js +3 -9
  344. package/dist/notifications/index.js.map +1 -1
  345. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  346. package/dist/notifications/reply-listener.d.ts.map +1 -1
  347. package/dist/notifications/reply-listener.js.map +1 -1
  348. package/dist/notifications/session-registry.js.map +1 -1
  349. package/dist/notifications/session-status.js.map +1 -1
  350. package/dist/notifications/temp-contract.js.map +1 -1
  351. package/dist/notifications/tmux-detector.js.map +1 -1
  352. package/dist/notifications/tmux.js.map +1 -1
  353. package/dist/openclaw/config.js.map +1 -1
  354. package/dist/openclaw/dispatcher.js.map +1 -1
  355. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  356. package/dist/pipeline/__tests__/stages.test.js +8 -5
  357. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  358. package/dist/pipeline/stages/code-review.js.map +1 -1
  359. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  360. package/dist/pipeline/stages/ralplan.js.map +1 -1
  361. package/dist/pipeline/stages/team-exec.js.map +1 -1
  362. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  363. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  364. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  365. package/dist/planning/artifact-names.js.map +1 -1
  366. package/dist/planning/artifacts.js.map +1 -1
  367. package/dist/question/__tests__/state.test.js.map +1 -1
  368. package/dist/question/__tests__/ui.test.js.map +1 -1
  369. package/dist/question/autopilot-wait.js.map +1 -1
  370. package/dist/question/client.d.ts.map +1 -1
  371. package/dist/question/client.js.map +1 -1
  372. package/dist/question/deep-interview.js.map +1 -1
  373. package/dist/question/events.js.map +1 -1
  374. package/dist/question/renderer.js.map +1 -1
  375. package/dist/question/state.d.ts.map +1 -1
  376. package/dist/question/state.js.map +1 -1
  377. package/dist/question/ui.js.map +1 -1
  378. package/dist/ralph/contract.d.ts +1 -1
  379. package/dist/ralph/contract.d.ts.map +1 -1
  380. package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
  381. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  382. package/dist/ralplan/__tests__/runtime.test.js +112 -14
  383. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  384. package/dist/ralplan/consensus-gate.d.ts +3 -3
  385. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  386. package/dist/ralplan/consensus-gate.js +155 -60
  387. package/dist/ralplan/consensus-gate.js.map +1 -1
  388. package/dist/ralplan/runtime.d.ts +2 -2
  389. package/dist/ralplan/runtime.d.ts.map +1 -1
  390. package/dist/ralplan/runtime.js +1 -2
  391. package/dist/ralplan/runtime.js.map +1 -1
  392. package/dist/runtime/bridge.d.ts.map +1 -1
  393. package/dist/runtime/bridge.js.map +1 -1
  394. package/dist/runtime/process-tree.js.map +1 -1
  395. package/dist/runtime/run-loop.js.map +1 -1
  396. package/dist/runtime/run-outcome.d.ts +3 -3
  397. package/dist/runtime/run-outcome.d.ts.map +1 -1
  398. package/dist/runtime/run-outcome.js.map +1 -1
  399. package/dist/runtime/run-state.js.map +1 -1
  400. package/dist/scripts/__tests__/codex-native-hook.test.js +4121 -592
  401. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  402. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  403. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  404. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  405. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  406. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  407. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  408. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  409. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  410. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  411. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  412. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  413. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  414. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  415. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
  418. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  419. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
  422. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  423. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  424. package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
  425. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  426. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  427. package/dist/scripts/build-api.js.map +1 -1
  428. package/dist/scripts/build-explore-harness.js.map +1 -1
  429. package/dist/scripts/build-sparkshell.js.map +1 -1
  430. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  431. package/dist/scripts/codex-execution-surface.js.map +1 -1
  432. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  433. package/dist/scripts/codex-native-hook.js +689 -156
  434. package/dist/scripts/codex-native-hook.js.map +1 -1
  435. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  436. package/dist/scripts/generate-release-body.js.map +1 -1
  437. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  438. package/dist/scripts/hook-payload-guard.js.map +1 -1
  439. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  440. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  441. package/dist/scripts/notify-fallback-delivery.js +419 -0
  442. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  443. package/dist/scripts/notify-fallback-watcher.js +136 -30
  444. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  445. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  446. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  447. package/dist/scripts/notify-hook/auto-nudge.js +39 -13
  448. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  449. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  450. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  451. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  452. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  453. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  454. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  455. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  456. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  457. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  458. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  460. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  461. package/dist/scripts/notify-hook/state-io.js +73 -11
  462. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  463. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  464. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  466. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
  467. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  469. package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
  470. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  472. package/dist/scripts/notify-hook/tmux-injection.js +25 -16
  473. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  474. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  475. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  476. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  477. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  478. package/dist/scripts/notify-hook.d.ts +24 -1
  479. package/dist/scripts/notify-hook.d.ts.map +1 -1
  480. package/dist/scripts/notify-hook.js +266 -90
  481. package/dist/scripts/notify-hook.js.map +1 -1
  482. package/dist/scripts/postinstall.js.map +1 -1
  483. package/dist/scripts/prompt-inventory.js.map +1 -1
  484. package/dist/scripts/smoke-packed-install.d.ts +879 -2
  485. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  486. package/dist/scripts/smoke-packed-install.js +1528 -7
  487. package/dist/scripts/smoke-packed-install.js.map +1 -1
  488. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  489. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  490. package/dist/scripts/test-sparkshell.js.map +1 -1
  491. package/dist/scripts/verify-native-agents.js.map +1 -1
  492. package/dist/session-history/friction.js.map +1 -1
  493. package/dist/session-history/search.js.map +1 -1
  494. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  495. package/dist/sidecar/collector.js.map +1 -1
  496. package/dist/sidecar/index.js.map +1 -1
  497. package/dist/sidecar/render.js.map +1 -1
  498. package/dist/sidecar/tmux.js.map +1 -1
  499. package/dist/state/__tests__/operations.test.js +64 -15
  500. package/dist/state/__tests__/operations.test.js.map +1 -1
  501. package/dist/state/__tests__/skill-active.test.js +19 -1
  502. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  503. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  504. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  505. package/dist/state/mode-state-context.js.map +1 -1
  506. package/dist/state/operations.d.ts +1 -1
  507. package/dist/state/operations.d.ts.map +1 -1
  508. package/dist/state/operations.js +49 -55
  509. package/dist/state/operations.js.map +1 -1
  510. package/dist/state/paths.d.ts +1 -1
  511. package/dist/state/paths.d.ts.map +1 -1
  512. package/dist/state/paths.js +1 -1
  513. package/dist/state/paths.js.map +1 -1
  514. package/dist/state/skill-active.d.ts +13 -1
  515. package/dist/state/skill-active.d.ts.map +1 -1
  516. package/dist/state/skill-active.js +43 -4
  517. package/dist/state/skill-active.js.map +1 -1
  518. package/dist/state/terminal-normalization.js.map +1 -1
  519. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  520. package/dist/state/workflow-transition-reconcile.js +20 -4
  521. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  522. package/dist/state/workflow-transition.d.ts +1 -1
  523. package/dist/state/workflow-transition.d.ts.map +1 -1
  524. package/dist/state/workflow-transition.js.map +1 -1
  525. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
  526. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
  527. package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
  528. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
  529. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  530. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  531. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
  532. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  533. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  534. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  535. package/dist/subagents/adapted-role-binding.d.ts +17 -0
  536. package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
  537. package/dist/subagents/adapted-role-binding.js +75 -0
  538. package/dist/subagents/adapted-role-binding.js.map +1 -0
  539. package/dist/subagents/role-routing-marker.d.ts +10 -0
  540. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  541. package/dist/subagents/role-routing-marker.js +136 -0
  542. package/dist/subagents/role-routing-marker.js.map +1 -0
  543. package/dist/subagents/tracker.d.ts +172 -0
  544. package/dist/subagents/tracker.d.ts.map +1 -1
  545. package/dist/subagents/tracker.js +1077 -61
  546. package/dist/subagents/tracker.js.map +1 -1
  547. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  548. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  549. package/dist/team/__tests__/model-contract.test.js +231 -1
  550. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  551. package/dist/team/__tests__/role-router.test.js.map +1 -1
  552. package/dist/team/__tests__/runtime.test.js +401 -1
  553. package/dist/team/__tests__/runtime.test.js.map +1 -1
  554. package/dist/team/__tests__/scaling.test.js +181 -0
  555. package/dist/team/__tests__/scaling.test.js.map +1 -1
  556. package/dist/team/__tests__/state-root.test.js.map +1 -1
  557. package/dist/team/__tests__/tmux-session.test.js +434 -4
  558. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  559. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  560. package/dist/team/api-interop.d.ts +2 -2
  561. package/dist/team/api-interop.d.ts.map +1 -1
  562. package/dist/team/api-interop.js.map +1 -1
  563. package/dist/team/commit-hygiene.js.map +1 -1
  564. package/dist/team/contracts.d.ts +5 -5
  565. package/dist/team/contracts.d.ts.map +1 -1
  566. package/dist/team/delivery-log.js.map +1 -1
  567. package/dist/team/followup-planner.js.map +1 -1
  568. package/dist/team/goal-workflow.js.map +1 -1
  569. package/dist/team/idle-nudge.d.ts.map +1 -1
  570. package/dist/team/model-contract.d.ts +20 -1
  571. package/dist/team/model-contract.d.ts.map +1 -1
  572. package/dist/team/model-contract.js +267 -30
  573. package/dist/team/model-contract.js.map +1 -1
  574. package/dist/team/orchestrator.js.map +1 -1
  575. package/dist/team/pane-status.js.map +1 -1
  576. package/dist/team/phase-controller.js.map +1 -1
  577. package/dist/team/reminder-intents.d.ts +1 -1
  578. package/dist/team/reminder-intents.d.ts.map +1 -1
  579. package/dist/team/reminder-intents.js.map +1 -1
  580. package/dist/team/runtime-cli.js.map +1 -1
  581. package/dist/team/runtime.d.ts.map +1 -1
  582. package/dist/team/runtime.js +83 -52
  583. package/dist/team/runtime.js.map +1 -1
  584. package/dist/team/scaling.d.ts +9 -7
  585. package/dist/team/scaling.d.ts.map +1 -1
  586. package/dist/team/scaling.js +69 -28
  587. package/dist/team/scaling.js.map +1 -1
  588. package/dist/team/state/dispatch-lock.js.map +1 -1
  589. package/dist/team/state/dispatch.js.map +1 -1
  590. package/dist/team/state/events.js.map +1 -1
  591. package/dist/team/state/tasks.js.map +1 -1
  592. package/dist/team/state-root.js.map +1 -1
  593. package/dist/team/state.js.map +1 -1
  594. package/dist/team/team-identity.d.ts.map +1 -1
  595. package/dist/team/team-identity.js.map +1 -1
  596. package/dist/team/tmux-session.d.ts +2 -0
  597. package/dist/team/tmux-session.d.ts.map +1 -1
  598. package/dist/team/tmux-session.js +90 -42
  599. package/dist/team/tmux-session.js.map +1 -1
  600. package/dist/team/worker-bootstrap.js.map +1 -1
  601. package/dist/team/worktree.js.map +1 -1
  602. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  603. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  604. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  605. package/dist/ultragoal/artifacts.js +4 -1
  606. package/dist/ultragoal/artifacts.js.map +1 -1
  607. package/dist/url-reader/index.js.map +1 -1
  608. package/dist/utils/agents-model-table.js.map +1 -1
  609. package/dist/utils/package.js.map +1 -1
  610. package/dist/utils/paths.js.map +1 -1
  611. package/dist/utils/platform-command.js.map +1 -1
  612. package/dist/utils/repo-deps.js.map +1 -1
  613. package/dist/utils/version.js.map +1 -1
  614. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  615. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  616. package/dist/verification/verifier.js.map +1 -1
  617. package/dist/visual/constants.d.ts +1 -1
  618. package/dist/visual/constants.d.ts.map +1 -1
  619. package/dist/vscode/__tests__/index.test.js.map +1 -1
  620. package/dist/vscode/index.js.map +1 -1
  621. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  622. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  623. package/dist/wiki/__tests__/query.test.js.map +1 -1
  624. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  625. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  626. package/dist/wiki/lint.js.map +1 -1
  627. package/dist/wiki/query.js.map +1 -1
  628. package/dist/wiki/storage.js.map +1 -1
  629. package/package.json +2 -2
  630. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  631. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  632. package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
  633. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
  634. package/skills/omx-setup/SKILL.md +10 -5
  635. package/skills/ralph/SKILL.md +4 -2
  636. package/skills/ralplan/SKILL.md +5 -2
  637. package/src/scripts/__tests__/codex-native-hook.test.ts +17522 -10630
  638. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  639. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  640. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  641. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
  642. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
  643. package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
  644. package/src/scripts/codex-native-hook.ts +779 -159
  645. package/src/scripts/notify-fallback-delivery.ts +416 -0
  646. package/src/scripts/notify-fallback-watcher.ts +132 -40
  647. package/src/scripts/notify-hook/auto-nudge.ts +43 -12
  648. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  649. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  650. package/src/scripts/notify-hook/state-io.ts +100 -13
  651. package/src/scripts/notify-hook/tmux-injection.ts +38 -21
  652. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  653. package/src/scripts/notify-hook.ts +335 -90
  654. package/src/scripts/smoke-packed-install.ts +1829 -6
@@ -4,10 +4,11 @@ import { existsSync } from 'node:fs';
4
4
  import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { join } from 'node:path';
7
- import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, DEEP_INTERVIEW_STATE_FILE, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS, DEEP_INTERVIEW_INPUT_LOCK_MESSAGE, persistDeepInterviewModeState, } from '../keyword-detector.js';
7
+ import { detectKeywords, detectPrimaryKeyword, classifyKeywordInput, KEYWORD_INERT_DIAGNOSTIC_ORDER, recordSkillActivation, DEEP_INTERVIEW_STATE_FILE, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS, DEEP_INTERVIEW_INPUT_LOCK_MESSAGE, persistDeepInterviewModeState, } from '../keyword-detector.js';
8
8
  import { SKILL_ACTIVE_STATE_FILE } from '../../state/skill-active.js';
9
9
  import { isUnderspecifiedForExecution, applyRalplanGate } from '../keyword-detector.js';
10
- import { KEYWORD_TRIGGER_DEFINITIONS } from '../keyword-registry.js';
10
+ import { EXPLICIT_SKILL_ALIASES, getExplicitSkillDefinition, KEYWORD_TRIGGER_DEFINITIONS, } from '../keyword-registry.js';
11
+ import { evaluateResolvedPromptTurn } from '../prompt-session-provenance.js';
11
12
  async function withIsolatedHome(prefix, run) {
12
13
  const homeDir = await mkdtemp(join(tmpdir(), `omx-keyword-home-${prefix}-`));
13
14
  const previousHome = process.env.HOME;
@@ -367,6 +368,1268 @@ describe('keyword detector team compatibility', () => {
367
368
  assert.equal(detectPrimaryKeyword('ㅁㅔㅔ로 처리해줘'), null);
368
369
  });
369
370
  });
371
+ describe('keyword input classification direct grammar', () => {
372
+ it('classifies prompt-leading direct forms, aliases, priority, and block order', () => {
373
+ const cases = [
374
+ { text: '$ralplan implement this', skills: ['ralplan'], keywords: ['$ralplan'], priorities: [11] },
375
+ { text: '\u00a0$RALPLAN implement this', skills: ['ralplan'], keywords: ['$RALPLAN'], priorities: [11] },
376
+ { text: '- $team $ralph ship this', skills: ['team', 'ralph'], keywords: ['$team', '$ralph'], priorities: [8, 9] },
377
+ { text: '12) $oh-my-codex:ralplan build this', skills: ['ralplan'], keywords: ['$oh-my-codex:ralplan'], priorities: [11] },
378
+ { text: '$ulw $frontend-ui-ux build this', skills: ['ultrawork', 'design'], keywords: ['$ulw', '$frontend-ui-ux'], priorities: [10, 6] },
379
+ { text: '$ralplan $unknown $ralplan $ralph ship this', skills: ['ralplan', 'ralph'], keywords: ['$ralplan', '$ralph'], priorities: [11, 9] },
380
+ { text: '$ㅕㅣㅈ 병렬 작업', skills: ['ultrawork'], keywords: ['$ulw'], priorities: [10] },
381
+ { text: 'use $ralplan plan this', skills: ['ralplan'], keywords: ['$ralplan'], priorities: [11] },
382
+ { text: 'please use $ralplan plan this', skills: ['ralplan'], keywords: ['$ralplan'], priorities: [11] },
383
+ { text: 'run $ralplan plan this', skills: ['ralplan'], keywords: ['$ralplan'], priorities: [11] },
384
+ { text: '- use $ralplan plan this', skills: ['ralplan'], keywords: ['$ralplan'], priorities: [11] },
385
+ { text: 'run $analyze', skills: ['analyze'], keywords: ['$analyze'], priorities: [7] },
386
+ { text: 'run $code-review', skills: ['code-review'], keywords: ['$code-review'], priorities: [6] },
387
+ { text: 'please use $code-review', skills: ['code-review'], keywords: ['$code-review'], priorities: [6] },
388
+ { text: 'please run $team', skills: ['team'], keywords: ['$team'], priorities: [8] },
389
+ { text: 'start $deep-interview', skills: ['deep-interview'], keywords: ['$deep-interview'], priorities: [8] },
390
+ { text: 'enable $ultragoal', skills: ['ultragoal'], keywords: ['$ultragoal'], priorities: [10] },
391
+ { text: 'launch $autopilot', skills: ['autopilot'], keywords: ['$autopilot'], priorities: [10] },
392
+ { text: 'invoke $ralph', skills: ['ralph'], keywords: ['$ralph'], priorities: [9] },
393
+ { text: 'activate $ultrawork', skills: ['ultrawork'], keywords: ['$ultrawork'], priorities: [10] },
394
+ { text: 'resume $team', skills: ['team'], keywords: ['$team'], priorities: [8] },
395
+ { text: 'continue $code-review', skills: ['code-review'], keywords: ['$code-review'], priorities: [6] },
396
+ ];
397
+ for (const testCase of cases) {
398
+ const classification = classifyKeywordInput(testCase.text);
399
+ assert.equal(classification.reservedInput, null, testCase.text);
400
+ assert.deepEqual(classification.matches.map((match) => match.skill), testCase.skills, testCase.text);
401
+ assert.deepEqual(classification.matches.map((match) => match.keyword), testCase.keywords, testCase.text);
402
+ assert.deepEqual(classification.matches.map((match) => match.priority), testCase.priorities, testCase.text);
403
+ }
404
+ });
405
+ it('does not extend discourse negation beyond a comma-separated directive', () => {
406
+ for (const text of [
407
+ 'No worries, use autopilot mode.',
408
+ "I'm not sure, please use autopilot mode.",
409
+ ]) {
410
+ assert.deepEqual(classifyKeywordInput(text).matches.map((match) => match.skill), ['autopilot'], text);
411
+ }
412
+ for (const text of ['Do not use autopilot mode.', 'No autopilot mode.']) {
413
+ assert.deepEqual(classifyKeywordInput(text).matches, [], text);
414
+ }
415
+ });
416
+ it('accepts every directive verb and polite prefix for every explicit token and alias', () => {
417
+ const tokens = new Set([
418
+ ...KEYWORD_TRIGGER_DEFINITIONS
419
+ .filter((definition) => definition.keyword.startsWith('$'))
420
+ .map((definition) => definition.keyword.slice(1)),
421
+ ...EXPLICIT_SKILL_ALIASES.map((alias) => alias.source),
422
+ ]);
423
+ const directiveVerbs = ['use', 'run', 'start', 'enable', 'launch', 'invoke', 'activate', 'resume', 'continue'];
424
+ for (const token of tokens) {
425
+ const definition = getExplicitSkillDefinition(token);
426
+ assert.ok(definition, token);
427
+ const tokenForms = [`$${token}`];
428
+ if (/^[A-Za-z]/u.test(token))
429
+ tokenForms.push(`$oh-my-codex:${token}`);
430
+ for (const tokenForm of tokenForms) {
431
+ for (const directiveVerb of directiveVerbs) {
432
+ for (const politePrefix of ['', 'please ']) {
433
+ const text = `${politePrefix}${directiveVerb} ${tokenForm} now`;
434
+ const classification = classifyKeywordInput(text);
435
+ assert.deepEqual(classification.matches.map((match) => match.skill), [definition.skill], text);
436
+ assert.deepEqual(classification.matches.map((match) => match.priority), [definition.priority], text);
437
+ assert.deepEqual(classification.candidates[0]?.reasons, [], text);
438
+ }
439
+ }
440
+ }
441
+ }
442
+ });
443
+ it('keeps directive-looking documentation and non-leading prose inert', () => {
444
+ for (const text of [
445
+ 'use $ralplan is the consensus-planning command',
446
+ 'please run $team is a workflow command',
447
+ 'use $ralplan is the workflow command for autopilot mode',
448
+ 'use $ralplan is the consensus-planning command\nAutopilot mode is its alias.',
449
+ 'use $ralplan, $autopilot, and $team are workflow commands',
450
+ 'use $ralplan is the consensus-planning command; $team is its alias',
451
+ 'use $ralplan,$autopilot are workflow commands',
452
+ 'use $ralplan, $autopilot, and $team are workflow commands',
453
+ 'use $ralplan is the consensus-planning command; $team is also its alias',
454
+ 'use $ralplan is the consensus-planning command\nAutopilot mode is also its alias.',
455
+ 'use $ralplan is the workflow command; $team appears in the documentation.',
456
+ 'use $ralplan، $autopilot are workflow commands',
457
+ '$ralplan، $autopilot are prohibited',
458
+ '$ralplan,$autopilot are prohibited',
459
+ 'Autopilot mode، deep interview are prohibited.',
460
+ 'Autopilot mode،deep interview are prohibited.',
461
+ 'use $ralplan is the workflow command, e.g. use $autopilot in examples.',
462
+ 'use $ralplan is the workflow command; autopilot mode appears in the documentation.',
463
+ '$ralplan、 $autopilot are prohibited',
464
+ 'Autopilot mode、 deep interview are prohibited.',
465
+ 'uſe $ralplan plan it',
466
+ 'pleaſe use $ralplan plan it',
467
+ 'Do not use deep interview but uſe autopilot mode.',
468
+ 'For instance: use autopilot mode.',
469
+ 'For instance, use autopilot mode.',
470
+ 'For instance، use autopilot mode.',
471
+ 'For instance、 use autopilot mode.',
472
+ 'use $ralplan is the workflow command; $autopilot is documented in the guide.',
473
+ '$autopilot is described in the manual.',
474
+ 'The docs say use $ralplan plan this',
475
+ 'I think we should run $code-review before merge',
476
+ ]) {
477
+ assert.deepEqual(classifyKeywordInput(text).matches, [], text);
478
+ }
479
+ });
480
+ it('contains nested predecessors, preserves first-block and reserved dominance', () => {
481
+ const nested = classifyKeywordInput('"`x`\n$ralplan plan it');
482
+ assert.deepEqual(nested.matches, []);
483
+ assert.deepEqual(nested.candidates[0]?.reasons, ['not-leading-region']);
484
+ const firstBlock = classifyKeywordInput('$ralplan plan it\n"x"\n$autopilot build it');
485
+ assert.deepEqual(firstBlock.matches.map((match) => match.skill), ['ralplan']);
486
+ assert.deepEqual(firstBlock.candidates[1]?.reasons, ['not-leading-region']);
487
+ const reserved = classifyKeywordInput('/prompts:architect\n"x"\n$ralplan plan it');
488
+ assert.equal(reserved.reservedInput, 'prompts');
489
+ assert.deepEqual(reserved.matches, []);
490
+ for (const text of [
491
+ '/prompts:architect— use autopilot mode',
492
+ '/prompts:architect, use autopilot mode',
493
+ ]) {
494
+ const classification = classifyKeywordInput(text);
495
+ assert.equal(classification.reservedInput, 'prompts', text);
496
+ assert.deepEqual(classification.matches, [], text);
497
+ }
498
+ const confusablePrompts = classifyKeywordInput('/promptſ:architect; use autopilot mode.');
499
+ assert.equal(confusablePrompts.reservedInput, null);
500
+ assert.deepEqual(confusablePrompts.matches.map((match) => match.skill), ['autopilot']);
501
+ });
502
+ it('tracks list fence closer identity and relative indentation', () => {
503
+ const rootFence = classifyKeywordInput('- ```\n sample\n```\n$ralplan plan it');
504
+ assert.deepEqual(rootFence.matches, []);
505
+ assert.ok(rootFence.candidates[0]?.reasons.includes('fenced-code'));
506
+ const relativeCloser = classifyKeywordInput('- ```\n sample\n ```\n$ralplan plan it');
507
+ assert.deepEqual(relativeCloser.matches.map((match) => match.skill), ['ralplan']);
508
+ });
509
+ it('binds B3 through B5 fence candidates exactly', () => {
510
+ const cases = [
511
+ {
512
+ text: '```\n$ralph\n````\n$ralplan plan it',
513
+ skills: ['ralplan'],
514
+ candidates: [
515
+ { rawKeyword: '$ralph', reasons: ['fenced-code', 'not-leading-region'] },
516
+ { rawKeyword: '$ralplan', reasons: [] },
517
+ ],
518
+ },
519
+ {
520
+ text: '````\n$ralplan\n```\n$ralph ship it',
521
+ skills: [],
522
+ candidates: [
523
+ { rawKeyword: '$ralplan', reasons: ['fenced-code', 'not-leading-region'] },
524
+ { rawKeyword: '$ralph', reasons: ['fenced-code', 'not-leading-region'] },
525
+ ],
526
+ },
527
+ {
528
+ text: '```\n$ralplan\n~~~\n$ralph ship it',
529
+ skills: [],
530
+ candidates: [
531
+ { rawKeyword: '$ralplan', reasons: ['fenced-code', 'not-leading-region'] },
532
+ { rawKeyword: '$ralph', reasons: ['fenced-code', 'not-leading-region'] },
533
+ ],
534
+ },
535
+ ];
536
+ for (const testCase of cases) {
537
+ const classification = classifyKeywordInput(testCase.text);
538
+ assert.equal(classification.reservedInput, null, testCase.text);
539
+ assert.deepEqual(classification.matches.map((match) => match.skill), testCase.skills, testCase.text);
540
+ assert.deepEqual(classification.candidates.map((candidate) => ({ rawKeyword: candidate.rawKeyword, reasons: candidate.reasons })), testCase.candidates, testCase.text);
541
+ }
542
+ });
543
+ it('masks multiline Markdown reference titles', () => {
544
+ for (const text of [
545
+ '[docs]: /target "title\nuse /prompts:architect\n$ralplan plan it"',
546
+ '[docs]: /target "title\nuse autopilot mode"',
547
+ '[docs]: ./target\n (autopilot mode)',
548
+ '[docs]:\n ./target\n (autopilot mode)',
549
+ '[docs]: ./target\n(autopilot mode)',
550
+ ]) {
551
+ assert.deepEqual(classifyKeywordInput(text).matches, [], text);
552
+ }
553
+ const closedPromptsTitle = classifyKeywordInput('[docs]: /target "title\nUse /prompts:architect"\n$ralplan plan it');
554
+ assert.deepEqual(closedPromptsTitle.matches.map((match) => match.skill), ['ralplan']);
555
+ });
556
+ it('fails closed for Unicode case-fold and confusable explicit-token continuations', () => {
557
+ for (const testCase of [
558
+ { text: '$ultraworK execute', rawKeyword: '$ultraworK' },
559
+ { text: '$ralplan・suffix plan it', rawKeyword: '$ralplan・suffix' },
560
+ { text: '$ralplan・suffix plan it', rawKeyword: '$ralplan・suffix' },
561
+ { text: '$ralplan٪docs', rawKeyword: '$ralplan٪docs' },
562
+ { text: '$ralplan∕config', rawKeyword: '$ralplan∕config' },
563
+ ]) {
564
+ const classification = classifyKeywordInput(testCase.text);
565
+ assert.deepEqual(classification.matches, [], testCase.text);
566
+ assert.equal(classification.candidates[0]?.rawKeyword, testCase.rawKeyword, testCase.text);
567
+ assert.equal(classification.candidates[0]?.skill, null, testCase.text);
568
+ }
569
+ });
570
+ it('composes V11 malformed-token, documentation, directive, and Arabic-clause controls', () => {
571
+ for (const text of ["$ralplan's", '$ralplan’s', '$ralplan's']) {
572
+ const classification = classifyKeywordInput(text);
573
+ assert.equal(classification.hasExplicitLikeInvocation, true, text);
574
+ assert.equal(classification.candidates[0]?.rawKeyword, text, text);
575
+ assert.equal(classification.candidates[0]?.skill, null, text);
576
+ assert.deepEqual(classification.matches, [], text);
577
+ }
578
+ const possessivePrompts = classifyKeywordInput("/prompts:architect's");
579
+ assert.equal(possessivePrompts.reservedInput, null);
580
+ assert.deepEqual(possessivePrompts.matches, []);
581
+ for (const text of ['$・autopilot mode', '$・autopilot mode', '$٪autopilot mode', '$∕autopilot mode']) {
582
+ const classification = classifyKeywordInput(text);
583
+ assert.equal(classification.hasExplicitLikeInvocation, true, text);
584
+ assert.equal(classification.candidates[0]?.skill, null, text);
585
+ assert.deepEqual(classification.matches, [], text);
586
+ }
587
+ for (const testCase of [
588
+ { text: 'use $autopilot is documented but use $ralplan plan it', skills: ['ralplan'] },
589
+ { text: '- use $ralplan: consensus-planning workflow', skills: [] },
590
+ { text: 'use $ralplan is the workflow command؟ run $autopilot', skills: ['autopilot'] },
591
+ { text: '$ralplan؛ $autopilot is prohibited', skills: ['ralplan'] },
592
+ { text: '$ralplan is prohibited but uſe autopilot mode.', skills: [] },
593
+ { text: '$ralplan is prohibited but use autopilot mode.', skills: ['autopilot'] },
594
+ { text: '[docs]: /target "title\nplain text"\n$ralplan plan it', skills: ['ralplan'] },
595
+ { text: '[docs]: ./target\n$ralplan plan it', skills: ['ralplan'] },
596
+ { text: 'Do not run $ralplan; use the $autopilot build it', skills: ['autopilot'] },
597
+ { text: '"quoted"\ncontinue with $ralplan', skills: ['ralplan'] },
598
+ { text: 'use $ralplan is documented; advance to $ultragoal', skills: ['ultragoal'] },
599
+ ]) {
600
+ assert.deepEqual(classifyKeywordInput(testCase.text).matches.map((match) => match.skill), testCase.skills, testCase.text);
601
+ }
602
+ });
603
+ it('masks mixed postposed negation and documentary subject chains without reopening prose', () => {
604
+ for (const text of [
605
+ 'Autopilot mode and $ralplan are prohibited.',
606
+ 'use $ralplan and autopilot mode are workflow commands',
607
+ '$ralplan is prohibited because docs use $autopilot.',
608
+ ]) {
609
+ assert.deepEqual(classifyKeywordInput(text).matches, [], text);
610
+ }
611
+ assert.deepEqual(classifyKeywordInput('use $ralplan and autopilot mode are workflow commands; use $team execute it').matches.map((match) => match.skill), ['team']);
612
+ for (const punctuation of ['.', ';']) {
613
+ const text = `Do not run $ralplan${punctuation} use $autopilot build it`;
614
+ assert.deepEqual(classifyKeywordInput(text).matches.map((match) => match.skill), ['autopilot'], text);
615
+ assert.deepEqual(classifyKeywordInput(`Do not run $ralplan${punctuation}suffix`).matches, [], `${text} attached suffix`);
616
+ }
617
+ });
618
+ it('classifies adversarial explicit candidate families without repeated tail scans', () => {
619
+ const count = 4_096;
620
+ const cases = [
621
+ Array.from({ length: count }, () => '$team is prohibited').join('; '),
622
+ `Do not run ${'$team '.repeat(count)}`,
623
+ `Mode | Meaning\n--- | ---\n${Array.from({ length: count }, () => '$team | workflow documentation').join('\n')}`,
624
+ `use ${Array.from({ length: count }, () => '$team').join(', ')} are workflow commands`,
625
+ ];
626
+ for (const text of cases) {
627
+ const classification = classifyKeywordInput(text);
628
+ assert.equal(classification.candidates.length, count);
629
+ assert.deepEqual(classification.matches, [], text.slice(0, 80));
630
+ }
631
+ });
632
+ it('repairs V13 Unicode grammar, coordinated negation, documentation, and Markdown-table probes', () => {
633
+ const cases = [
634
+ { text: 'Do not use deep interview яbut use autopilot mode.', skills: [] },
635
+ { text: 'Do not use deep interview but use autopilot mode.', skills: ['autopilot'] },
636
+ { text: '$ralplan, autopilot mode, $team are prohibited.', skills: [] },
637
+ { text: 'Autopilot mode and $ralplan are workflow commands; use $team execute it', skills: ['team'] },
638
+ { text: 'Use autopilot mode; "note"; use $ralplan is the workflow command.', skills: ['autopilot'] },
639
+ { text: 'use $ralplan is the workflow command: use $autopilot build it', skills: ['autopilot'] },
640
+ { text: 'Mode | Meaning\n--- | ---\nmanual | documentation\n$ralplan plan it', skills: ['ralplan'] },
641
+ { text: 'Do not run $ralplan but advance to $ultragoal', skills: ['ultragoal'] },
642
+ { text: 'Do not run $ralplan but jump straight to $ultragoal', skills: ['ultragoal'] },
643
+ { text: 'Do not run $ralplan яbut advance to $ultragoal', skills: [] },
644
+ { text: 'use $ralplan is the workflow command яbut use $autopilot build it', skills: [] },
645
+ ];
646
+ for (const testCase of cases) {
647
+ assert.deepEqual(classifyKeywordInput(testCase.text).matches.map((match) => match.skill), testCase.skills, testCase.text);
648
+ }
649
+ for (const separator of [', ', ',', '،', '、', ' / ', ' and ']) {
650
+ const text = `Do not run $ralplan${separator}$autopilot; use $team execute it`;
651
+ assert.deepEqual(classifyKeywordInput(text).matches.map((match) => match.skill), ['team'], text);
652
+ }
653
+ });
654
+ it('keeps V13 coordinated scans near-linear at 4096 explicit candidates', () => {
655
+ const count = 4_096;
656
+ const families = [
657
+ { text: '$team; '.repeat(count), skills: ['team'] },
658
+ { text: `Do not run ${Array.from({ length: count }, () => '$team').join(', ')}`, skills: [] },
659
+ { text: `${Array.from({ length: count }, () => '$team, autopilot mode').join(', ')} are prohibited`, skills: [] },
660
+ ];
661
+ for (const family of families) {
662
+ const startedAt = Date.now();
663
+ const classification = classifyKeywordInput(family.text);
664
+ assert.equal(classification.candidates.length, count, family.text.slice(0, 80));
665
+ assert.deepEqual(classification.matches.map((match) => match.skill), family.skills, family.text.slice(0, 80));
666
+ assert.ok(Date.now() - startedAt < 2_000, 'V13 coordinated scan must stay bounded');
667
+ }
668
+ });
669
+ it('repairs V14 mixed subject chains and semicolon-local documentation', () => {
670
+ const cases = [
671
+ { text: 'Both autopilot mode and $ralplan are prohibited.', skills: [] },
672
+ { text: 'Both autopilot mode and $ralplan are workflow commands; use $team execute it', skills: ['team'] },
673
+ { text: 'Use autopilot mode; use $ralplan is the workflow command.', skills: ['autopilot'] },
674
+ ];
675
+ for (const testCase of cases) {
676
+ assert.deepEqual(classifyKeywordInput(testCase.text).matches.map((match) => match.skill), testCase.skills, testCase.text);
677
+ }
678
+ const bareDollarPrefixes = classifyKeywordInput('$ $ $team');
679
+ assert.equal(bareDollarPrefixes.candidates.length, 1);
680
+ assert.equal(bareDollarPrefixes.candidates[0]?.rawKeyword, '$team');
681
+ assert.equal(bareDollarPrefixes.candidates[0]?.skill, 'team');
682
+ assert.deepEqual(bareDollarPrefixes.matches, []);
683
+ });
684
+ it('keeps V14 explicit scans bounded at 4096 candidates', () => {
685
+ const count = 4_096;
686
+ const families = [
687
+ {
688
+ name: 'bare-dollar prefixes before a canonical token',
689
+ text: `${'$ '.repeat(count)}$team`,
690
+ candidates: 1,
691
+ skills: [],
692
+ },
693
+ {
694
+ name: 'non-leading same-line canonical candidates',
695
+ text: `ordinary prose ${'$team '.repeat(count)}`,
696
+ candidates: count,
697
+ skills: [],
698
+ },
699
+ {
700
+ name: 'implicit-leading coordinated documentary candidates',
701
+ text: `The docs mention autopilot mode, ${'$team, '.repeat(count)}are workflow commands`,
702
+ candidates: count,
703
+ skills: [],
704
+ },
705
+ ];
706
+ for (const family of families) {
707
+ const startedAt = Date.now();
708
+ const classification = classifyKeywordInput(family.text);
709
+ assert.equal(classification.candidates.length, family.candidates, family.name);
710
+ assert.deepEqual(classification.matches.map((match) => match.skill), family.skills, family.name);
711
+ assert.ok(Date.now() - startedAt < 2_000, `${family.name} must stay bounded`);
712
+ }
713
+ });
714
+ it('keeps V15 documentary chains, compact candidates, and repeated predicates bounded', () => {
715
+ const count = 4_096;
716
+ const implicitSubjects = Array.from({ length: count }, () => 'autopilot mode').join(', ');
717
+ const compactCandidates = Array.from({ length: count }, () => '$team').join(',');
718
+ const families = [
719
+ {
720
+ name: 'implicit-only documentary comma chain',
721
+ text: `The docs mention ${implicitSubjects} are workflow commands`,
722
+ candidates: 0,
723
+ maximumMilliseconds: 2_000,
724
+ },
725
+ {
726
+ name: 'compact documentary explicit comma chain',
727
+ text: `${compactCandidates} are workflow commands`,
728
+ candidates: count,
729
+ maximumMilliseconds: 2_000,
730
+ },
731
+ {
732
+ name: 'repeated postposed predicates',
733
+ text: `${compactCandidates}${' are prohibited'.repeat(count)}`,
734
+ candidates: count,
735
+ maximumMilliseconds: 4_000,
736
+ },
737
+ ];
738
+ for (const family of families) {
739
+ const startedAt = Date.now();
740
+ const classification = classifyKeywordInput(family.text);
741
+ assert.equal(classification.candidates.length, family.candidates, family.name);
742
+ assert.deepEqual(classification.matches, [], family.name);
743
+ assert.ok(Date.now() - startedAt < family.maximumMilliseconds, `${family.name} must stay bounded`);
744
+ }
745
+ const laterMixedDocumentaryChain = classifyKeywordInput('The docs mention autopilot mode, deep interview; $team and autopilot mode are workflow commands');
746
+ assert.deepEqual(laterMixedDocumentaryChain.matches, []);
747
+ });
748
+ it('repairs V16 documentary clause, reference, and negation composition', () => {
749
+ for (const testCase of [
750
+ { text: 'Use autopilot mode, and $ralplan is documented in the guide.', skills: ['autopilot'] },
751
+ { text: '[docs]: "target\n$autopilot build it', skills: [] },
752
+ { text: '[docs]: `target\n$autopilot build it', skills: [] },
753
+ { text: 'Do not run $ralplan and use autopilot mode.', skills: [] },
754
+ { text: '$team is prohibited and is forbidden; use $ralplan plan it', skills: ['ralplan'] },
755
+ { text: 'Use $ralplan, autopilot mode and $team are workflow commands.', skills: [] },
756
+ ]) {
757
+ assert.deepEqual(classifyKeywordInput(testCase.text).matches.map((match) => match.skill), testCase.skills, testCase.text);
758
+ }
759
+ });
760
+ it('keeps V16 documentary and implicit-negative families subquadratic through 8192 items', () => {
761
+ const families = [
762
+ {
763
+ name: 'documentary followup chain',
764
+ text: (count) => `Autopilot mode is documented, ${Array.from({ length: count }, () => '$team').join(', ')} are workflow commands`,
765
+ candidates: (count) => count,
766
+ },
767
+ {
768
+ name: 'implicit prefix negation',
769
+ text: (count) => 'Do not use autopilot mode. '.repeat(count),
770
+ candidates: () => 0,
771
+ },
772
+ {
773
+ name: 'implicit postposed negation',
774
+ text: (count) => 'Autopilot mode is prohibited. '.repeat(count),
775
+ candidates: () => 0,
776
+ },
777
+ ];
778
+ for (const family of families) {
779
+ const elapsed = new Map();
780
+ for (const count of [4_096, 8_192]) {
781
+ const startedAt = process.hrtime.bigint();
782
+ const classification = classifyKeywordInput(family.text(count));
783
+ elapsed.set(count, process.hrtime.bigint() - startedAt);
784
+ assert.equal(classification.candidates.length, family.candidates(count), `${family.name}: ${count}`);
785
+ assert.deepEqual(classification.matches, [], `${family.name}: ${count}`);
786
+ assert.deepEqual(classification.implicitMatches, [], `${family.name}: ${count}`);
787
+ }
788
+ assert.ok((elapsed.get(8_192) ?? 0n) < (elapsed.get(4_096) ?? 0n) * 4n, `${family.name} must remain subquadratic when the input doubles`);
789
+ }
790
+ });
791
+ it('accepts only direct punctuation and list boundaries', () => {
792
+ const cases = [
793
+ { text: '* $ralph', skills: ['ralph'] },
794
+ { text: '+ $team', skills: ['team'] },
795
+ { text: '1. $ralplan', skills: ['ralplan'] },
796
+ { text: '999) $ultrawork', skills: ['ultrawork'] },
797
+ { text: '($ralplan)', skills: [] },
798
+ { text: '[$ralplan]', skills: [] },
799
+ { text: '1,$ralplan', skills: [] },
800
+ ];
801
+ for (const testCase of cases) {
802
+ const classification = classifyKeywordInput(testCase.text);
803
+ assert.deepEqual(classification.matches.map((match) => match.skill), testCase.skills, testCase.text);
804
+ if (testCase.skills.length === 0) {
805
+ assert.deepEqual(classification.candidates[0]?.reasons, ['not-leading-region'], testCase.text);
806
+ }
807
+ }
808
+ });
809
+ it('lexes malformed maximal tokens without activating canonical prefixes', () => {
810
+ for (const text of [
811
+ '$ralplan- plan this',
812
+ '$oh-my-codex:ralplan- plan this',
813
+ '$ralplan_invalid plan this',
814
+ '$ralplan@docs plan this',
815
+ '$ralplan#docs plan this',
816
+ '$ralplan=docs plan this',
817
+ '$ralplan@docs plan this',
818
+ '$ralplan#docs plan this',
819
+ '$ralplan=docs plan this',
820
+ ]) {
821
+ const classification = classifyKeywordInput(text);
822
+ assert.deepEqual(classification.matches, [], text);
823
+ assert.equal(classification.reservedInput, null, text);
824
+ assert.equal(classification.hasExplicitLikeInvocation, true, text);
825
+ assert.equal(classification.candidates.length, 1, text);
826
+ assert.equal(classification.candidates[0]?.rawKeyword, text.split(' ')[0], text);
827
+ assert.equal(classification.candidates[0]?.skill, null, text);
828
+ assert.deepEqual(classification.candidates[0]?.reasons, [], text);
829
+ }
830
+ });
831
+ it('scans maximal explicit tokens and rejects documentation, paths, Unicode, compatibility, and control suffixes', () => {
832
+ const cases = [
833
+ { text: '$ralplan.md is the workflow documentation file', rawKeyword: '$ralplan.md' },
834
+ { text: '$autopilot/config', rawKeyword: '$autopilot/config' },
835
+ { text: '$ralplan한글', rawKeyword: '$ralplan한글' },
836
+ { text: '$oh-my-codex:ralplan.md', rawKeyword: '$oh-my-codex:ralplan.md' },
837
+ { text: '$ralplan..md', rawKeyword: '$ralplan..md' },
838
+ { text: '$ralplan‐suffix', rawKeyword: '$ralplan‐suffix' },
839
+ { text: '$ralplan\u200B.md', rawKeyword: '$ralplan\u200B.md' },
840
+ { text: '$ralplan/config', rawKeyword: '$ralplan/config' },
841
+ { text: '$ralplan\u0000md', rawKeyword: '$ralplan\u0000md' },
842
+ { text: '$ralplan\u202Emd', rawKeyword: '$ralplan\u202Emd' },
843
+ { text: '$ralplan\uFEFF.md', rawKeyword: '$ralplan\uFEFF.md' },
844
+ { text: '$ralplan.md', rawKeyword: '$ralplan.md' },
845
+ { text: '$ralplan·suffix plan it', rawKeyword: '$ralplan·suffix' },
846
+ { text: '$ralplan%docs', rawKeyword: '$ralplan%docs' },
847
+ { text: '$ralplan%docs', rawKeyword: '$ralplan%docs' },
848
+ ];
849
+ for (const testCase of cases) {
850
+ const classification = classifyKeywordInput(testCase.text);
851
+ assert.deepEqual(classification.matches, [], testCase.text);
852
+ assert.equal(classification.candidates.length, 1, testCase.text);
853
+ assert.equal(classification.candidates[0]?.rawKeyword, testCase.rawKeyword, testCase.text);
854
+ assert.equal(classification.candidates[0]?.skill, null, testCase.text);
855
+ assert.deepEqual(classification.candidates[0]?.reasons, [], testCase.text);
856
+ }
857
+ for (const text of ['$ralplan, plan this', '$ralplan; plan this', '$ralplan\nplan this']) {
858
+ assert.deepEqual(classifyKeywordInput(text).matches.map((match) => match.skill), ['ralplan'], text);
859
+ }
860
+ });
861
+ it('accepts later directives after structurally separated inert or negative mentions', () => {
862
+ const cases = [
863
+ { text: 'Do not run $ralplan; instead $autopilot build issue #3140', skills: ['autopilot'] },
864
+ { text: 'Do not run $ralplan, instead $autopilot build it', skills: ['autopilot'] },
865
+ { text: 'Do not run $ralplan; use $autopilot build it', skills: ['autopilot'] },
866
+ { text: 'Do not run $ralplan but use $autopilot build it', skills: ['autopilot'] },
867
+ { text: 'Do not run $ralplan but instead use $autopilot build it', skills: ['autopilot'] },
868
+ { text: 'Do not run $ralplan — instead use $autopilot build it', skills: ['autopilot'] },
869
+ { text: 'Quoted inline-code `$ralplan`; use $autopilot build it', skills: ['autopilot'] },
870
+ { text: 'Without $ralplan.\n$autopilot build it', skills: ['autopilot'] },
871
+ { text: 'Quoted example: "$ralplan plan it".\n$autopilot build it', skills: ['autopilot'] },
872
+ { text: '`$ralplan` is inert.\n$autopilot build it', skills: ['autopilot'] },
873
+ { text: '"Use /prompts:architect"\n$ralplan plan it', skills: ['ralplan'] },
874
+ { text: '> quoted context\n$ralplan plan it', skills: ['ralplan'] },
875
+ { text: '```text\nquoted context\n```\n$ralplan plan it', skills: ['ralplan'] },
876
+ { text: ' quoted context\n$ralplan plan it', skills: ['ralplan'] },
877
+ { text: '"quoted context"\n$ralplan plan it', skills: ['ralplan'] },
878
+ { text: 'Use /prompts:architect.\n$ralplan plan it', skills: ['ralplan'] },
879
+ { text: '- Use /prompts:architect.\n$ralplan plan it', skills: ['ralplan'] },
880
+ { text: '- - ```\n quoted context\n ```\n$ralplan plan it', skills: ['ralplan'] },
881
+ { text: '> quoted context\n$ralplan plan it\nLater discussion.\n$autopilot build it', skills: ['ralplan'] },
882
+ ];
883
+ for (const testCase of cases) {
884
+ const classification = classifyKeywordInput(testCase.text);
885
+ assert.deepEqual(classification.matches.map((match) => match.skill), testCase.skills, testCase.text);
886
+ assert.equal(classification.reservedInput, null, testCase.text);
887
+ }
888
+ for (const text of [
889
+ 'Do not run $ralplan and use $autopilot build it',
890
+ 'Do not run $ralplan; do not run $autopilot',
891
+ 'Do not run $ralplan, $autopilot',
892
+ '"$ralplan" mentions $autopilot without a clause boundary',
893
+ 'Do not run $ralplan. We only document $autopilot behavior',
894
+ '"$ralplan". The $autopilot workflow is documented',
895
+ '```text\nquoted context\n$ralplan plan it',
896
+ '```text\nquoted context\n~~~\n$ralplan plan it',
897
+ '"quoted context\n$ralplan plan it',
898
+ '> quoted context\nThe docs mention $ralplan only',
899
+ '"quoted context"\nDo not run $ralplan',
900
+ '> quoted context\nProse\n$ralplan implement this',
901
+ '> quoted context\nProse\nUse $ralplan plan this',
902
+ '> quoted context\n/prompts:architect\n$ralplan plan this',
903
+ 'Do not run $ralplan.\n"unclosed context\n$autopilot build it',
904
+ '[$ralplan]: ./docs\n"unclosed context\n$autopilot build it',
905
+ '"Use /prompts:architect\n$ralplan plan it',
906
+ '/prompts:architect한글\n$ralplan plan it',
907
+ '[docs]:\nautopilot',
908
+ ]) {
909
+ assert.deepEqual(classifyKeywordInput(text).matches, [], text);
910
+ }
911
+ });
912
+ it('keeps prompt reservations and list documentation structural and composable', () => {
913
+ const reserved = classifyKeywordInput('/prompts:architect analyze this issue');
914
+ assert.equal(reserved.reservedInput, 'prompts');
915
+ assert.deepEqual(reserved.matches, []);
916
+ for (const text of [
917
+ 'Documentation mentions /prompts:architect and asks to analyze this issue',
918
+ '"/prompts:architect" is quoted documentation',
919
+ '- $ralplan is the consensus-planning command',
920
+ '1. $autopilot refers to the autonomous workflow command',
921
+ '- /prompts:architect is the prompt command documentation',
922
+ '- $ralplan: consensus-planning workflow',
923
+ '- $ralplan: consensus-planning workflow',
924
+ '- $ralplan — consensus-planning command',
925
+ '- $ralplan, $autopilot are workflow commands',
926
+ '- $ralplan and $autopilot are workflow commands',
927
+ '- $ralplan, $autopilot, and $team are workflow commands',
928
+ '- $ralplan / $autopilot are workflow commands',
929
+ '- $ralplan/$autopilot are workflow commands',
930
+ ]) {
931
+ const classification = classifyKeywordInput(text);
932
+ assert.equal(classification.reservedInput, null, text);
933
+ assert.deepEqual(classification.matches, [], text);
934
+ }
935
+ for (const testCase of [
936
+ { text: '- $ralplan is the consensus-planning command\n$autopilot build it', skills: ['autopilot'] },
937
+ { text: '- /prompts:architect is the prompt command documentation\n$ralplan plan it', skills: ['ralplan'] },
938
+ { text: '- $ralplan, $autopilot are workflow commands\n$team execute it', skills: ['team'] },
939
+ { text: '- $ralplan and $autopilot are workflow commands\n$team execute it', skills: ['team'] },
940
+ { text: '- $ralplan, $autopilot, and $team are workflow commands\n$ralph execute it', skills: ['ralph'] },
941
+ { text: '- $ralplan / $autopilot are workflow commands\n$team execute it', skills: ['team'] },
942
+ { text: '- $ralplan/$autopilot are workflow commands\n$team execute it', skills: ['team'] },
943
+ { text: 'use $ralplan is the consensus-planning command\n$autopilot build it', skills: ['autopilot'] },
944
+ { text: 'use $ralplan is the consensus-planning command; $autopilot build it', skills: ['autopilot'] },
945
+ { text: 'use $ralplan is the consensus-planning command\nUse autopilot mode.', skills: ['autopilot'] },
946
+ { text: 'use $ralplan is the workflow command for planning\n$autopilot build it', skills: ['autopilot'] },
947
+ { text: '- use $ralplan and $autopilot are workflow commands\n$team execute it', skills: ['team'] },
948
+ { text: 'use $ralplan is the consensus-planning command; use $autopilot build it', skills: ['autopilot'] },
949
+ { text: 'use $ralplan is the consensus-planning command\nuse $autopilot is the autonomous workflow command\n$team execute it', skills: ['team'] },
950
+ { text: 'use $ralplan is the workflow command for $team\n$autopilot build it', skills: ['autopilot'] },
951
+ { text: 'use $ralplan is the workflow command; use $autopilot update the documentation', skills: ['autopilot'] },
952
+ { text: 'use $ralplan is the consensus-planning command; use autopilot mode.', skills: ['autopilot'] },
953
+ { text: 'use $ralplan is the consensus-planning command; then use $autopilot build it', skills: ['autopilot'] },
954
+ { text: 'use $ralplan is the consensus-planning command\nAutopilot mode is its alias.\n$team execute it', skills: ['team'] },
955
+ { text: 'use $ralplan/$autopilot are workflow commands\n$team execute it', skills: ['team'] },
956
+ { text: 'use $ralplan is the consensus-planning command. Use autopilot mode.', skills: ['autopilot'] },
957
+ { text: 'use $ralplan is the consensus-planning command; autopilot mode.', skills: ['autopilot'] },
958
+ { text: 'use $ralplan is the consensus-planning command; use $autopilot build it', skills: ['autopilot'] },
959
+ { text: 'use $ralplan is the workflow command; but use $autopilot build it', skills: ['autopilot'] },
960
+ { text: '$ralplan; $autopilot is prohibited', skills: ['ralplan'] },
961
+ { text: '$ralplan; $autopilot is documented in the guide.', skills: ['ralplan'] },
962
+ { text: `use $ralplan is the workflow command;${' '.repeat(161)}use $autopilot build it`, skills: ['autopilot'] },
963
+ { text: `$ralplan; $autopilot${' '.repeat(193)}is prohibited`, skills: ['ralplan'] },
964
+ { text: 'For instance: manual mode is slower。 Use autopilot mode.', skills: ['autopilot'] },
965
+ { text: 'use $ralplan is the workflow command; autopilot mode is its alias; $team execute it', skills: ['team'] },
966
+ { text: 'use $ralplan is the workflow command; autopilot mode is documented in the guide; $team execute it', skills: ['team'] },
967
+ { text: 'use $ralplan is the workflow command; autopilot mode is workflow documentation; use $ralph execute it', skills: ['ralph'] },
968
+ { text: `use $ralplan is the workflow command; use${' '.repeat(161)}$autopilot build it`, skills: ['autopilot'] },
969
+ { text: 'use $ralplan is the consensus-planning command! run $autopilot', skills: ['autopilot'] },
970
+ { text: 'use $ralplan is the consensus-planning command? run $autopilot', skills: ['autopilot'] },
971
+ { text: 'Autopilot mode is workflow documentation.\n$ralplan plan it', skills: ['ralplan'] },
972
+ { text: 'use $ralplan is the workflow command, but use $autopilot build it', skills: ['autopilot'] },
973
+ { text: 'use $ralplan is the workflow command, but use $autopilot build it', skills: ['autopilot'] },
974
+ { text: 'use $ralplan is the workflow command، but use $autopilot build it', skills: ['autopilot'] },
975
+ { text: 'use $ralplan is the workflow command、 but use $autopilot build it', skills: ['autopilot'] },
976
+ { text: 'Do not run $ralplan، use $autopilot build it', skills: ['autopilot'] },
977
+ { text: 'Do not run $ralplan、 use $autopilot build it', skills: ['autopilot'] },
978
+ { text: 'The docs mention autopilot mode.\n$ralplan plan it', skills: ['ralplan'] },
979
+ { text: 'The docs mention autopilot mode; use $ralplan plan it', skills: ['ralplan'] },
980
+ { text: 'Autopilot mode is workflow documentation; use $ralplan plan it', skills: ['ralplan'] },
981
+ { text: '[docs]: $ralplan\n$autopilot build it', skills: ['autopilot'] },
982
+ { text: '[docs]: /prompts:architect\n$autopilot build it', skills: ['autopilot'] },
983
+ ]) {
984
+ assert.deepEqual(classifyKeywordInput(testCase.text).matches.map((match) => match.skill), testCase.skills, testCase.text);
985
+ }
986
+ assert.deepEqual(detectKeywords('- $ralplan plan this').map((match) => match.skill), ['ralplan']);
987
+ });
988
+ it('requires implicit workflow phrases to be active, unmasked, and non-negated', () => {
989
+ for (const text of [
990
+ 'Do not use autopilot mode.',
991
+ 'Do not use deep interview, autopilot mode.',
992
+ 'Do not use deep interview, nor autopilot mode.',
993
+ 'Avoid autopilot mode.',
994
+ 'Neither deep interview nor autopilot mode.',
995
+ 'Autopilot mode is not allowed.',
996
+ "Autopilot mode isn't allowed.",
997
+ 'Autopilot mode cannot be used.',
998
+ 'Autopilot mode is prohibited.',
999
+ 'No autopilot mode.',
1000
+ 'The docs call this "autopilot mode".',
1001
+ '```\nautopilot mode\n```',
1002
+ '- ```\n autopilot mode\n ```',
1003
+ '- ```\n first example\n- ```\n autopilot mode\n ```',
1004
+ 'use /prompts:architect autopilot mode',
1005
+ '- autopilot mode is a workflow command',
1006
+ 'The reference describes autopilot mode.',
1007
+ 'This documents autopilot mode.',
1008
+ 'The guide says do not use deep interview but instead use autopilot mode.',
1009
+ '[autopilot mode](./docs.md)',
1010
+ '## Autopilot mode',
1011
+ '| autopilot mode | workflow command |',
1012
+ 'Autopilot mode — autonomous workflow command',
1013
+ 'Autopilot mode / deep interview are workflow commands.',
1014
+ 'autopilot mode is workflow documentation.',
1015
+ '- autopilot mode: autonomous workflow command',
1016
+ '`autopilot mode`',
1017
+ '> autopilot mode',
1018
+ '„autopilot mode“',
1019
+ '‚autopilot mode‘',
1020
+ '문서autopilot mode한글',
1021
+ 'autopilot mode는 사용하지 마세요',
1022
+ 'Autopilot mode and deep interview are prohibited.',
1023
+ 'Autopilot mode should be avoided.',
1024
+ 'Example: do not use deep interview but instead use autopilot mode.',
1025
+ ''autopilot mode'',
1026
+ '\\$oh-my-codex:autopilot mode',
1027
+ 'Autopilot mode, deep interview, and team are prohibited.',
1028
+ 'For example, do not use deep interview but instead use autopilot mode.',
1029
+ 'According to the docs, do not use deep interview but instead use autopilot mode.',
1030
+ 'Don’t use autopilot mode.',
1031
+ 'Don't use autopilot mode.',
1032
+ 'Autopilot mode isn’t allowed.',
1033
+ 'Autopilot mode is to be avoided.',
1034
+ 'Autopilot mode was to be disabled.',
1035
+ 'Autopilot mode and the deep interview workflow are prohibited.',
1036
+ 'As an example, do not use deep interview but instead use autopilot mode.',
1037
+ 'For instance, do not use deep interview but instead use autopilot mode.',
1038
+ '[autopilot mode][docs]',
1039
+ 'Autopilot mode and deep interview workflows are prohibited.',
1040
+ 'As an example, ignore the docs and use autopilot mode.',
1041
+ 'For instance, ignore the docs and use autopilot mode.',
1042
+ '[autopilot mode]: ./docs',
1043
+ '[autopilot mode]',
1044
+ 'Autopilot mode\n===',
1045
+ '$ralplan | workflow\n--- | ---',
1046
+ 'Mode | Meaning\n--- | ---\nautopilot mode | autonomous workflow command',
1047
+ 'Autopilot mode as well as deep interview workflows are prohibited.',
1048
+ 'Autopilot mode along with deep interview workflows are prohibited.',
1049
+ 'Autopilot mode together with deep interview workflows are prohibited.',
1050
+ 'Autopilot mode & deep interview workflows are prohibited.',
1051
+ 'As an example: ignore the docs and use autopilot mode.',
1052
+ 'For instance — ignore the docs and use autopilot mode.',
1053
+ 'For example - ignore the docs and use autopilot mode.',
1054
+ 'For instance: use autopilot mode.',
1055
+ 'For instance — use autopilot mode.',
1056
+ 'Mode | Meaning\n--- | ---\nmanual | docs\nautopilot mode | autonomous workflow command',
1057
+ 'See [autopilot mode] for details.\n\n[autopilot mode]: ./docs',
1058
+ 'Autopilot mode, as well as deep interview workflows, are prohibited.',
1059
+ 'Autopilot mode, along with deep interview workflows, are prohibited.',
1060
+ 'Autopilot mode, together with deep interview workflows, are prohibited.',
1061
+ 'See [autopilot mode] for details.\n\n[autopilot mode]: ./docs',
1062
+ 'Ignore autopilot mode.',
1063
+ 'Skip autopilot mode.',
1064
+ 'Exclude autopilot mode.',
1065
+ '$ralplan is prohibited.',
1066
+ '$ralplan should not be run.',
1067
+ '$ralplan and $autopilot are prohibited.',
1068
+ 'See [ẞ autopilot mode] for details.\n\n[SS autopilot mode]: ./docs',
1069
+ 'See [foo\\] autopilot mode] for details.\n\n[foo\\] autopilot mode]: ./docs',
1070
+ 'For instance: in version 1.2, use autopilot mode.',
1071
+ 'For instance: e.g. use autopilot mode.',
1072
+ '$ralplan and the $autopilot workflow are prohibited.',
1073
+ '$ralplan and autopilot mode are prohibited.',
1074
+ 'See [autopilot mode] for details.\n\n> [autopilot mode]: ./docs',
1075
+ 'See [autopilot mode] for details.\n\n- [autopilot mode]: ./docs',
1076
+ 'See [autopilot mode] for details.\n\n> [autopilot mode]: ./docs',
1077
+ '- $ralplan',
1078
+ '1. autopilot mode',
1079
+ 'See [autopilot mode] for details.\n\n> [autopilot mode]: ./docs',
1080
+ 'See [autopilot mode] for details.\n\n[autopilot mode]:\n ./docs',
1081
+ '- \t$ralplan',
1082
+ '- - autopilot mode',
1083
+ '$ralplan is also prohibited.',
1084
+ 'Autopilot mode is still prohibited.',
1085
+ '- > autopilot mode',
1086
+ '- - - - - - - - - autopilot mode',
1087
+ '1234. > autopilot mode',
1088
+ ]) {
1089
+ const classification = classifyKeywordInput(text);
1090
+ assert.deepEqual(classification.matches, [], text);
1091
+ assert.deepEqual(classification.implicitMatches, [], text);
1092
+ }
1093
+ for (const testCase of [
1094
+ { text: 'Use autopilot mode.', skills: ['autopilot'] },
1095
+ { text: 'List files and use autopilot mode.', skills: ['autopilot'] },
1096
+ { text: "No, don't stop.", skills: ['ralph'] },
1097
+ { text: 'Do not use deep interview, but use autopilot mode.', skills: ['autopilot'] },
1098
+ { text: 'Do not use deep interview but use autopilot mode.', skills: ['autopilot'] },
1099
+ { text: 'Do not use deep interview but instead use autopilot mode.', skills: ['autopilot'] },
1100
+ { text: 'Do not use deep interview — instead use autopilot mode.', skills: ['autopilot'] },
1101
+ { text: 'Autopilot mode is workflow documentation.\nUse autopilot mode.', skills: ['autopilot'] },
1102
+ { text: 'Use /prompts:architect.\nUse autopilot mode.', skills: ['autopilot'] },
1103
+ { text: 'Ignore the quoted "/prompts:architect" and use autopilot mode.', skills: ['autopilot'] },
1104
+ { text: 'Ignore the quoted "/prompts:architect" then use autopilot mode.', skills: ['autopilot'] },
1105
+ { text: 'Do not run $ralplan but instead use autopilot mode.', skills: ['autopilot'] },
1106
+ { text: 'Ignore "$ralplan" and use autopilot mode.', skills: ['autopilot'] },
1107
+ { text: 'Ignore \\/prompts:architect and use autopilot mode.', skills: ['autopilot'] },
1108
+ { text: 'See https://example.com/prompts:architect and use autopilot mode.', skills: ['autopilot'] },
1109
+ { text: 'Autopilot mode should be used.', skills: ['autopilot'] },
1110
+ { text: 'Autopilot mode must be enabled.', skills: ['autopilot'] },
1111
+ { text: 'Autopilot mode can be run.', skills: ['autopilot'] },
1112
+ { text: 'User's request: use autopilot mode.', skills: ['autopilot'] },
1113
+ { text: 'See [/prompts:architect](./docs.md) and use autopilot mode.', skills: ['autopilot'] },
1114
+ { text: 'Use autopilot mode, while deep interview is prohibited.', skills: ['autopilot'] },
1115
+ { text: 'Read the docs. Use autopilot mode.', skills: ['autopilot'] },
1116
+ { text: 'The docs are stale; use autopilot mode.', skills: ['autopilot'] },
1117
+ { text: 'Ignore the docs, use autopilot mode.', skills: ['autopilot'] },
1118
+ { text: 'Ignore \\/prompts:architect\n$ralplan plan it', skills: ['ralplan'] },
1119
+ { text: 'See https://example.com/prompts:architect\n$ralplan plan it', skills: ['ralplan'] },
1120
+ { text: 'See [/prompts:architect](./docs.md)\n$ralplan plan it', skills: ['ralplan'] },
1121
+ { text: 'See [$ralplan](./docs.md) and use autopilot mode.', skills: ['autopilot'] },
1122
+ { text: 'See https://example.com/$ralplan and use autopilot mode.', skills: ['autopilot'] },
1123
+ { text: 'Use autopilot mode, and deep interview is prohibited.', skills: ['autopilot'] },
1124
+ { text: 'Ignore the docs and use autopilot mode.', skills: ['autopilot'] },
1125
+ { text: 'Ignore \\/prompts:architect; use $ralplan plan it', skills: ['ralplan'] },
1126
+ { text: 'See https://example.com/prompts:architect; use $ralplan plan it', skills: ['ralplan'] },
1127
+ { text: 'See [/prompts:architect](./docs.md); use $ralplan plan it', skills: ['ralplan'] },
1128
+ { text: 'See [/prompts:architect][docs]; use $ralplan plan it', skills: ['ralplan'] },
1129
+ { text: '## /prompts:architect\n$ralplan plan it', skills: ['ralplan'] },
1130
+ { text: '| /prompts:architect |\n$ralplan plan it', skills: ['ralplan'] },
1131
+ { text: '## $ralplan\nUse autopilot mode.', skills: ['autopilot'] },
1132
+ { text: '| $ralplan |\nUse autopilot mode.', skills: ['autopilot'] },
1133
+ { text: 'See [$ralplan][docs] and use autopilot mode.', skills: ['autopilot'] },
1134
+ { text: 'See C:\\docs\\$ralplan and use autopilot mode.', skills: ['autopilot'] },
1135
+ { text: 'Users' request: use autopilot mode.', skills: ['autopilot'] },
1136
+ { text: 'Use autopilot mode, and deep interview should be avoided.', skills: ['autopilot'] },
1137
+ { text: 'Ignore the docs but use autopilot mode.', skills: ['autopilot'] },
1138
+ { text: '[/prompts:architect]: ./docs\n$ralplan plan it', skills: ['ralplan'] },
1139
+ { text: '[/prompts:architect]\n$ralplan plan it', skills: ['ralplan'] },
1140
+ { text: '[$ralplan]: ./docs\nUse autopilot mode.', skills: ['autopilot'] },
1141
+ { text: '[$ralplan]\nUse autopilot mode.', skills: ['autopilot'] },
1142
+ { text: '$ralplan\n===\nUse autopilot mode.', skills: ['autopilot'] },
1143
+ { text: '$ralplan | workflow\n--- | ---\nUse autopilot mode.', skills: ['autopilot'] },
1144
+ { text: '## $ralplan\n$autopilot build it', skills: ['autopilot'] },
1145
+ { text: 'See [$ralplan](./docs.md); use $autopilot build it', skills: ['autopilot'] },
1146
+ { text: 'See C:\\docs\\$ralplan; use $autopilot build it', skills: ['autopilot'] },
1147
+ { text: 'Mode | Meaning\n--- | ---\n$ralplan | planning\n$autopilot build it', skills: ['autopilot'] },
1148
+ { text: 'See [$ralplan] for details.\n\n[$ralplan]: ./docs\nUse autopilot mode.', skills: ['autopilot'] },
1149
+ { text: 'Ignore "$ralplan" and use $autopilot build it', skills: ['autopilot'] },
1150
+ { text: 'Ignore `$ralplan` and use $autopilot build it', skills: ['autopilot'] },
1151
+ { text: 'See [$ralplan ] for details.\n\n[$ralplan]: ./docs\nUse autopilot mode.', skills: ['autopilot'] },
1152
+ { text: 'For instance: manual mode is slower. Use autopilot mode.', skills: ['autopilot'] },
1153
+ { text: 'See [`$ralplan`](./docs.md) and use $autopilot build it', skills: ['autopilot'] },
1154
+ { text: 'Ignore deep interview and use autopilot mode.', skills: ['autopilot'] },
1155
+ { text: 'See [docs](https://example.com/$ralplan) and use $autopilot build it', skills: ['autopilot'] },
1156
+ { text: 'See [docs](./docs.md "$ralplan reference") and use $autopilot build it', skills: ['autopilot'] },
1157
+ { text: 'See [docs](https://example.com/(v1)/$ralplan) and use $autopilot build it', skills: ['autopilot'] },
1158
+ { text: 'See [$ralplan](https://example.com/(v1)) and use $autopilot build it', skills: ['autopilot'] },
1159
+ { text: 'See [docs](./docs.md "$ralplan (reference") and use $autopilot build it', skills: ['autopilot'] },
1160
+ { text: 'See [autopilot mode] for details.\n\n> [autopilot mode]: ./docs', skills: ['autopilot'] },
1161
+ { text: '-\t $ralplan', skills: ['ralplan'] },
1162
+ { text: '$ralplan is prohibited but use $autopilot build it', skills: ['autopilot'] },
1163
+ { text: '$ralplan is prohibited and use $autopilot build it', skills: ['autopilot'] },
1164
+ { text: '- - $ralplan plan it', skills: ['ralplan'] },
1165
+ { text: '1234. $ralplan plan it', skills: ['ralplan'] },
1166
+ { text: '- ```\n $ralplan\n ```\n$autopilot build it', skills: ['autopilot'] },
1167
+ { text: '- - $ralplan is the consensus-planning command\n$autopilot build it', skills: ['autopilot'] },
1168
+ ]) {
1169
+ assert.deepEqual(classifyKeywordInput(testCase.text).matches.map((match) => match.skill), testCase.skills, testCase.text);
1170
+ }
1171
+ });
1172
+ it('defines punctuation-separated workflow directives as one ordered block', () => {
1173
+ assert.deepEqual(detectKeywords('$ralplan, $autopilot; $team build it').map((match) => match.skill), ['ralplan', 'autopilot', 'team']);
1174
+ assert.deepEqual(detectKeywords('$ralplan plan it, then $team execute').map((match) => match.skill), ['ralplan']);
1175
+ });
1176
+ it('fails closed for balanced and unbalanced quote pairs', () => {
1177
+ const quotePairs = [
1178
+ { name: 'ASCII double', opening: '"', closing: '"' },
1179
+ { name: 'ASCII single', opening: "'", closing: "'" },
1180
+ { name: 'curly double', opening: '“', closing: '”' },
1181
+ { name: 'curly single', opening: '‘', closing: '’' },
1182
+ { name: 'guillemets', opening: '«', closing: '»' },
1183
+ { name: 'Japanese corner', opening: '「', closing: '」' },
1184
+ { name: 'Japanese nested', opening: '『', closing: '』' },
1185
+ { name: 'fullwidth', opening: '"', closing: '"' },
1186
+ { name: 'fullwidth single', opening: ''', closing: ''' },
1187
+ { name: 'single guillemets', opening: '‹', closing: '›' },
1188
+ ];
1189
+ for (const quotePair of quotePairs) {
1190
+ for (const closing of [quotePair.closing, '']) {
1191
+ const text = `${quotePair.opening}$ralplan${closing}`;
1192
+ const classification = classifyKeywordInput(text);
1193
+ assert.deepEqual(classification.matches, [], `${quotePair.name}: ${JSON.stringify(text)}`);
1194
+ assert.deepEqual(classification.candidates[0]?.reasons, ['quote', 'not-leading-region'], `${quotePair.name}: ${JSON.stringify(text)}`);
1195
+ }
1196
+ }
1197
+ });
1198
+ it('keeps fences and escape parity inert without semantic phrase matching', () => {
1199
+ const structuralCases = [
1200
+ { text: '```\n$ralplan\n```', reason: 'fenced-code' },
1201
+ { text: '~~~\n$ralplan\n~~~', reason: 'fenced-code' },
1202
+ { text: '```\n$ralplan', reason: 'fenced-code' },
1203
+ { text: '~~~\n$ralplan', reason: 'fenced-code' },
1204
+ { text: ' $ralplan plan this change', reason: 'indented-code' },
1205
+ { text: '\t$ralplan plan this change', reason: 'indented-code' },
1206
+ { text: ' \t$ralplan plan this change', reason: 'indented-code' },
1207
+ { text: ' \t$ralplan plan this change', reason: 'indented-code' },
1208
+ { text: ' \t$ralplan plan this change', reason: 'indented-code' },
1209
+ ];
1210
+ for (const testCase of structuralCases) {
1211
+ const classification = classifyKeywordInput(testCase.text);
1212
+ assert.deepEqual(classification.matches, [], testCase.text);
1213
+ assert.deepEqual(classification.candidates[0]?.reasons, [testCase.reason, 'not-leading-region'], testCase.text);
1214
+ }
1215
+ const escapeCases = [
1216
+ { text: '\\$ralplan', reasons: ['escaped', 'not-leading-region'] },
1217
+ { text: '\\\\$ralplan', reasons: ['not-leading-region'] },
1218
+ { text: '\\\\\\$ralplan', reasons: ['escaped', 'not-leading-region'] },
1219
+ ];
1220
+ for (const testCase of escapeCases) {
1221
+ const classification = classifyKeywordInput(testCase.text);
1222
+ assert.deepEqual(classification.matches, [], testCase.text);
1223
+ assert.deepEqual(classification.candidates[0]?.reasons, testCase.reasons, testCase.text);
1224
+ }
1225
+ });
1226
+ it('respects quote escape parity and fence container prefixes', () => {
1227
+ const escapedQuote = '"$ralplan \\"; $autopilot build it';
1228
+ const escapedQuoteClassification = classifyKeywordInput(escapedQuote);
1229
+ assert.deepEqual(escapedQuoteClassification.matches, []);
1230
+ assert.deepEqual(escapedQuoteClassification.candidates.map((candidate) => candidate.reasons), [
1231
+ ['quote', 'not-leading-region'],
1232
+ ['quote', 'not-leading-region'],
1233
+ ]);
1234
+ const evenBackslashQuote = '"$ralplan \\\\"; use $autopilot build it';
1235
+ assert.deepEqual(classifyKeywordInput(evenBackslashQuote).matches.map((match) => match.skill), ['autopilot']);
1236
+ const unquotedFence = '```\n$ralplan\n> ```\n$autopilot build it';
1237
+ assert.deepEqual(classifyKeywordInput(unquotedFence).matches, []);
1238
+ const unquotedFenceCandidates = classifyKeywordInput(unquotedFence).candidates;
1239
+ assert.ok(unquotedFenceCandidates.every((candidate) => candidate.reasons.includes('fenced-code')));
1240
+ const quotedFence = '> ```\n> $ralplan\n```\n> $autopilot build it';
1241
+ assert.deepEqual(classifyKeywordInput(quotedFence).matches, []);
1242
+ assert.ok(classifyKeywordInput(quotedFence).candidates.every((candidate) => candidate.reasons.length > 0));
1243
+ const matchingQuotedFence = '> ```\n> $ralplan\n> ```\n$autopilot build it';
1244
+ assert.deepEqual(classifyKeywordInput(matchingQuotedFence).matches.map((match) => match.skill), ['autopilot']);
1245
+ const nestedQuotedFence = '> > ```\n> > $ralplan\n> ```\n$autopilot build it';
1246
+ assert.deepEqual(classifyKeywordInput(nestedQuotedFence).matches, []);
1247
+ });
1248
+ it('requires valid fence closers and directive clause prefixes after inert mentions', () => {
1249
+ for (const text of [
1250
+ '```\n$ralplan\n``` still code\n$autopilot build it',
1251
+ '> ```\n> $ralplan\n> ``` still code\n> $autopilot build it',
1252
+ ]) {
1253
+ const classification = classifyKeywordInput(text);
1254
+ assert.deepEqual(classification.matches, [], text);
1255
+ assert.ok(classification.candidates.every((candidate) => candidate.reasons.length > 0), text);
1256
+ }
1257
+ for (const testCase of [
1258
+ { text: '"$ralplan"; now $autopilot build it', skills: ['autopilot'] },
1259
+ { text: '"$ralplan"; please use $autopilot build it', skills: ['autopilot'] },
1260
+ { text: '"$ralplan". We only document $autopilot behavior', skills: [] },
1261
+ { text: '"$ralplan". The $autopilot workflow is documented', skills: [] },
1262
+ ]) {
1263
+ assert.deepEqual(classifyKeywordInput(testCase.text).matches.map((match) => match.skill), testCase.skills, testCase.text);
1264
+ }
1265
+ });
1266
+ it('terminates fenced, inline, quote, and blockquote regions on every logical line ending', () => {
1267
+ const lineTerminators = ['\r', '\r\n', '\u2028', '\u2029'];
1268
+ for (const lineTerminator of lineTerminators) {
1269
+ const label = JSON.stringify(lineTerminator);
1270
+ const fenced = classifyKeywordInput(`~~~${lineTerminator}$ralplan${lineTerminator}~~~`);
1271
+ assert.deepEqual(fenced.candidates[0]?.reasons, ['fenced-code', 'not-leading-region'], label);
1272
+ const blockquoted = classifyKeywordInput(`> $ralplan${lineTerminator}$team`);
1273
+ assert.deepEqual(blockquoted.candidates[0]?.reasons, ['blockquote', 'not-leading-region'], label);
1274
+ assert.deepEqual(blockquoted.candidates[1]?.reasons, [], label);
1275
+ assert.deepEqual(blockquoted.matches.map((match) => match.skill), ['team'], label);
1276
+ const inline = classifyKeywordInput(`\`$ralplan${lineTerminator}$team`);
1277
+ assert.deepEqual(inline.candidates[0]?.reasons, ['inline-code', 'not-leading-region'], label);
1278
+ assert.deepEqual(inline.candidates[1]?.reasons, [], label);
1279
+ assert.deepEqual(inline.matches.map((match) => match.skill), ['team'], label);
1280
+ const quoted = classifyKeywordInput(`"$ralplan${lineTerminator}$team`);
1281
+ assert.deepEqual(quoted.candidates[0]?.reasons, ['quote', 'not-leading-region'], label);
1282
+ assert.deepEqual(quoted.candidates[1]?.reasons, [], label);
1283
+ assert.deepEqual(quoted.matches.map((match) => match.skill), ['team'], label);
1284
+ }
1285
+ });
1286
+ it('keeps candidate diagnostics bounded across thousands of inert ranges', () => {
1287
+ const count = 4_096;
1288
+ const classification = classifyKeywordInput('`$ralplan` '.repeat(count).trimEnd());
1289
+ assert.equal(classification.candidates.length, count);
1290
+ assert.deepEqual(classification.candidates[0]?.reasons, ['inline-code', 'not-leading-region']);
1291
+ assert.deepEqual(classification.candidates.at(-1)?.reasons, ['inline-code', 'not-leading-region']);
1292
+ assert.deepEqual(classification.matches, []);
1293
+ const shortcuts = classifyKeywordInput('See [$ralplan] for details.\n'.repeat(count) + '\n[$ralplan]: ./docs');
1294
+ assert.equal(shortcuts.candidates.length, count + 1);
1295
+ assert.deepEqual(shortcuts.matches, []);
1296
+ const nestedParents = classifyKeywordInput('"`x`\n'.repeat(count) + '$ralplan plan it');
1297
+ assert.deepEqual(nestedParents.matches, []);
1298
+ const unclosedTitles = classifyKeywordInput('[docs]: /target (\n'.repeat(count) + '$ralplan plan it');
1299
+ assert.deepEqual(unclosedTitles.matches, []);
1300
+ const documentationTokens = classifyKeywordInput(`use $ralplan is the workflow command for ${'$team '.repeat(count)}`);
1301
+ assert.equal(documentationTokens.candidates.length, count + 1);
1302
+ assert.deepEqual(documentationTokens.matches, []);
1303
+ const semicolonDocumentation = classifyKeywordInput(`use $ralplan is the workflow command${'; explanatory note'.repeat(count)}`);
1304
+ assert.deepEqual(semicolonDocumentation.matches, []);
1305
+ const candidateTailDocumentation = classifyKeywordInput(`use $ralplan is the workflow command${'; $team is filler'.repeat(count)} documentation`);
1306
+ assert.equal(candidateTailDocumentation.candidates.length, count + 1);
1307
+ assert.deepEqual(candidateTailDocumentation.matches.map((match) => match.skill), ['team']);
1308
+ const periodDocumentationStart = Date.now();
1309
+ const periodDocumentation = classifyKeywordInput('Autopilot mode is documented. '.repeat(count) + 'use $ralplan plan it');
1310
+ assert.deepEqual(periodDocumentation.matches.map((match) => match.skill), ['ralplan']);
1311
+ assert.ok(Date.now() - periodDocumentationStart < 2_000, 'period-heavy documentation scan must stay bounded');
1312
+ const contiguousChainStart = Date.now();
1313
+ const contiguousChain = classifyKeywordInput('$ralplan '.repeat(count).trimEnd());
1314
+ assert.equal(contiguousChain.candidates.length, count);
1315
+ assert.deepEqual(contiguousChain.matches.map((match) => match.skill), ['ralplan']);
1316
+ assert.ok(Date.now() - contiguousChainStart < 2_000, 'contiguous explicit chains must stay bounded');
1317
+ const coordinatedNegationStart = Date.now();
1318
+ const coordinatedNegation = classifyKeywordInput(Array.from({ length: count }, () => '$team').join(',') + ' are prohibited');
1319
+ assert.equal(coordinatedNegation.candidates.length, count);
1320
+ assert.deepEqual(coordinatedNegation.matches, []);
1321
+ assert.ok(Date.now() - coordinatedNegationStart < 2_000, 'coordinated postposed negation must stay bounded');
1322
+ });
1323
+ it('suppresses prose, multilingual, documentation, quoted, escaped, and code candidates without a phrase classifier', () => {
1324
+ const cases = [
1325
+ 'Do not run $autopilot',
1326
+ "don't use $autopilot",
1327
+ 'without $ralplan',
1328
+ 'Не запускай $autopilot',
1329
+ 'Не используй $autopilot',
1330
+ '実行しないで $autopilot',
1331
+ '使わないで $autopilot',
1332
+ 'Documentation example: $ralplan',
1333
+ '($ralplan) is an example',
1334
+ '"$autopilot" is an example',
1335
+ '`$ralph` is a literal',
1336
+ '```\n$team\n```',
1337
+ ' $ralplan plan this change',
1338
+ '\t$ralplan plan this change',
1339
+ '> $ultrawork is quoted',
1340
+ '\\$autopilot',
1341
+ 'Prose\n$ralplan implement this',
1342
+ 'Prose\n$ralplan\n$team',
1343
+ ];
1344
+ for (const text of cases) {
1345
+ const classification = classifyKeywordInput(text);
1346
+ assert.deepEqual(classification.matches, [], text);
1347
+ assert.equal(classification.hasExplicitLikeInvocation, true, text);
1348
+ assert.ok(classification.candidates.every((candidate) => candidate.reasons.includes('not-leading-region')), text);
1349
+ }
1350
+ });
1351
+ it('applies marked-answer, accepted-direct, prompts, and explicit-like precedence in order', () => {
1352
+ const cases = [
1353
+ { text: '[omx question answered] $ralplan', reservedInput: 'omx-question-answered', skills: [] },
1354
+ { text: '$ralplan plan this; /prompts:architect review', reservedInput: null, skills: ['ralplan'] },
1355
+ { text: '$unknown /prompts:architect review', reservedInput: null, skills: [] },
1356
+ { text: '/prompts:architect, keep going', reservedInput: 'prompts', skills: [] },
1357
+ { text: '/prompts:unknown $ralplan plan this', reservedInput: 'prompts', skills: [] },
1358
+ { text: 'Prose\n/prompts:architect\n$ralplan plan this', reservedInput: null, skills: [] },
1359
+ { text: 'Do not run $autopilot', reservedInput: null, skills: [] },
1360
+ ];
1361
+ for (const testCase of cases) {
1362
+ const classification = classifyKeywordInput(testCase.text);
1363
+ assert.equal(classification.reservedInput, testCase.reservedInput, testCase.text);
1364
+ assert.deepEqual(classification.matches.map((match) => match.skill), testCase.skills, testCase.text);
1365
+ }
1366
+ });
1367
+ it('binds G1 and G2 direct classification fields exactly', () => {
1368
+ const cases = [
1369
+ {
1370
+ text: '$ralplan, $autopilot; $team',
1371
+ reservedInput: null,
1372
+ skills: ['ralplan', 'autopilot', 'team'],
1373
+ candidates: [
1374
+ { rawKeyword: '$ralplan', reasons: [] },
1375
+ { rawKeyword: '$autopilot', reasons: [] },
1376
+ { rawKeyword: '$team', reasons: [] },
1377
+ ],
1378
+ },
1379
+ {
1380
+ text: '$ultrawork $ulw',
1381
+ reservedInput: null,
1382
+ skills: ['ultrawork'],
1383
+ candidates: [
1384
+ { rawKeyword: '$ultrawork', reasons: [] },
1385
+ { rawKeyword: '$ulw', reasons: [] },
1386
+ ],
1387
+ },
1388
+ {
1389
+ text: 'use $ralplan is the consensus-planning command',
1390
+ reservedInput: null,
1391
+ skills: [],
1392
+ candidates: [{ rawKeyword: '$ralplan', reasons: ['not-leading-region'] }],
1393
+ },
1394
+ {
1395
+ text: 'do not start $autopilot — café',
1396
+ reservedInput: null,
1397
+ skills: [],
1398
+ candidates: [{ rawKeyword: '$autopilot', reasons: ['not-leading-region'] }],
1399
+ },
1400
+ ];
1401
+ for (const testCase of cases) {
1402
+ const classification = classifyKeywordInput(testCase.text);
1403
+ assert.equal(classification.reservedInput, testCase.reservedInput, testCase.text);
1404
+ assert.deepEqual(classification.matches.map((match) => match.skill), testCase.skills, testCase.text);
1405
+ assert.deepEqual(classification.candidates.map((candidate) => ({ rawKeyword: candidate.rawKeyword, reasons: candidate.reasons })), testCase.candidates, testCase.text);
1406
+ }
1407
+ });
1408
+ it('deduplicates implicit aliases by skill without changing the stable winner', () => {
1409
+ const text = 'autopilot mode; build me a dashboard';
1410
+ const classification = classifyKeywordInput(text);
1411
+ assert.deepEqual(classification.implicitMatches, [
1412
+ { keyword: 'autopilot', skill: 'autopilot', priority: 10 },
1413
+ ]);
1414
+ assert.deepEqual(classification.matches, classification.implicitMatches);
1415
+ assert.deepEqual(detectKeywords(text), classification.implicitMatches);
1416
+ });
1417
+ it('freezes classifications and retains ordered inert diagnostics', () => {
1418
+ assert.equal(Object.isFrozen(KEYWORD_INERT_DIAGNOSTIC_ORDER), true);
1419
+ assert.deepEqual(KEYWORD_INERT_DIAGNOSTIC_ORDER, [
1420
+ 'fenced-code',
1421
+ 'indented-code',
1422
+ 'blockquote',
1423
+ 'inline-code',
1424
+ 'quote',
1425
+ 'escaped',
1426
+ 'not-leading-region',
1427
+ ]);
1428
+ const classification = classifyKeywordInput('"\\$ralph and $team"');
1429
+ assert.equal(Object.isFrozen(classification), true);
1430
+ assert.equal(Object.isFrozen(classification.candidates), true);
1431
+ assert.equal(Object.isFrozen(classification.candidates[0]), true);
1432
+ assert.equal(Object.isFrozen(classification.candidates[0]?.reasons), true);
1433
+ assert.deepEqual(classification.candidates[0]?.reasons, ['quote', 'escaped', 'not-leading-region']);
1434
+ assert.deepEqual(classification.candidates[1]?.reasons, ['quote', 'not-leading-region']);
1435
+ const quotedFence = classifyKeywordInput('> ```\n> $ralph\n> ```');
1436
+ assert.deepEqual(quotedFence.candidates[0]?.reasons, ['fenced-code', 'blockquote', 'not-leading-region']);
1437
+ const blockquotedInline = classifyKeywordInput('> `$ralplan`\n$team');
1438
+ assert.deepEqual(blockquotedInline.candidates[0]?.reasons, ['blockquote', 'inline-code', 'not-leading-region']);
1439
+ assert.deepEqual(blockquotedInline.candidates[1]?.reasons, []);
1440
+ assert.deepEqual(blockquotedInline.matches.map((match) => match.skill), ['team']);
1441
+ });
1442
+ it('passes a supplied classification through recording, rejects mismatched text, and leaves rejected state bytes untouched', async () => {
1443
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-classification-state-'));
1444
+ const stateDir = join(cwd, '.omx', 'state');
1445
+ const sessionId = 'session-classification-state';
1446
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1447
+ const statePath = join(sessionDir, SKILL_ACTIVE_STATE_FILE);
1448
+ const detailPath = join(sessionDir, 'autopilot-state.json');
1449
+ const rawState = '{"version":1,"active":true,"skill":"autopilot","keyword":"$autopilot","phase":"deep-interview","activated_at":"2026-01-01T00:00:00.000Z","updated_at":"2026-01-01T00:00:00.000Z"}';
1450
+ const rawDetail = '{"active":true,"mode":"autopilot","current_phase":"deep-interview"}';
1451
+ try {
1452
+ await mkdir(sessionDir, { recursive: true });
1453
+ await writeFile(statePath, rawState);
1454
+ await writeFile(detailPath, rawDetail);
1455
+ const text = 'Do not run $autopilot';
1456
+ const classification = classifyKeywordInput(text);
1457
+ const result = await recordSkillActivation({ stateDir, sourceCwd: cwd, sessionId, text, classification });
1458
+ assert.equal(result, null);
1459
+ assert.equal(await readFile(statePath, 'utf-8'), rawState);
1460
+ assert.equal(await readFile(detailPath, 'utf-8'), rawDetail);
1461
+ assert.equal(existsSync(join(stateDir, SKILL_ACTIVE_STATE_FILE)), false);
1462
+ await assert.rejects(recordSkillActivation({
1463
+ stateDir,
1464
+ sourceCwd: cwd,
1465
+ text: '$ralph',
1466
+ classification,
1467
+ }), /classification text does not match activation text/);
1468
+ assert.equal(await readFile(statePath, 'utf-8'), rawState);
1469
+ assert.equal(await readFile(detailPath, 'utf-8'), rawDetail);
1470
+ }
1471
+ finally {
1472
+ await rm(cwd, { recursive: true, force: true });
1473
+ }
1474
+ });
1475
+ it('does not create or mutate tracked workflow state for ineligible explicit candidates', async () => {
1476
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-ineligible-explicit-'));
1477
+ const stateDir = join(cwd, '.omx', 'state');
1478
+ const cases = [
1479
+ { name: 'no canonical state', text: '“$ralplan”', existingSkill: null },
1480
+ { name: 'active Ralplan same skill', text: 'without $ralplan', existingSkill: 'ralplan' },
1481
+ { name: 'active Autopilot same skill', text: "don't use $autopilot", existingSkill: 'autopilot' },
1482
+ { name: 'active Autopilot cross skill', text: 'without $ralplan', existingSkill: 'autopilot' },
1483
+ ];
1484
+ try {
1485
+ for (const [index, testCase] of cases.entries()) {
1486
+ const sessionId = `ineligible-${index}`;
1487
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1488
+ const statePath = join(sessionDir, SKILL_ACTIVE_STATE_FILE);
1489
+ const detailPath = join(sessionDir, 'autopilot-state.json');
1490
+ const rawState = testCase.existingSkill
1491
+ ? JSON.stringify({
1492
+ version: 1,
1493
+ active: true,
1494
+ skill: testCase.existingSkill,
1495
+ keyword: `$${testCase.existingSkill}`,
1496
+ phase: 'planning',
1497
+ session_id: sessionId,
1498
+ active_skills: [{ skill: testCase.existingSkill, phase: 'planning', active: true, session_id: sessionId }],
1499
+ })
1500
+ : null;
1501
+ const rawDetail = testCase.existingSkill === 'autopilot'
1502
+ ? '{"active":true,"mode":"autopilot","current_phase":"ralplan"}'
1503
+ : null;
1504
+ if (rawState) {
1505
+ await mkdir(sessionDir, { recursive: true });
1506
+ await writeFile(statePath, rawState);
1507
+ }
1508
+ if (rawDetail)
1509
+ await writeFile(detailPath, rawDetail);
1510
+ const classification = classifyKeywordInput(testCase.text);
1511
+ assert.equal(classification.hasExplicitLikeInvocation, true, testCase.name);
1512
+ const result = await recordSkillActivation({
1513
+ stateDir,
1514
+ sourceCwd: cwd,
1515
+ sessionId,
1516
+ text: testCase.text,
1517
+ classification,
1518
+ });
1519
+ assert.equal(result, null, testCase.name);
1520
+ assert.equal(existsSync(join(stateDir, SKILL_ACTIVE_STATE_FILE)), false, testCase.name);
1521
+ if (rawState)
1522
+ assert.equal(await readFile(statePath, 'utf-8'), rawState, testCase.name);
1523
+ else
1524
+ assert.equal(existsSync(statePath), false, testCase.name);
1525
+ if (rawDetail)
1526
+ assert.equal(await readFile(detailPath, 'utf-8'), rawDetail, testCase.name);
1527
+ else
1528
+ assert.equal(existsSync(detailPath), false, testCase.name);
1529
+ }
1530
+ }
1531
+ finally {
1532
+ await rm(cwd, { recursive: true, force: true });
1533
+ }
1534
+ });
1535
+ it('persists marked answers only for eligible active workflows', async () => {
1536
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-marked-answer-'));
1537
+ const stateDir = join(cwd, '.omx', 'state');
1538
+ const cases = [
1539
+ { skill: 'autopilot', persists: true },
1540
+ { skill: 'ralplan', persists: false },
1541
+ ];
1542
+ try {
1543
+ for (const [index, testCase] of cases.entries()) {
1544
+ const sessionId = `marked-answer-${index}`;
1545
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1546
+ const statePath = join(sessionDir, SKILL_ACTIVE_STATE_FILE);
1547
+ const rawState = JSON.stringify({
1548
+ version: 1,
1549
+ active: true,
1550
+ skill: testCase.skill,
1551
+ keyword: `$${testCase.skill}`,
1552
+ phase: 'planning',
1553
+ activated_at: '2026-01-01T00:00:00.000Z',
1554
+ updated_at: '2026-01-01T00:00:00.000Z',
1555
+ session_id: sessionId,
1556
+ active_skills: [{ skill: testCase.skill, phase: 'planning', active: true, session_id: sessionId }],
1557
+ });
1558
+ await mkdir(sessionDir, { recursive: true });
1559
+ await writeFile(statePath, rawState);
1560
+ const text = '[omx question answered] yes';
1561
+ const classification = classifyKeywordInput(text);
1562
+ assert.equal(classification.reservedInput, 'omx-question-answered');
1563
+ const result = await recordSkillActivation({
1564
+ stateDir,
1565
+ sourceCwd: cwd,
1566
+ sessionId,
1567
+ text,
1568
+ classification,
1569
+ nowIso: '2026-02-01T00:00:00.000Z',
1570
+ });
1571
+ if (testCase.persists) {
1572
+ assert.equal(result?.skill, 'autopilot');
1573
+ assert.notEqual(await readFile(statePath, 'utf-8'), rawState);
1574
+ assert.equal(existsSync(join(sessionDir, 'autopilot-state.json')), true);
1575
+ }
1576
+ else {
1577
+ assert.equal(result, null);
1578
+ assert.equal(await readFile(statePath, 'utf-8'), rawState);
1579
+ assert.equal(existsSync(join(sessionDir, 'ralplan-state.json')), false);
1580
+ }
1581
+ }
1582
+ }
1583
+ finally {
1584
+ await rm(cwd, { recursive: true, force: true });
1585
+ }
1586
+ });
1587
+ it('keeps terminal marked answers from restarting stale active workflows', async () => {
1588
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-terminal-marked-answer-'));
1589
+ const stateDir = join(cwd, '.omx', 'state');
1590
+ const sessionId = 'terminal-marked-answer';
1591
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1592
+ const statePath = join(sessionDir, SKILL_ACTIVE_STATE_FILE);
1593
+ const detailPath = join(sessionDir, 'autopilot-state.json');
1594
+ const rawState = JSON.stringify({
1595
+ version: 1,
1596
+ active: true,
1597
+ skill: 'autopilot',
1598
+ keyword: '$autopilot',
1599
+ phase: 'completing',
1600
+ activated_at: '2026-01-01T00:00:00.000Z',
1601
+ updated_at: '2026-01-01T00:00:00.000Z',
1602
+ session_id: sessionId,
1603
+ active_skills: [{ skill: 'autopilot', phase: 'completing', active: true, session_id: sessionId }],
1604
+ });
1605
+ const rawDetail = JSON.stringify({
1606
+ mode: 'autopilot',
1607
+ active: false,
1608
+ current_phase: 'complete',
1609
+ completed_at: '2026-01-01T00:00:00.000Z',
1610
+ session_id: sessionId,
1611
+ });
1612
+ try {
1613
+ await mkdir(sessionDir, { recursive: true });
1614
+ await writeFile(statePath, rawState);
1615
+ await writeFile(detailPath, rawDetail);
1616
+ const text = '[omx question answered] yes';
1617
+ const result = await recordSkillActivation({
1618
+ stateDir,
1619
+ sourceCwd: cwd,
1620
+ sessionId,
1621
+ text,
1622
+ classification: classifyKeywordInput(text),
1623
+ });
1624
+ assert.equal(result, null);
1625
+ assert.equal(await readFile(statePath, 'utf-8'), rawState);
1626
+ assert.equal(await readFile(detailPath, 'utf-8'), rawDetail);
1627
+ }
1628
+ finally {
1629
+ await rm(cwd, { recursive: true, force: true });
1630
+ }
1631
+ });
1632
+ });
370
1633
  describe('autoresearch keyword detection', () => {
371
1634
  it('detects explicit $autoresearch invocation', () => {
372
1635
  const match = detectPrimaryKeyword('please run $autoresearch now');
@@ -426,6 +1689,19 @@ describe('keyword registry coverage', () => {
426
1689
  assert.ok(registryKeywords.has('ultragoal'));
427
1690
  assert.ok(registryKeywords.has('autopilot'));
428
1691
  });
1692
+ it('resolves immutable aliases without duplicate sources or skill collisions', () => {
1693
+ assert.equal(Object.isFrozen(EXPLICIT_SKILL_ALIASES), true);
1694
+ assert.equal(new Set(EXPLICIT_SKILL_ALIASES.map((alias) => alias.source.toLowerCase())).size, EXPLICIT_SKILL_ALIASES.length);
1695
+ for (const alias of EXPLICIT_SKILL_ALIASES) {
1696
+ const target = getExplicitSkillDefinition(alias.target);
1697
+ const source = getExplicitSkillDefinition(alias.source.toUpperCase());
1698
+ assert.ok(target, alias.target);
1699
+ assert.deepEqual(source, target, alias.source);
1700
+ const canonicalSource = KEYWORD_TRIGGER_DEFINITIONS.find((definition) => definition.keyword.toLowerCase() === `$${alias.source.toLowerCase()}`);
1701
+ if (canonicalSource)
1702
+ assert.equal(canonicalSource.skill, target.skill, alias.source);
1703
+ }
1704
+ });
429
1705
  });
430
1706
  describe('keyword detector skill-active-state lifecycle', () => {
431
1707
  it('co-locates direct boxed activation mode detail and canonical skill state for OMX_ROOT', async () => {
@@ -3634,4 +4910,98 @@ describe('applyRalplanGate', () => {
3634
4910
  assert.ok(result.gatedKeywords.includes('ultrawork'));
3635
4911
  });
3636
4912
  });
4913
+ describe('recordSkillActivation prompt provenance', () => {
4914
+ it('writes only the authorized explicit payload session and stamps its owner', async () => {
4915
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-keyword-provenance-'));
4916
+ try {
4917
+ const context = evaluateResolvedPromptTurn({
4918
+ producer: 'native',
4919
+ payloadSessionId: 'payload-session',
4920
+ selectedPointer: { status: 'absent' },
4921
+ nowIso: '2026-07-14T00:00:00.000Z',
4922
+ });
4923
+ const state = await recordSkillActivation({
4924
+ stateDir,
4925
+ text: '$ralplan implement the scoped change',
4926
+ sessionId: 'payload-session',
4927
+ resolvedPromptTurnContext: context,
4928
+ });
4929
+ assert.equal(state?.owner_codex_session_id, 'payload-session');
4930
+ assert.equal(existsSync(join(stateDir, 'sessions', 'payload-session', SKILL_ACTIVE_STATE_FILE)), true);
4931
+ assert.equal(existsSync(join(stateDir, SKILL_ACTIVE_STATE_FILE)), false);
4932
+ }
4933
+ finally {
4934
+ await rm(stateDir, { recursive: true, force: true });
4935
+ }
4936
+ });
4937
+ it('rejects nested foreign owners before direct activation writes', async () => {
4938
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-keyword-nested-owner-'));
4939
+ try {
4940
+ const targetDir = join(stateDir, 'sessions', 'target');
4941
+ await mkdir(targetDir, { recursive: true });
4942
+ const statePath = join(targetDir, SKILL_ACTIVE_STATE_FILE);
4943
+ const original = JSON.stringify({
4944
+ skill: 'ralph', active: true, session_id: 'target', owner_codex_session_id: 'target',
4945
+ active_skills: [{ skill: 'ralph', session_id: 'target', owner_codex_session_id: 'foreign' }],
4946
+ });
4947
+ await writeFile(statePath, original);
4948
+ const context = evaluateResolvedPromptTurn({ producer: 'native', payloadSessionId: 'target', selectedPointer: { status: 'absent' } });
4949
+ let rejections = 0;
4950
+ const result = await recordSkillActivation({
4951
+ stateDir, text: '$ralph continue', sessionId: 'target', resolvedPromptTurnContext: context,
4952
+ onProvenanceRejected: () => { rejections += 1; },
4953
+ });
4954
+ assert.equal(result, null);
4955
+ assert.equal(rejections, 1);
4956
+ assert.equal(await readFile(statePath, 'utf8'), original);
4957
+ assert.equal(existsSync(join(targetDir, 'ralph-state.json')), false);
4958
+ }
4959
+ finally {
4960
+ await rm(stateDir, { recursive: true, force: true });
4961
+ }
4962
+ });
4963
+ it('rejects malformed target state before direct activation writes', async () => {
4964
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-keyword-malformed-owner-'));
4965
+ try {
4966
+ const targetDir = join(stateDir, 'sessions', 'target');
4967
+ await mkdir(targetDir, { recursive: true });
4968
+ const malformedPath = join(targetDir, 'ralph-state.json');
4969
+ await writeFile(malformedPath, '{ malformed');
4970
+ const context = evaluateResolvedPromptTurn({ producer: 'native', payloadSessionId: 'target', selectedPointer: { status: 'absent' } });
4971
+ let rejections = 0;
4972
+ const result = await recordSkillActivation({
4973
+ stateDir, text: '$ralph continue', sessionId: 'target', resolvedPromptTurnContext: context,
4974
+ onProvenanceRejected: () => { rejections += 1; },
4975
+ });
4976
+ assert.equal(result, null);
4977
+ assert.equal(rejections, 1);
4978
+ assert.equal(await readFile(malformedPath, 'utf8'), '{ malformed');
4979
+ assert.equal(existsSync(join(targetDir, SKILL_ACTIVE_STATE_FILE)), false);
4980
+ }
4981
+ finally {
4982
+ await rm(stateDir, { recursive: true, force: true });
4983
+ }
4984
+ });
4985
+ it('rejects target enumeration failures before direct activation writes', async () => {
4986
+ const stateDir = await mkdtemp(join(tmpdir(), 'omx-keyword-enumeration-owner-'));
4987
+ try {
4988
+ const sessionsDir = join(stateDir, 'sessions');
4989
+ await mkdir(sessionsDir, { recursive: true });
4990
+ const targetPath = join(sessionsDir, 'target');
4991
+ await writeFile(targetPath, 'not-a-directory');
4992
+ const context = evaluateResolvedPromptTurn({ producer: 'native', payloadSessionId: 'target', selectedPointer: { status: 'absent' } });
4993
+ let rejections = 0;
4994
+ const result = await recordSkillActivation({
4995
+ stateDir, text: '$ralph continue', sessionId: 'target', resolvedPromptTurnContext: context,
4996
+ onProvenanceRejected: () => { rejections += 1; },
4997
+ });
4998
+ assert.equal(result, null);
4999
+ assert.equal(rejections, 1);
5000
+ assert.equal(await readFile(targetPath, 'utf8'), 'not-a-directory');
5001
+ }
5002
+ finally {
5003
+ await rm(stateDir, { recursive: true, force: true });
5004
+ }
5005
+ });
5006
+ });
3637
5007
  //# sourceMappingURL=keyword-detector.test.js.map