oh-my-copilot 4.13.0 → 4.13.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 (420) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +2 -2
  4. package/agents/analyst.agent.md +2 -1
  5. package/agents/architect.agent.md +2 -1
  6. package/agents/code-reviewer.agent.md +2 -1
  7. package/agents/critic.agent.md +2 -1
  8. package/agents/debugger.agent.md +2 -1
  9. package/agents/designer.agent.md +2 -1
  10. package/agents/devils-advocate.agent.md +2 -1
  11. package/agents/document-specialist.agent.md +2 -1
  12. package/agents/executor.agent.md +2 -1
  13. package/agents/explore.agent.md +2 -1
  14. package/agents/git-master.agent.md +2 -1
  15. package/agents/planner.agent.md +2 -1
  16. package/agents/qa-tester.agent.md +2 -1
  17. package/agents/scientist.agent.md +2 -1
  18. package/agents/security-reviewer.agent.md +2 -1
  19. package/agents/test-engineer.agent.md +2 -1
  20. package/agents/verifier.agent.md +2 -1
  21. package/agents/writer.agent.md +2 -1
  22. package/bridge/cli.cjs +2823 -1491
  23. package/bridge/mcp-server.cjs +15 -2
  24. package/bridge/run-mcp-server.sh +1 -1
  25. package/bridge/runtime-cli.cjs +3589 -2448
  26. package/bridge/team-bridge.cjs +6 -4
  27. package/bridge/team-mcp.cjs +19607 -0
  28. package/bridge/team.js +3576 -2080
  29. package/dist/__tests__/auto-slash-aliases.test.js +5 -5
  30. package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
  31. package/dist/__tests__/bedrock-lm-suffix-hook.test.js +2 -2
  32. package/dist/__tests__/bedrock-lm-suffix-hook.test.js.map +1 -1
  33. package/dist/__tests__/cleanup-orphans-unref.test.d.ts +2 -0
  34. package/dist/__tests__/cleanup-orphans-unref.test.d.ts.map +1 -0
  35. package/dist/__tests__/cleanup-orphans-unref.test.js +44 -0
  36. package/dist/__tests__/cleanup-orphans-unref.test.js.map +1 -0
  37. package/dist/__tests__/context-safety.test.js +3 -3
  38. package/dist/__tests__/context-safety.test.js.map +1 -1
  39. package/dist/__tests__/hooks/learner/bridge.test.js +106 -0
  40. package/dist/__tests__/hooks/learner/bridge.test.js.map +1 -1
  41. package/dist/__tests__/hooks/learner/parser.test.js +30 -0
  42. package/dist/__tests__/hooks/learner/parser.test.js.map +1 -1
  43. package/dist/__tests__/hooks.test.js +47 -0
  44. package/dist/__tests__/hooks.test.js.map +1 -1
  45. package/dist/__tests__/hud/extra-usage.test.js +42 -0
  46. package/dist/__tests__/hud/extra-usage.test.js.map +1 -1
  47. package/dist/__tests__/hud/render-enterprise.test.d.ts +5 -0
  48. package/dist/__tests__/hud/render-enterprise.test.d.ts.map +1 -0
  49. package/dist/__tests__/hud/render-enterprise.test.js +232 -0
  50. package/dist/__tests__/hud/render-enterprise.test.js.map +1 -0
  51. package/dist/__tests__/hud/render-rate-limits-priority.test.js +159 -0
  52. package/dist/__tests__/hud/render-rate-limits-priority.test.js.map +1 -1
  53. package/dist/__tests__/hud/stdin-session-scope.test.d.ts +2 -0
  54. package/dist/__tests__/hud/stdin-session-scope.test.d.ts.map +1 -0
  55. package/dist/__tests__/hud/stdin-session-scope.test.js +169 -0
  56. package/dist/__tests__/hud/stdin-session-scope.test.js.map +1 -0
  57. package/dist/__tests__/hud/usage-api.test.js +111 -1
  58. package/dist/__tests__/hud/usage-api.test.js.map +1 -1
  59. package/dist/__tests__/issue-2652-runtime-wiring-and-output-contract.test.js +2 -0
  60. package/dist/__tests__/issue-2652-runtime-wiring-and-output-contract.test.js.map +1 -1
  61. package/dist/__tests__/keyword-detector-echo-guard.test.d.ts +2 -0
  62. package/dist/__tests__/keyword-detector-echo-guard.test.d.ts.map +1 -0
  63. package/dist/__tests__/keyword-detector-echo-guard.test.js +218 -0
  64. package/dist/__tests__/keyword-detector-echo-guard.test.js.map +1 -0
  65. package/dist/__tests__/keyword-detector-script.test.js +2 -1
  66. package/dist/__tests__/keyword-detector-script.test.js.map +1 -1
  67. package/dist/__tests__/omc-cli-rendering.test.js +13 -10
  68. package/dist/__tests__/omc-cli-rendering.test.js.map +1 -1
  69. package/dist/__tests__/pre-tool-enforcer.test.js +22 -38
  70. package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
  71. package/dist/__tests__/preemptive-compaction-hook.test.js +63 -0
  72. package/dist/__tests__/preemptive-compaction-hook.test.js.map +1 -1
  73. package/dist/__tests__/safe-command-approver.test.d.ts +2 -0
  74. package/dist/__tests__/safe-command-approver.test.d.ts.map +1 -0
  75. package/dist/__tests__/safe-command-approver.test.js +103 -0
  76. package/dist/__tests__/safe-command-approver.test.js.map +1 -0
  77. package/dist/__tests__/skills.test.js +18 -0
  78. package/dist/__tests__/skills.test.js.map +1 -1
  79. package/dist/__tests__/state-root-resolution.test.js +64 -0
  80. package/dist/__tests__/state-root-resolution.test.js.map +1 -1
  81. package/dist/cli/__tests__/launch.test.js +24 -0
  82. package/dist/cli/__tests__/launch.test.js.map +1 -1
  83. package/dist/cli/__tests__/team-status-worktree.test.d.ts +2 -0
  84. package/dist/cli/__tests__/team-status-worktree.test.d.ts.map +1 -0
  85. package/dist/cli/__tests__/team-status-worktree.test.js +96 -0
  86. package/dist/cli/__tests__/team-status-worktree.test.js.map +1 -0
  87. package/dist/cli/__tests__/team.test.js +164 -0
  88. package/dist/cli/__tests__/team.test.js.map +1 -1
  89. package/dist/cli/__tests__/tmux-utils.test.js +18 -3
  90. package/dist/cli/__tests__/tmux-utils.test.js.map +1 -1
  91. package/dist/cli/autoresearch-setup-session.d.ts.map +1 -1
  92. package/dist/cli/autoresearch-setup-session.js +1 -0
  93. package/dist/cli/autoresearch-setup-session.js.map +1 -1
  94. package/dist/cli/commands/team.js +1 -1
  95. package/dist/cli/launch.d.ts.map +1 -1
  96. package/dist/cli/launch.js +10 -2
  97. package/dist/cli/launch.js.map +1 -1
  98. package/dist/cli/team.d.ts.map +1 -1
  99. package/dist/cli/team.js +103 -4
  100. package/dist/cli/team.js.map +1 -1
  101. package/dist/cli/tmux-utils.d.ts.map +1 -1
  102. package/dist/cli/tmux-utils.js +5 -2
  103. package/dist/cli/tmux-utils.js.map +1 -1
  104. package/dist/cli/utils/formatting.d.ts +0 -10
  105. package/dist/cli/utils/formatting.d.ts.map +1 -1
  106. package/dist/cli/utils/formatting.js +0 -52
  107. package/dist/cli/utils/formatting.js.map +1 -1
  108. package/dist/config/loader.d.ts.map +1 -1
  109. package/dist/config/loader.js +34 -8
  110. package/dist/config/loader.js.map +1 -1
  111. package/dist/features/__tests__/magic-keywords.test.d.ts +2 -0
  112. package/dist/features/__tests__/magic-keywords.test.d.ts.map +1 -0
  113. package/dist/features/__tests__/magic-keywords.test.js +19 -0
  114. package/dist/features/__tests__/magic-keywords.test.js.map +1 -0
  115. package/dist/features/delegation-enforcer.d.ts +2 -0
  116. package/dist/features/delegation-enforcer.d.ts.map +1 -1
  117. package/dist/features/delegation-enforcer.js +10 -0
  118. package/dist/features/delegation-enforcer.js.map +1 -1
  119. package/dist/features/magic-keywords.d.ts +1 -1
  120. package/dist/features/magic-keywords.d.ts.map +1 -1
  121. package/dist/features/magic-keywords.js +5 -192
  122. package/dist/features/magic-keywords.js.map +1 -1
  123. package/dist/hooks/__tests__/bridge-routing.test.js +23 -0
  124. package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
  125. package/dist/hooks/bridge-normalize.d.ts +5 -0
  126. package/dist/hooks/bridge-normalize.d.ts.map +1 -1
  127. package/dist/hooks/bridge-normalize.js +6 -0
  128. package/dist/hooks/bridge-normalize.js.map +1 -1
  129. package/dist/hooks/bridge.d.ts +4 -0
  130. package/dist/hooks/bridge.d.ts.map +1 -1
  131. package/dist/hooks/bridge.js +60 -3
  132. package/dist/hooks/bridge.js.map +1 -1
  133. package/dist/hooks/keyword-detector/index.js +1 -1
  134. package/dist/hooks/keyword-detector/index.js.map +1 -1
  135. package/dist/hooks/keyword-detector/ultrawork/__tests__/index.test.d.ts +2 -0
  136. package/dist/hooks/keyword-detector/ultrawork/__tests__/index.test.d.ts.map +1 -0
  137. package/dist/hooks/keyword-detector/ultrawork/__tests__/index.test.js +37 -0
  138. package/dist/hooks/keyword-detector/ultrawork/__tests__/index.test.js.map +1 -0
  139. package/dist/hooks/keyword-detector/ultrawork/default.d.ts +3 -0
  140. package/dist/hooks/keyword-detector/ultrawork/default.d.ts.map +1 -0
  141. package/dist/hooks/keyword-detector/ultrawork/default.js +67 -0
  142. package/dist/hooks/keyword-detector/ultrawork/default.js.map +1 -0
  143. package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +3 -0
  144. package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts.map +1 -0
  145. package/dist/hooks/keyword-detector/ultrawork/gemini.js +47 -0
  146. package/dist/hooks/keyword-detector/ultrawork/gemini.js.map +1 -0
  147. package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts +3 -0
  148. package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts.map +1 -0
  149. package/dist/hooks/keyword-detector/ultrawork/gpt.js +52 -0
  150. package/dist/hooks/keyword-detector/ultrawork/gpt.js.map +1 -0
  151. package/dist/hooks/keyword-detector/ultrawork/index.d.ts +8 -0
  152. package/dist/hooks/keyword-detector/ultrawork/index.d.ts.map +1 -0
  153. package/dist/hooks/keyword-detector/ultrawork/index.js +24 -0
  154. package/dist/hooks/keyword-detector/ultrawork/index.js.map +1 -0
  155. package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +3 -0
  156. package/dist/hooks/keyword-detector/ultrawork/planner.d.ts.map +1 -0
  157. package/dist/hooks/keyword-detector/ultrawork/planner.js +57 -0
  158. package/dist/hooks/keyword-detector/ultrawork/planner.js.map +1 -0
  159. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts +6 -0
  160. package/dist/hooks/keyword-detector/ultrawork/source-detector.d.ts.map +1 -0
  161. package/dist/hooks/keyword-detector/ultrawork/source-detector.js +37 -0
  162. package/dist/hooks/keyword-detector/ultrawork/source-detector.js.map +1 -0
  163. package/dist/hooks/learner/bridge.d.ts +2 -0
  164. package/dist/hooks/learner/bridge.d.ts.map +1 -1
  165. package/dist/hooks/learner/bridge.js +15 -109
  166. package/dist/hooks/learner/bridge.js.map +1 -1
  167. package/dist/hooks/learner/index.d.ts.map +1 -1
  168. package/dist/hooks/learner/index.js +51 -14
  169. package/dist/hooks/learner/index.js.map +1 -1
  170. package/dist/hooks/learner/parser.d.ts +9 -0
  171. package/dist/hooks/learner/parser.d.ts.map +1 -1
  172. package/dist/hooks/learner/parser.js +18 -5
  173. package/dist/hooks/learner/parser.js.map +1 -1
  174. package/dist/hooks/learner/types.d.ts +6 -0
  175. package/dist/hooks/learner/types.d.ts.map +1 -1
  176. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  177. package/dist/hooks/persistent-mode/index.js +7 -4
  178. package/dist/hooks/persistent-mode/index.js.map +1 -1
  179. package/dist/hooks/persistent-mode/session-isolation.test.js +8 -3
  180. package/dist/hooks/persistent-mode/session-isolation.test.js.map +1 -1
  181. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +142 -7
  182. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
  183. package/dist/hooks/project-memory/__tests__/learner.test.js +7 -0
  184. package/dist/hooks/project-memory/__tests__/learner.test.js.map +1 -1
  185. package/dist/hooks/project-memory/learner.d.ts +1 -1
  186. package/dist/hooks/project-memory/learner.d.ts.map +1 -1
  187. package/dist/hooks/project-memory/learner.js +3 -0
  188. package/dist/hooks/project-memory/learner.js.map +1 -1
  189. package/dist/hooks/session-end/index.d.ts.map +1 -1
  190. package/dist/hooks/session-end/index.js +43 -0
  191. package/dist/hooks/session-end/index.js.map +1 -1
  192. package/dist/hooks/skill-bridge.cjs +116 -87
  193. package/dist/hud/__tests__/enterprise-cost.test.d.ts +5 -0
  194. package/dist/hud/__tests__/enterprise-cost.test.d.ts.map +1 -0
  195. package/dist/hud/__tests__/enterprise-cost.test.js +151 -0
  196. package/dist/hud/__tests__/enterprise-cost.test.js.map +1 -0
  197. package/dist/hud/__tests__/usage-api-enterprise.test.d.ts +5 -0
  198. package/dist/hud/__tests__/usage-api-enterprise.test.d.ts.map +1 -0
  199. package/dist/hud/__tests__/usage-api-enterprise.test.js +249 -0
  200. package/dist/hud/__tests__/usage-api-enterprise.test.js.map +1 -0
  201. package/dist/hud/elements/enterprise-cost.d.ts +16 -0
  202. package/dist/hud/elements/enterprise-cost.d.ts.map +1 -0
  203. package/dist/hud/elements/enterprise-cost.js +63 -0
  204. package/dist/hud/elements/enterprise-cost.js.map +1 -0
  205. package/dist/hud/elements/permission.d.ts.map +1 -1
  206. package/dist/hud/elements/permission.js +2 -5
  207. package/dist/hud/elements/permission.js.map +1 -1
  208. package/dist/hud/elements/session.d.ts.map +1 -1
  209. package/dist/hud/elements/session.js +5 -9
  210. package/dist/hud/elements/session.js.map +1 -1
  211. package/dist/hud/index.d.ts.map +1 -1
  212. package/dist/hud/index.js +22 -5
  213. package/dist/hud/index.js.map +1 -1
  214. package/dist/hud/render.d.ts.map +1 -1
  215. package/dist/hud/render.js +34 -2
  216. package/dist/hud/render.js.map +1 -1
  217. package/dist/hud/stdin.d.ts.map +1 -1
  218. package/dist/hud/stdin.js +49 -5
  219. package/dist/hud/stdin.js.map +1 -1
  220. package/dist/hud/types.d.ts +16 -0
  221. package/dist/hud/types.d.ts.map +1 -1
  222. package/dist/hud/types.js +1 -1
  223. package/dist/hud/types.js.map +1 -1
  224. package/dist/hud/usage-api.d.ts +19 -1
  225. package/dist/hud/usage-api.d.ts.map +1 -1
  226. package/dist/hud/usage-api.js +101 -18
  227. package/dist/hud/usage-api.js.map +1 -1
  228. package/dist/installer/__tests__/hook-templates.test.js +23 -0
  229. package/dist/installer/__tests__/hook-templates.test.js.map +1 -1
  230. package/dist/installer/hooks.d.ts +4 -2
  231. package/dist/installer/hooks.d.ts.map +1 -1
  232. package/dist/installer/hooks.js +5 -94
  233. package/dist/installer/hooks.js.map +1 -1
  234. package/dist/installer/index.d.ts +1 -0
  235. package/dist/installer/index.d.ts.map +1 -1
  236. package/dist/installer/index.js +64 -24
  237. package/dist/installer/index.js.map +1 -1
  238. package/dist/installer/mcp-registry.d.ts.map +1 -1
  239. package/dist/installer/mcp-registry.js +3 -2
  240. package/dist/installer/mcp-registry.js.map +1 -1
  241. package/dist/mcp/__tests__/team-server-artifact-convergence.test.js +156 -4
  242. package/dist/mcp/__tests__/team-server-artifact-convergence.test.js.map +1 -1
  243. package/dist/mcp/omc-tools-server.d.ts +11 -17
  244. package/dist/mcp/omc-tools-server.d.ts.map +1 -1
  245. package/dist/mcp/omc-tools-server.js +22 -32
  246. package/dist/mcp/omc-tools-server.js.map +1 -1
  247. package/dist/mcp/team-job-convergence.d.ts +9 -2
  248. package/dist/mcp/team-job-convergence.d.ts.map +1 -1
  249. package/dist/mcp/team-job-convergence.js +35 -14
  250. package/dist/mcp/team-job-convergence.js.map +1 -1
  251. package/dist/mcp/team-server.d.ts.map +1 -1
  252. package/dist/mcp/team-server.js +93 -13
  253. package/dist/mcp/team-server.js.map +1 -1
  254. package/dist/ralphthon/prd.d.ts.map +1 -1
  255. package/dist/ralphthon/prd.js +13 -17
  256. package/dist/ralphthon/prd.js.map +1 -1
  257. package/dist/shared/types.d.ts +3 -1
  258. package/dist/shared/types.d.ts.map +1 -1
  259. package/dist/team/__tests__/api-interop.cleanup.test.d.ts +2 -0
  260. package/dist/team/__tests__/api-interop.cleanup.test.d.ts.map +1 -0
  261. package/dist/team/__tests__/api-interop.cleanup.test.js +234 -0
  262. package/dist/team/__tests__/api-interop.cleanup.test.js.map +1 -0
  263. package/dist/team/__tests__/api-interop.dispatch.test.js +2 -1
  264. package/dist/team/__tests__/api-interop.dispatch.test.js.map +1 -1
  265. package/dist/team/__tests__/git-worktree.test.js +164 -3
  266. package/dist/team/__tests__/git-worktree.test.js.map +1 -1
  267. package/dist/team/__tests__/model-contract.test.js +38 -22
  268. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  269. package/dist/team/__tests__/resolved-routing-snapshot.test.js +29 -1
  270. package/dist/team/__tests__/resolved-routing-snapshot.test.js.map +1 -1
  271. package/dist/team/__tests__/runtime-cli.test.js +2 -2
  272. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  273. package/dist/team/__tests__/runtime-prompt-mode.test.js +17 -17
  274. package/dist/team/__tests__/runtime-prompt-mode.test.js.map +1 -1
  275. package/dist/team/__tests__/runtime-v2-role-routing.test.js +7 -1
  276. package/dist/team/__tests__/runtime-v2-role-routing.test.js.map +1 -1
  277. package/dist/team/__tests__/runtime-v2.dispatch.test.js +145 -16
  278. package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
  279. package/dist/team/__tests__/runtime-v2.monitor.test.d.ts +2 -0
  280. package/dist/team/__tests__/runtime-v2.monitor.test.d.ts.map +1 -0
  281. package/dist/team/__tests__/runtime-v2.monitor.test.js +219 -0
  282. package/dist/team/__tests__/runtime-v2.monitor.test.js.map +1 -0
  283. package/dist/team/__tests__/runtime-v2.shutdown.test.d.ts +2 -0
  284. package/dist/team/__tests__/runtime-v2.shutdown.test.d.ts.map +1 -0
  285. package/dist/team/__tests__/runtime-v2.shutdown.test.js +292 -0
  286. package/dist/team/__tests__/runtime-v2.shutdown.test.js.map +1 -0
  287. package/dist/team/__tests__/scaling-launch-config.test.js +204 -4
  288. package/dist/team/__tests__/scaling-launch-config.test.js.map +1 -1
  289. package/dist/team/__tests__/stage-router.test.js +29 -1
  290. package/dist/team/__tests__/stage-router.test.js.map +1 -1
  291. package/dist/team/__tests__/team-summary-worktree.test.d.ts +2 -0
  292. package/dist/team/__tests__/team-summary-worktree.test.d.ts.map +1 -0
  293. package/dist/team/__tests__/team-summary-worktree.test.js +85 -0
  294. package/dist/team/__tests__/team-summary-worktree.test.js.map +1 -0
  295. package/dist/team/__tests__/tmux-session-liveness.test.d.ts +2 -0
  296. package/dist/team/__tests__/tmux-session-liveness.test.d.ts.map +1 -0
  297. package/dist/team/__tests__/tmux-session-liveness.test.js +37 -0
  298. package/dist/team/__tests__/tmux-session-liveness.test.js.map +1 -0
  299. package/dist/team/__tests__/worker-bootstrap.test.js +47 -1
  300. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  301. package/dist/team/__tests__/worktree-contract-fields.test.d.ts +2 -0
  302. package/dist/team/__tests__/worktree-contract-fields.test.d.ts.map +1 -0
  303. package/dist/team/__tests__/worktree-contract-fields.test.js +157 -0
  304. package/dist/team/__tests__/worktree-contract-fields.test.js.map +1 -0
  305. package/dist/team/api-interop.d.ts +2 -1
  306. package/dist/team/api-interop.d.ts.map +1 -1
  307. package/dist/team/api-interop.js +121 -14
  308. package/dist/team/api-interop.js.map +1 -1
  309. package/dist/team/capabilities.d.ts.map +1 -1
  310. package/dist/team/capabilities.js +2 -0
  311. package/dist/team/capabilities.js.map +1 -1
  312. package/dist/team/cli-worker-contract.d.ts +7 -6
  313. package/dist/team/cli-worker-contract.d.ts.map +1 -1
  314. package/dist/team/cli-worker-contract.js +7 -6
  315. package/dist/team/cli-worker-contract.js.map +1 -1
  316. package/dist/team/events.d.ts +3 -0
  317. package/dist/team/events.d.ts.map +1 -1
  318. package/dist/team/events.js +2 -1
  319. package/dist/team/events.js.map +1 -1
  320. package/dist/team/git-worktree.d.ts +78 -10
  321. package/dist/team/git-worktree.d.ts.map +1 -1
  322. package/dist/team/git-worktree.js +453 -97
  323. package/dist/team/git-worktree.js.map +1 -1
  324. package/dist/team/governance.d.ts +3 -2
  325. package/dist/team/governance.d.ts.map +1 -1
  326. package/dist/team/governance.js.map +1 -1
  327. package/dist/team/model-contract.d.ts +18 -2
  328. package/dist/team/model-contract.d.ts.map +1 -1
  329. package/dist/team/model-contract.js +55 -27
  330. package/dist/team/model-contract.js.map +1 -1
  331. package/dist/team/monitor.d.ts.map +1 -1
  332. package/dist/team/monitor.js +47 -3
  333. package/dist/team/monitor.js.map +1 -1
  334. package/dist/team/runtime-v2.d.ts +9 -0
  335. package/dist/team/runtime-v2.d.ts.map +1 -1
  336. package/dist/team/runtime-v2.js +347 -120
  337. package/dist/team/runtime-v2.js.map +1 -1
  338. package/dist/team/runtime.d.ts.map +1 -1
  339. package/dist/team/runtime.js +4 -1
  340. package/dist/team/runtime.js.map +1 -1
  341. package/dist/team/scaling.d.ts.map +1 -1
  342. package/dist/team/scaling.js +110 -13
  343. package/dist/team/scaling.js.map +1 -1
  344. package/dist/team/state/tasks.d.ts.map +1 -1
  345. package/dist/team/state/tasks.js +1 -0
  346. package/dist/team/state/tasks.js.map +1 -1
  347. package/dist/team/team-ops.d.ts.map +1 -1
  348. package/dist/team/team-ops.js +112 -16
  349. package/dist/team/team-ops.js.map +1 -1
  350. package/dist/team/tmux-session.d.ts +17 -29
  351. package/dist/team/tmux-session.d.ts.map +1 -1
  352. package/dist/team/tmux-session.js +116 -107
  353. package/dist/team/tmux-session.js.map +1 -1
  354. package/dist/team/types.d.ts +31 -17
  355. package/dist/team/types.d.ts.map +1 -1
  356. package/dist/team/types.js.map +1 -1
  357. package/dist/team/worker-bootstrap.d.ts +7 -0
  358. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  359. package/dist/team/worker-bootstrap.js +27 -13
  360. package/dist/team/worker-bootstrap.js.map +1 -1
  361. package/dist/team/worker-canonicalization.d.ts.map +1 -1
  362. package/dist/team/worker-canonicalization.js +2 -0
  363. package/dist/team/worker-canonicalization.js.map +1 -1
  364. package/dist/utils/frontmatter.d.ts.map +1 -1
  365. package/dist/utils/frontmatter.js +1 -16
  366. package/dist/utils/frontmatter.js.map +1 -1
  367. package/dist/utils/omc-cli-rendering.d.ts.map +1 -1
  368. package/dist/utils/omc-cli-rendering.js +1 -12
  369. package/dist/utils/omc-cli-rendering.js.map +1 -1
  370. package/dist/utils/resolve-node.d.ts.map +1 -1
  371. package/dist/utils/resolve-node.js +22 -28
  372. package/dist/utils/resolve-node.js.map +1 -1
  373. package/docs/MIGRATION.md +1003 -0
  374. package/docs/REFERENCE.md +4 -0
  375. package/docs/TEAM-WORKTREE-MODE.md +86 -0
  376. package/docs/copilot-instructions.md +1 -1
  377. package/docs/reference/compatibility.md +1 -1
  378. package/hooks/hooks.json +34 -13
  379. package/package.json +1 -1
  380. package/scripts/cleanup-orphans.mjs +24 -3
  381. package/scripts/context-safety.mjs +10 -11
  382. package/scripts/keyword-detector.debug.mjs +2 -2
  383. package/scripts/keyword-detector.mjs +219 -57
  384. package/scripts/lib/state-root.cjs +1 -1
  385. package/scripts/lib/state-root.mjs +3 -3
  386. package/scripts/persistent-mode.cjs +30 -4
  387. package/scripts/persistent-mode.mjs +28 -2
  388. package/scripts/plugin-setup.mjs +8 -7
  389. package/scripts/post-tool-rules-injector.mjs +1 -1
  390. package/scripts/post-tool-verifier.mjs +55 -3
  391. package/scripts/pre-tool-enforcer.mjs +9 -21
  392. package/scripts/project-memory-session.mjs +1 -1
  393. package/scripts/run.cjs +9 -9
  394. package/scripts/safe-command-approver.mjs +156 -0
  395. package/scripts/session-start.mjs +91 -11
  396. package/scripts/setup-copilot-instructions.sh +5 -4
  397. package/scripts/skill-injector.mjs +60 -23
  398. package/scripts/test-pr25.sh +1 -1
  399. package/scripts/uninstall.sh +1 -1
  400. package/scripts/verify-deliverables.mjs +1 -1
  401. package/skills/deep-interview/SKILL.md +23 -10
  402. package/skills/learner/SKILL.md +1 -1
  403. package/skills/project-session-manager/lib/config.sh +1 -1
  404. package/skills/project-session-manager/lib/parse.sh +1 -1
  405. package/skills/project-session-manager/lib/providers/azure-devops.sh +1 -1
  406. package/skills/project-session-manager/lib/providers/bitbucket.sh +1 -1
  407. package/skills/project-session-manager/lib/providers/gitea.sh +1 -1
  408. package/skills/project-session-manager/lib/providers/github.sh +1 -1
  409. package/skills/project-session-manager/lib/providers/gitlab.sh +1 -1
  410. package/skills/project-session-manager/lib/providers/interface.sh +1 -1
  411. package/skills/project-session-manager/lib/providers/jira.sh +1 -1
  412. package/skills/project-session-manager/lib/session.sh +1 -1
  413. package/skills/project-session-manager/lib/tmux.sh +1 -1
  414. package/skills/project-session-manager/lib/worktree.sh +1 -1
  415. package/skills/project-session-manager/psm.sh +1 -1
  416. package/skills/project-session-manager/tests/test-psm-prompt-injection.sh +1 -1
  417. package/skills/skill/SKILL.md +1 -1
  418. package/templates/hooks/keyword-detector.mjs +183 -26
  419. package/templates/hooks/persistent-mode.mjs +28 -2
  420. package/templates/hooks/session-start.mjs +126 -11
@@ -28,23 +28,36 @@ import { join, dirname } from 'path';
28
28
  import { homedir } from 'os';
29
29
  import { fileURLToPath } from 'url';
30
30
  import { getClaudeConfigDir } from './lib/config-dir.mjs';
31
+ import { atomicWriteFileSync } from './lib/atomic-write.mjs';
31
32
  import { readStdin } from './lib/stdin.mjs';
32
33
 
33
- // Resolve OMC package root: CLAUDE_PLUGIN_ROOT (plugin system) or derive from this script's location
34
- const _omcRoot = process.env.CLAUDE_PLUGIN_ROOT ||
34
+ // Resolve OMC package root: PLUGIN_ROOT (plugin system) or derive from this script's location
35
+ const _omcRoot = process.env.PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT ||
35
36
  join(dirname(fileURLToPath(import.meta.url)), '..');
37
+ const SKILL_INVOCATION_USER_REQUEST_MAX = 1200;
36
38
 
37
- /**
38
- * Load skill content directly from SKILL.md on disk.
39
- * Works for both npm installs and plugin marketplace installs.
40
- * Returns null if the skill file is not found.
41
- */
42
- function loadSkillContent(skillName) {
43
- const skillPath = join(_omcRoot, 'skills', skillName, 'SKILL.md');
44
- if (existsSync(skillPath)) {
45
- try { return readFileSync(skillPath, 'utf8'); } catch { /* fall through */ }
39
+ function compactHookText(text, maxChars = SKILL_INVOCATION_USER_REQUEST_MAX) {
40
+ const notice = '\n...[truncated; original user prompt remains available in the conversation]';
41
+ if (!text || text.length <= maxChars) return text || '';
42
+ if (maxChars <= notice.length) return notice.slice(0, Math.max(0, maxChars));
43
+ return `${text.slice(0, maxChars - notice.length).trimEnd()}${notice}`;
44
+ }
45
+
46
+ function getSkillPathCandidates(skillName) {
47
+ const roots = [
48
+ process.env.PLUGIN_ROOT,
49
+ process.env.CLAUDE_PLUGIN_ROOT,
50
+ _omcRoot,
51
+ process.cwd(),
52
+ ].filter(Boolean);
53
+ return [...new Set(roots.map(root => join(root, 'skills', skillName, 'SKILL.md')))];
54
+ }
55
+
56
+ function resolveSkillPath(skillName) {
57
+ for (const skillPath of getSkillPathCandidates(skillName)) {
58
+ if (existsSync(skillPath)) return skillPath;
46
59
  }
47
- return null;
60
+ return getSkillPathCandidates(skillName)[0] || `skills/${skillName}/SKILL.md`;
48
61
  }
49
62
 
50
63
  const ULTRATHINK_MESSAGE = `<think-mode>
@@ -204,7 +217,7 @@ const ROLE_BOUNDARY_PATTERN =
204
217
  /^<\s*\/?\s*(system|human|assistant|user|tool_use|tool_result)\b[^>]*>/i;
205
218
  const SKILL_TRANSCRIPT_LINE_PATTERN =
206
219
  /^\s*Skill:\s+oh-my-(?:copilot|claudecode|codex):/i;
207
- const USER_REQUEST_LINE_PATTERN = /^\s*User request:\s*$/i;
220
+ const USER_REQUEST_LINE_PATTERN = /^\s*User request(?:\s*\([^)]*\))?:\s*$/i;
208
221
  const SHELL_TRANSCRIPT_LINE_PATTERN = /^\s*[$%❯]\s+/;
209
222
  const GIT_DIFF_START_PATTERNS = [
210
223
  /^diff\s+--git\s+a\//,
@@ -347,6 +360,119 @@ const QUESTION_FOLLOWUP_PATTERNS = [
347
360
  /\b(?:how\s+many|how\s+much|why|what\s+happened|what\s+went\s+wrong|token\s+budget|cost|pricing)\b/i,
348
361
  /(?:왜|얼마|몇\s*번|몇번|토큰|가격|비용|질문)/u,
349
362
  ];
363
+
364
+ // Patterns that identify system-generated echoes (hook outputs) which can be
365
+ // pasted back into the prompt verbatim. If a mode keyword only appears inside
366
+ // such an echo block we MUST NOT re-activate the mode: otherwise a user who
367
+ // copies a "[RALPH LOOP - ITERATION N] ..." block into a new session to debug
368
+ // it will unintentionally re-trigger ralph, and the pasted text ends up as
369
+ // the new state.prompt — producing a recursive self-reinforcing loop.
370
+ // Continuation lines that hook output typically emits DIRECTLY after a
371
+ // recognized block header. They must be stripped only in that context —
372
+ // never standalone — because a user might legitimately start a prompt with
373
+ // "Task: …" or similar (Codex automated review P1/P2 on #2795).
374
+ const ECHO_CONTINUATION = '(?:\\r?\\n[ \\t]*(?:Task:\\s|When FULLY complete \\(after Architect verification\\)|run\\s+\\/oh-my-(?:claudecode|codex|copilot):cancel).*)*';
375
+
376
+ // NOTE: each pattern is a SINGLE LOGICAL BLOCK: the block header line +
377
+ // zero-or-more continuation lines that hooks emit right after it. The whole
378
+ // match is stripped together. Both `i` (case-insensitive against the
379
+ // lower-cased cleanPrompt upstream) and `m` (so `^`/`$` match line
380
+ // boundaries) are required.
381
+ function buildEchoBlockRegex(headerBody) {
382
+ return new RegExp(`^[ \\t]*${headerBody}.*${ECHO_CONTINUATION}$`, 'gim');
383
+ }
384
+
385
+ const SYSTEM_ECHO_BLOCK_PATTERNS = [
386
+ // persistent-mode.mjs block headers
387
+ buildEchoBlockRegex('\\[RALPH LOOP\\s*-\\s*ITERATION[^\\]\\n]*\\]'),
388
+ buildEchoBlockRegex('\\[RALPH LOOP\\s*-\\s*(?:HARD LIMIT|EXTENDED)\\]'),
389
+ buildEchoBlockRegex('\\[TEAM\\s*-\\s*Phase:[^\\]\\n]*\\]'),
390
+ buildEchoBlockRegex('\\[AUTOPILOT[^\\]\\n]*\\]'),
391
+ buildEchoBlockRegex('\\[ULTRAPILOT[^\\]\\n]*\\]'),
392
+ buildEchoBlockRegex('\\[ULTRAWORK[^\\]\\n]*\\]'),
393
+ buildEchoBlockRegex('\\[ULTRAQA[^\\]\\n]*\\]'),
394
+ buildEchoBlockRegex('\\[PIPELINE[^\\]\\n]*\\]'),
395
+ buildEchoBlockRegex('\\[SWARM[^\\]\\n]*\\]'),
396
+ buildEchoBlockRegex('\\[TOOL ERROR[^\\]\\n]*\\]'),
397
+ // keyword-detector.mjs block headers
398
+ buildEchoBlockRegex('\\[MAGIC KEYWORD:[^\\]\\n]*\\]'),
399
+ buildEchoBlockRegex('\\[MAGIC KEYWORDS DETECTED:[^\\]\\n]*\\]'),
400
+ // Stop-hook wrapping by the Claude Code harness
401
+ buildEchoBlockRegex('Stop hook (?:blocking error|feedback|stopped continuation)'),
402
+ buildEchoBlockRegex('PreToolUse:[^\\n]*hook additional context:'),
403
+ buildEchoBlockRegex('PostToolUse:[^\\n]*hook additional context:'),
404
+ ];
405
+
406
+ // Signature lines indicating the text is predominantly a system echo. Even
407
+ // when the block patterns above fail to delimit cleanly (e.g. truncation),
408
+ // presence of these sentinels should make us treat the prompt as an echo.
409
+ // All patterns use `i` because hasActionableKeyword sees a lowercased prompt.
410
+ const SYSTEM_ECHO_SIGNATURES = [
411
+ /\bWhen FULLY complete \(after Architect verification\)\b/i,
412
+ /\brun\s+\/oh-my-(?:claudecode|codex|copilot):cancel\b/i,
413
+ /\[RALPH LOOP\s*-\s*ITERATION\b/i,
414
+ ];
415
+
416
+ const MAX_STATE_PROMPT_LEN = 500;
417
+
418
+ function stripSystemEchoes(text) {
419
+ if (typeof text !== 'string' || text.length === 0) return '';
420
+ let cleaned = text;
421
+ for (const pattern of SYSTEM_ECHO_BLOCK_PATTERNS) {
422
+ cleaned = cleaned.replace(pattern, ' ');
423
+ }
424
+ return cleaned;
425
+ }
426
+
427
+ function looksLikeSystemEcho(text) {
428
+ if (typeof text !== 'string' || text.length === 0) return false;
429
+ if (SYSTEM_ECHO_SIGNATURES.some((pattern) => pattern.test(text))) return true;
430
+ // Also treat the presence of ANY echo block pattern as an echo, so that
431
+ // mode-specific paste blocks (AUTOPILOT, ULTRAWORK, TEAM, etc.) are
432
+ // recognized without needing a dedicated signature line.
433
+ for (const pattern of SYSTEM_ECHO_BLOCK_PATTERNS) {
434
+ const probe = new RegExp(pattern.source, pattern.flags.replace('g', ''));
435
+ if (probe.test(text)) return true;
436
+ }
437
+ return false;
438
+ }
439
+
440
+ /**
441
+ * Sanitize a prompt before persisting it to a *-state.json file.
442
+ * Returning the raw prompt risks storing large system echoes or pasted
443
+ * hook output, which persistent-mode.mjs would then blast back into the
444
+ * next Stop-hook block reason on every iteration.
445
+ *
446
+ * Strategy:
447
+ * 1. Strip echoes first. If non-empty content remains AND that remainder no
448
+ * longer looks like an echo, keep it (preserves the real user request in
449
+ * an "echo + blank line + real request" paste).
450
+ * 2. Otherwise, if the raw prompt looks like a pure echo, substitute the
451
+ * placeholder sentinel.
452
+ * 3. Finally, hard-truncate to MAX_STATE_PROMPT_LEN chars.
453
+ */
454
+ function sanitizePromptForState(prompt) {
455
+ if (typeof prompt !== 'string') return '';
456
+ const trimmed = prompt.trim();
457
+ if (!trimmed) return '';
458
+
459
+ const stripped = stripSystemEchoes(trimmed).trim();
460
+ if (stripped.length > 0 && !looksLikeSystemEcho(stripped)) {
461
+ return stripped.length > MAX_STATE_PROMPT_LEN
462
+ ? `${stripped.slice(0, MAX_STATE_PROMPT_LEN - 3)}...`
463
+ : stripped;
464
+ }
465
+
466
+ if (looksLikeSystemEcho(trimmed)) {
467
+ return '(prompt omitted: pasted system echo)';
468
+ }
469
+
470
+ // Fallback (stripping left nothing but original isn't recognizably an echo)
471
+ const base = stripped.length > 0 ? stripped : trimmed;
472
+ return base.length > MAX_STATE_PROMPT_LEN
473
+ ? `${base.slice(0, MAX_STATE_PROMPT_LEN - 3)}...`
474
+ : base;
475
+ }
350
476
  const MODE_REFERENCE_PATTERN =
351
477
  /\b(?:ralph|autopilot|auto[\s-]?pilot|ultrawork|ulw|ralplan|ultrathink|deepsearch|deep[\s-]?analyze|deepanalyze|deep[\s-]interview|ouroboros|ccg|claude-codex-gemini|deerflow)\b/gi;
352
478
 
@@ -461,15 +587,25 @@ function isInformationalKeywordContext(text, position, keywordLength, keywordTex
461
587
  }
462
588
 
463
589
  function hasActionableKeyword(text, pattern) {
590
+ // Strip system-generated echo blocks (persistent-mode/keyword-detector
591
+ // outputs, Stop-hook wrappers) BEFORE searching for keywords. Otherwise
592
+ // pasting a previous "[RALPH LOOP - ITERATION N] ..." block into a prompt
593
+ // would re-activate ralph mode and the echo itself would be saved as
594
+ // state.prompt, which persistent-mode.mjs then blasts back on every
595
+ // iteration — a self-reinforcing loop that is hard to cancel.
596
+ const searchText = looksLikeSystemEcho(text)
597
+ ? stripSystemEchoes(text)
598
+ : text;
599
+
464
600
  const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
465
601
  const globalPattern = new RegExp(pattern.source, flags);
466
602
 
467
- for (const match of text.matchAll(globalPattern)) {
603
+ for (const match of searchText.matchAll(globalPattern)) {
468
604
  if (match.index === undefined) {
469
605
  continue;
470
606
  }
471
607
 
472
- if (isInformationalKeywordContext(text, match.index, match[0].length, match[0])) {
608
+ if (isInformationalKeywordContext(searchText, match.index, match[0].length, match[0])) {
473
609
  continue;
474
610
  }
475
611
 
@@ -496,19 +632,26 @@ function hasExplicitInvocationContext(text, position, keywordLength, keywordText
496
632
  }
497
633
 
498
634
  function hasActionableRalplanKeyword(text, pattern) {
635
+ // Same echo guard as hasActionableKeyword.
636
+ const searchText = looksLikeSystemEcho(text)
637
+ ? stripSystemEchoes(text)
638
+ : text;
639
+
499
640
  const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
500
641
  const globalPattern = new RegExp(pattern.source, flags);
501
642
 
502
- for (const match of text.matchAll(globalPattern)) {
643
+ for (const match of searchText.matchAll(globalPattern)) {
503
644
  if (match.index === undefined) {
504
645
  continue;
505
646
  }
506
647
 
507
- if (isInformationalKeywordContext(text, match.index, match[0].length, match[0])) {
648
+ // match.index is relative to searchText — use searchText for all
649
+ // downstream context lookups to keep indices aligned.
650
+ if (isInformationalKeywordContext(searchText, match.index, match[0].length, match[0])) {
508
651
  continue;
509
652
  }
510
653
 
511
- if (!hasExplicitInvocationContext(text, match.index, match[0].length, match[0])) {
654
+ if (!hasExplicitInvocationContext(searchText, match.index, match[0].length, match[0])) {
512
655
  continue;
513
656
  }
514
657
 
@@ -520,6 +663,10 @@ function hasActionableRalplanKeyword(text, pattern) {
520
663
 
521
664
  // Create state file for a mode
522
665
  function activateState(directory, prompt, stateName, sessionId) {
666
+ const now = new Date().toISOString();
667
+ // Sanitize prompt BEFORE writing to state: prevents pasted system echoes
668
+ // and oversized blobs from being persisted and re-emitted by Stop hook.
669
+ const safePrompt = sanitizePromptForState(prompt);
523
670
  let state;
524
671
 
525
672
  if (stateName === 'ralph') {
@@ -528,45 +675,51 @@ function activateState(directory, prompt, stateName, sessionId) {
528
675
  active: true,
529
676
  iteration: 1,
530
677
  max_iterations: 100,
531
- started_at: new Date().toISOString(),
532
- prompt: prompt,
678
+ started_at: now,
679
+ prompt: safePrompt,
533
680
  session_id: sessionId || undefined,
534
681
  project_path: directory,
535
682
  linked_ultrawork: true,
536
683
  awaiting_confirmation: true,
537
- last_checked_at: new Date().toISOString()
684
+ awaiting_confirmation_set_at: now,
685
+ last_checked_at: now
538
686
  };
539
687
  } else if (stateName === 'ralplan') {
540
688
  // Ralplan needs active + session_id for stop-hook enforcement
541
689
  state = {
542
690
  active: true,
543
- started_at: new Date().toISOString(),
691
+ started_at: now,
544
692
  session_id: sessionId || undefined,
545
693
  project_path: directory,
546
694
  awaiting_confirmation: true,
547
- last_checked_at: new Date().toISOString()
695
+ awaiting_confirmation_set_at: now,
696
+ last_checked_at: now
548
697
  };
549
698
  } else {
550
699
  // Generic state for ultrawork, autopilot, etc.
551
700
  state = {
552
701
  active: true,
553
- started_at: new Date().toISOString(),
554
- original_prompt: prompt,
702
+ started_at: now,
703
+ original_prompt: safePrompt,
555
704
  session_id: sessionId || undefined,
556
705
  project_path: directory,
557
706
  reinforcement_count: 0,
558
707
  awaiting_confirmation: true,
559
- last_checked_at: new Date().toISOString()
708
+ awaiting_confirmation_set_at: now,
709
+ last_checked_at: now
560
710
  };
561
711
  }
562
712
 
563
- // Write to session-scoped path if sessionId available
713
+ // Write to session-scoped path if sessionId available. Use atomic writes
714
+ // so that concurrent hook processes cannot expose half-written JSON to
715
+ // persistent-mode.mjs's readJsonFile (which would otherwise return null
716
+ // and temporarily drop mode enforcement).
564
717
  if (sessionId && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) {
565
718
  const sessionDir = join(directory, '.omcp', 'state', 'sessions', sessionId);
566
719
  if (!existsSync(sessionDir)) {
567
720
  try { mkdirSync(sessionDir, { recursive: true }); } catch {}
568
721
  }
569
- try { writeFileSync(join(sessionDir, `${stateName}-state.json`), JSON.stringify(state, null, 2), { mode: 0o600 }); } catch {}
722
+ try { atomicWriteFileSync(join(sessionDir, `${stateName}-state.json`), JSON.stringify(state, null, 2)); } catch {}
570
723
  return;
571
724
  }
572
725
 
@@ -575,7 +728,7 @@ function activateState(directory, prompt, stateName, sessionId) {
575
728
  if (!existsSync(localDir)) {
576
729
  try { mkdirSync(localDir, { recursive: true }); } catch {}
577
730
  }
578
- try { writeFileSync(join(localDir, `${stateName}-state.json`), JSON.stringify(state, null, 2), { mode: 0o600 }); } catch {}
731
+ try { atomicWriteFileSync(join(localDir, `${stateName}-state.json`), JSON.stringify(state, null, 2)); } catch {}
579
732
  }
580
733
 
581
734
  function activateRalplanStartupState(directory, prompt, sessionId) {
@@ -584,7 +737,7 @@ function activateRalplanStartupState(directory, prompt, sessionId) {
584
737
  active: true,
585
738
  started_at: now,
586
739
  current_phase: 'ralplan',
587
- original_prompt: prompt,
740
+ original_prompt: sanitizePromptForState(prompt),
588
741
  session_id: sessionId || undefined,
589
742
  project_path: directory,
590
743
  awaiting_confirmation: true,
@@ -597,7 +750,7 @@ function activateRalplanStartupState(directory, prompt, sessionId) {
597
750
  if (!existsSync(sessionDir)) {
598
751
  try { mkdirSync(sessionDir, { recursive: true }); } catch {}
599
752
  }
600
- try { writeFileSync(join(sessionDir, 'ralplan-state.json'), JSON.stringify(state, null, 2), { mode: 0o600 }); } catch {}
753
+ try { atomicWriteFileSync(join(sessionDir, 'ralplan-state.json'), JSON.stringify(state, null, 2)); } catch {}
601
754
  return;
602
755
  }
603
756
 
@@ -605,7 +758,7 @@ function activateRalplanStartupState(directory, prompt, sessionId) {
605
758
  if (!existsSync(localDir)) {
606
759
  try { mkdirSync(localDir, { recursive: true }); } catch {}
607
760
  }
608
- try { writeFileSync(join(localDir, 'ralplan-state.json'), JSON.stringify(state, null, 2), { mode: 0o600 }); } catch {}
761
+ try { atomicWriteFileSync(join(localDir, 'ralplan-state.json'), JSON.stringify(state, null, 2)); } catch {}
609
762
  }
610
763
 
611
764
  /**
@@ -688,26 +841,27 @@ function isTeamEnabled() {
688
841
  }
689
842
 
690
843
  /**
691
- * Create a skill invocation message.
692
- * Prefers direct SKILL.md content injection (works for npm and plugin installs).
693
- * Falls back to Skill tool invocation (requires plugin marketplace install).
844
+ * Create a compact skill invocation guide without inlining SKILL.md bodies.
845
+ * Full skill text remains available by path, avoiding UserPromptSubmit token blowups.
694
846
  */
695
847
  function createSkillInvocation(skillName, originalPrompt, args = '') {
696
- const argsSection = args ? `\nArguments: ${args}` : '';
697
- const skillContent = loadSkillContent(skillName);
698
- if (skillContent) {
699
- return `[MAGIC KEYWORD: ${skillName.toUpperCase()}]\n\n${skillContent}\n\n---\nUser request:\n${originalPrompt}${argsSection}`;
700
- }
701
- return `[MAGIC KEYWORD: ${skillName.toUpperCase()}]
848
+ const argsSection = args ? `
849
+ Arguments: ${args}` : '';
850
+ const skillPath = resolveSkillPath(skillName);
851
+ const pathStatus = existsSync(skillPath)
852
+ ? `Read fallback: open ${skillPath} and follow its SKILL.md instructions.`
853
+ : `Read fallback: locate skills/${skillName}/SKILL.md in the active oh-my-copilot plugin/install and follow it.`;
702
854
 
703
- You MUST invoke the skill using the Skill tool:
855
+ return `[MAGIC KEYWORD: ${skillName.toUpperCase()}]
704
856
 
705
- Skill: oh-my-copilot:${skillName}${argsSection}
857
+ Skill routing detected: ${skillName}
858
+ Preferred invocation: /oh-my-copilot:${skillName}${args ? ` ${args}` : ''}
859
+ ${pathStatus}${argsSection}
706
860
 
707
- User request:
708
- ${originalPrompt}
861
+ User request (compact echo; original prompt remains authoritative):
862
+ ${compactHookText(originalPrompt)}
709
863
 
710
- IMPORTANT: Invoke the skill IMMEDIATELY. Do not proceed without loading the skill instructions.`;
864
+ IMPORTANT: Start the ${skillName} workflow immediately. If the slash invocation is unavailable, read the SKILL.md at the fallback path instead of relying on this compact guide.`;
711
865
  }
712
866
 
713
867
  /**
@@ -720,23 +874,24 @@ function createMultiSkillInvocation(skills, originalPrompt) {
720
874
  }
721
875
 
722
876
  const skillBlocks = skills.map((s, i) => {
723
- const argsSection = s.args ? `\nArguments: ${s.args}` : '';
724
- const content = loadSkillContent(s.name);
725
- if (content) {
726
- return `### Skill ${i + 1}: ${s.name.toUpperCase()}\n\n${content}${argsSection}`;
727
- }
728
- return `### Skill ${i + 1}: ${s.name.toUpperCase()}\nSkill: oh-my-copilot:${s.name}${argsSection}`;
877
+ const skillPath = resolveSkillPath(s.name);
878
+ const argsText = s.args ? ` ${s.args}` : '';
879
+ const pathStatus = existsSync(skillPath)
880
+ ? `Read fallback: ${skillPath}`
881
+ : `Read fallback: locate skills/${s.name}/SKILL.md in the active oh-my-copilot plugin/install`;
882
+ return `### Skill ${i + 1}: ${s.name.toUpperCase()}
883
+ Preferred invocation: /oh-my-copilot:${s.name}${argsText}
884
+ ${pathStatus}`;
729
885
  }).join('\n\n');
730
886
 
731
- const hasDirectContent = skills.some(s => loadSkillContent(s.name));
732
887
  return `[MAGIC KEYWORDS DETECTED: ${skills.map(s => s.name.toUpperCase()).join(', ')}]
733
888
 
734
- ${hasDirectContent ? 'Execute ALL of the following skills in order:' : 'You MUST invoke ALL of the following skills using the Skill tool, in order:'}
889
+ Execute ALL detected workflows in order using compact invocation guidance. Do not inline full SKILL.md files into the prompt.
735
890
 
736
891
  ${skillBlocks}
737
892
 
738
- User request:
739
- ${originalPrompt}
893
+ User request (compact echo; original prompt remains authoritative):
894
+ ${compactHookText(originalPrompt)}
740
895
 
741
896
  IMPORTANT: Complete ALL skills listed above in order. Start with the first skill IMMEDIATELY.`;
742
897
  }
@@ -844,7 +999,14 @@ async function main() {
844
999
  return;
845
1000
  }
846
1001
 
847
- const cleanPrompt = sanitizeForKeywordDetection(prompt).toLowerCase();
1002
+ // Strip pasted system-echo blocks BEFORE any mode-keyword dispatch runs.
1003
+ // This covers both the hasActionableKeyword() call sites AND alternative
1004
+ // matchers (e.g. isAntiSlopCleanupRequest) that bypass it. The helper
1005
+ // inside hasActionableKeyword also strips defensively, which stays as
1006
+ // belt-and-suspenders.
1007
+ const cleanPrompt = stripSystemEchoes(
1008
+ sanitizeForKeywordDetection(prompt).toLowerCase(),
1009
+ );
848
1010
 
849
1011
  // Collect all matching keywords
850
1012
  const matches = [];
@@ -19,7 +19,7 @@ const { createHash } = require('crypto');
19
19
  * @returns {Promise<string>} Absolute path to the .omcp root
20
20
  */
21
21
  async function resolveOmcStateRoot(directory) {
22
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
22
+ const pluginRoot = process.env.PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;
23
23
  if (pluginRoot) {
24
24
  try {
25
25
  const { pathToFileURL } = require('url');
@@ -5,7 +5,7 @@
5
5
  * hook scripts, respecting the OMC_STATE_DIR environment variable.
6
6
  *
7
7
  * Delegates to getOmcRoot() from dist/lib/worktree-paths.js (the canonical
8
- * implementation) when CLAUDE_PLUGIN_ROOT is available. Falls back to inline
8
+ * implementation) when PLUGIN_ROOT is available. Falls back to inline
9
9
  * logic when dist is not built — this should never happen in production, but
10
10
  * provides a safe fallback during development or first-run scenarios.
11
11
  *
@@ -13,7 +13,7 @@
13
13
  * - Uses directory path as hash source (not git remote URL). Matches
14
14
  * canonical behavior for local-only repos; may differ for remote-backed
15
15
  * repos when dist is missing — acceptable since dist is always present
16
- * in production (CLAUDE_PLUGIN_ROOT is always set).
16
+ * in production (PLUGIN_ROOT is always set).
17
17
  */
18
18
 
19
19
  import { join, basename } from 'path';
@@ -27,7 +27,7 @@ import { pathToFileURL } from 'url';
27
27
  * @returns {Promise<string>} Absolute path to the .omcp root
28
28
  */
29
29
  export async function resolveOmcStateRoot(directory) {
30
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
30
+ const pluginRoot = process.env.PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;
31
31
  if (pluginRoot) {
32
32
  try {
33
33
  const { getOmcRoot } = await import(
@@ -244,7 +244,7 @@ async function sendStopNotification(modeName, stateData, sessionId, directory) {
244
244
  if (stateData._stopNotified) return;
245
245
 
246
246
  try {
247
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
247
+ const pluginRoot = process.env.PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;
248
248
  if (!pluginRoot) return;
249
249
 
250
250
  const { pathToFileURL } = require('url');
@@ -572,6 +572,32 @@ function readStateFileWithSession(stateDir, filename, sessionId) {
572
572
  return readStateFile(stateDir, filename);
573
573
  }
574
574
 
575
+ const WORKFLOW_SLOT_TOMBSTONE_TTL_MS = 24 * 60 * 60 * 1000;
576
+
577
+ function isWorkflowSlotTombstonedForMode(stateDir, mode, sessionId) {
578
+ const safeSessionId = sessionId && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId) ? sessionId : "";
579
+ const ledgerPath = safeSessionId
580
+ ? join(stateDir, "sessions", safeSessionId, "skill-active-state.json")
581
+ : join(stateDir, "skill-active-state.json");
582
+ const ledger = readJsonFile(ledgerPath);
583
+ const slot = ledger?.active_skills?.[mode];
584
+ if (!slot || typeof slot !== "object") return false;
585
+ if (typeof slot.completed_at !== "string" || !slot.completed_at) return false;
586
+ const completedAt = new Date(slot.completed_at).getTime();
587
+ if (!Number.isFinite(completedAt)) return true;
588
+ return Date.now() - completedAt < WORKFLOW_SLOT_TOMBSTONE_TTL_MS;
589
+ }
590
+
591
+ function isAuthoritativeModeActive(stateDir, mode, loaded, sessionId) {
592
+ const state = loaded?.state;
593
+ if (!state?.active) return false;
594
+ if (isWorkflowSlotTombstonedForMode(stateDir, mode, sessionId)) return false;
595
+ const safeSessionId = sessionId && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId) ? sessionId : "";
596
+ if (safeSessionId && state.session_id && state.session_id !== safeSessionId) return false;
597
+ return true;
598
+ }
599
+
600
+
575
601
  function getActiveSubagentCount(stateDir) {
576
602
  try {
577
603
  const tracking = readJsonFile(join(stateDir, "subagent-tracking.json"));
@@ -891,7 +917,7 @@ async function main() {
891
917
 
892
918
  // Priority 1: Ralph Loop (explicit persistence mode)
893
919
  // Skip if state is stale (older than 2 hours) - prevents blocking new sessions
894
- if (ralph.state?.active && !isAwaitingConfirmation(ralph.state) && !isStaleState(ralph.state) && isSessionMatch(ralph.state, sessionId)) {
920
+ if (isAuthoritativeModeActive(stateDir, "ralph", ralph, sessionId) && !isAwaitingConfirmation(ralph.state) && !isStaleState(ralph.state) && isSessionMatch(ralph.state, sessionId)) {
895
921
  const iteration = ralph.state.iteration || 1;
896
922
  const maxIter = ralph.state.max_iterations || 100;
897
923
 
@@ -1212,7 +1238,7 @@ async function main() {
1212
1238
  // Session isolation: only block if state belongs to this session (issue #311)
1213
1239
  // Project isolation: only block if state belongs to this project
1214
1240
  if (
1215
- ultrawork.state?.active && !isAwaitingConfirmation(ultrawork.state) &&
1241
+ isAuthoritativeModeActive(stateDir, "ultrawork", ultrawork, sessionId) && !isAwaitingConfirmation(ultrawork.state) &&
1216
1242
  !isStaleState(ultrawork.state) &&
1217
1243
  isSessionMatch(ultrawork.state, sessionId) &&
1218
1244
  isStateForCurrentProject(ultrawork.state, directory, ultrawork.isGlobal)
@@ -1327,7 +1353,7 @@ async function main() {
1327
1353
  const idleRepoState = getIdleNotificationRepoState(directory);
1328
1354
  if (sessionId && shouldSendIdleNotification(stateDir, idleRepoState)) {
1329
1355
  try {
1330
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
1356
+ const pluginRoot = process.env.PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT;
1331
1357
  if (pluginRoot) {
1332
1358
  const { pathToFileURL } = require('url');
1333
1359
  import(pathToFileURL(join(pluginRoot, 'dist', 'notifications', 'index.js')).href)
@@ -370,6 +370,32 @@ function readStateFileWithSession(stateDir, globalStateDir, filename, sessionId)
370
370
  return readStateFile(stateDir, globalStateDir, filename);
371
371
  }
372
372
 
373
+ const WORKFLOW_SLOT_TOMBSTONE_TTL_MS = 24 * 60 * 60 * 1000;
374
+
375
+ function isWorkflowSlotTombstonedForMode(stateDir, mode, sessionId) {
376
+ const safeSessionId = sanitizeSessionId(sessionId);
377
+ const ledgerPath = safeSessionId
378
+ ? join(stateDir, "sessions", safeSessionId, "skill-active-state.json")
379
+ : join(stateDir, "skill-active-state.json");
380
+ const ledger = readJsonFile(ledgerPath);
381
+ const slot = ledger?.active_skills?.[mode];
382
+ if (!slot || typeof slot !== "object") return false;
383
+ if (typeof slot.completed_at !== "string" || !slot.completed_at) return false;
384
+ const completedAt = new Date(slot.completed_at).getTime();
385
+ if (!Number.isFinite(completedAt)) return true;
386
+ return Date.now() - completedAt < WORKFLOW_SLOT_TOMBSTONE_TTL_MS;
387
+ }
388
+
389
+ function isAuthoritativeModeActive(stateDir, mode, loaded, sessionId) {
390
+ const state = loaded?.state;
391
+ if (!state?.active) return false;
392
+ if (isWorkflowSlotTombstonedForMode(stateDir, mode, sessionId)) return false;
393
+ const safeSessionId = sanitizeSessionId(sessionId);
394
+ if (safeSessionId && state.session_id && state.session_id !== safeSessionId) return false;
395
+ return true;
396
+ }
397
+
398
+
373
399
  function isSessionCancelInProgress(stateDir, sessionId) {
374
400
  const isActiveSignal = (signalPath) => {
375
401
  const signal = readJsonFile(signalPath);
@@ -762,7 +788,7 @@ async function main() {
762
788
  // Priority 1: Ralph Loop (explicit persistence mode)
763
789
  // Skip if state is stale (older than 2 hours) - prevents blocking new sessions
764
790
  if (
765
- ralph.state?.active && !isAwaitingConfirmation(ralph.state) &&
791
+ isAuthoritativeModeActive(stateDir, "ralph", ralph, sessionId) && !isAwaitingConfirmation(ralph.state) &&
766
792
  !isStaleState(ralph.state) &&
767
793
  isStateForCurrentProject(ralph.state, directory, ralph.isGlobal)
768
794
  ) {
@@ -1102,7 +1128,7 @@ async function main() {
1102
1128
  // If state has session_id, it must match. If no session_id (legacy), allow.
1103
1129
  // Project isolation: only block if state belongs to this project
1104
1130
  if (
1105
- ultrawork.state?.active && !isAwaitingConfirmation(ultrawork.state) &&
1131
+ isAuthoritativeModeActive(stateDir, "ultrawork", ultrawork, sessionId) && !isAwaitingConfirmation(ultrawork.state) &&
1106
1132
  !isStaleState(ultrawork.state) &&
1107
1133
  (hasValidSessionId
1108
1134
  ? ultrawork.state.session_id === sessionId
@@ -271,9 +271,10 @@ try {
271
271
  // substitutes the real process.execPath so Copilot CLI always invokes the same
272
272
  // Node binary that ran this setup script.
273
273
  //
274
- // Two patterns are handled:
275
- // 1. New format – node "${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs" ... (all platforms)
276
- // 2. Old format – sh "${CLAUDE_PLUGIN_ROOT}/scripts/find-node.sh" ... (Windows
274
+ // Three patterns are handled:
275
+ // 1. Current – node "${PLUGIN_ROOT}/scripts/run.cjs" ... (all platforms)
276
+ // 2. Legacy – node "${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs" ... (migration)
277
+ // 3. Old format – sh "${CLAUDE_PLUGIN_ROOT}/scripts/find-node.sh" ... (Windows
277
278
  // backward-compat: migrates old installs to the new run.cjs chain)
278
279
  //
279
280
  // Fixes issues #909, #899, #892, #869.
@@ -283,11 +284,11 @@ try {
283
284
  const data = JSON.parse(readFileSync(hooksJsonPath, 'utf-8'));
284
285
  let patched = false;
285
286
 
286
- // Pattern 1 (new): node "${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs" <rest>
287
+ // Pattern 1+2 (current/legacy): node "${PLUGIN_ROOT}/scripts/run.cjs" <rest>
287
288
  const runCjsPattern =
288
- /^node ("\$\{CLAUDE_PLUGIN_ROOT\}\/scripts\/run\.cjs".*)$/;
289
+ /^node ("\$\{(?:PLUGIN_ROOT|CLAUDE_PLUGIN_ROOT)\}\/scripts\/run\.cjs".*)$/;
289
290
 
290
- // Pattern 2 (old, Windows backward-compat): sh find-node.sh <target> [args]
291
+ // Pattern 3 (old, Windows backward-compat): sh find-node.sh <target> [args]
291
292
  const findNodePattern =
292
293
  /^sh "\$\{CLAUDE_PLUGIN_ROOT\}\/scripts\/find-node\.sh" "(\$\{CLAUDE_PLUGIN_ROOT\}\/scripts\/[^"]+)"(.*)$/;
293
294
 
@@ -308,7 +309,7 @@ try {
308
309
  if (process.platform === 'win32') {
309
310
  const m2 = hook.command.match(findNodePattern);
310
311
  if (m2) {
311
- hook.command = `"${nodeBin}" "\${CLAUDE_PLUGIN_ROOT}/scripts/run.cjs" "${m2[1]}"${m2[2]}`;
312
+ hook.command = `"${nodeBin}" "\${PLUGIN_ROOT}/scripts/run.cjs" "${m2[1]}"${m2[2]}`;
312
313
  patched = true;
313
314
  }
314
315
  }
@@ -23,7 +23,7 @@ const __filename = fileURLToPath(import.meta.url);
23
23
  const __dirname = dirname(__filename);
24
24
 
25
25
  function getRuntimeBaseDir() {
26
- return process.env.CLAUDE_PLUGIN_ROOT || join(__dirname, '..');
26
+ return process.env.PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT || join(__dirname, '..');
27
27
  }
28
28
 
29
29
  // Dynamic import — graceful no-op when dist/ is not built (first run / dev)