@liangjie559567/ultrapower 5.5.12 → 5.5.13

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 (489) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/LICENSE +21 -21
  4. package/bridge/gyoshu_bridge.py +846 -846
  5. package/bridge/mcp-server.cjs +145 -38
  6. package/commands/wizard.md +5 -0
  7. package/dist/agents/__tests__/agent-wrapper.test.d.ts +2 -0
  8. package/dist/agents/__tests__/agent-wrapper.test.d.ts.map +1 -0
  9. package/dist/agents/__tests__/agent-wrapper.test.js +37 -0
  10. package/dist/agents/__tests__/agent-wrapper.test.js.map +1 -0
  11. package/dist/agents/__tests__/timeout-config.test.d.ts +2 -0
  12. package/dist/agents/__tests__/timeout-config.test.d.ts.map +1 -0
  13. package/dist/agents/__tests__/timeout-config.test.js +35 -0
  14. package/dist/agents/__tests__/timeout-config.test.js.map +1 -0
  15. package/dist/agents/__tests__/timeout-manager.test.d.ts +2 -0
  16. package/dist/agents/__tests__/timeout-manager.test.d.ts.map +1 -0
  17. package/dist/agents/__tests__/timeout-manager.test.js +37 -0
  18. package/dist/agents/__tests__/timeout-manager.test.js.map +1 -0
  19. package/dist/agents/agent-wrapper.d.ts +22 -0
  20. package/dist/agents/agent-wrapper.d.ts.map +1 -0
  21. package/dist/agents/agent-wrapper.js +51 -0
  22. package/dist/agents/agent-wrapper.js.map +1 -0
  23. package/dist/agents/coordinator-deprecated.d.ts +18 -0
  24. package/dist/agents/coordinator-deprecated.d.ts.map +1 -0
  25. package/dist/agents/coordinator-deprecated.js +38 -0
  26. package/dist/agents/coordinator-deprecated.js.map +1 -0
  27. package/dist/agents/index.d.ts +3 -0
  28. package/dist/agents/index.d.ts.map +1 -1
  29. package/dist/agents/index.js +4 -0
  30. package/dist/agents/index.js.map +1 -1
  31. package/dist/agents/timeout-config.d.ts +19 -0
  32. package/dist/agents/timeout-config.d.ts.map +1 -0
  33. package/dist/agents/timeout-config.js +57 -0
  34. package/dist/agents/timeout-config.js.map +1 -0
  35. package/dist/agents/timeout-manager.d.ts +30 -0
  36. package/dist/agents/timeout-manager.d.ts.map +1 -0
  37. package/dist/agents/timeout-manager.js +47 -0
  38. package/dist/agents/timeout-manager.js.map +1 -0
  39. package/dist/analytics/analytics-summary.d.ts.map +1 -1
  40. package/dist/analytics/analytics-summary.js +7 -1
  41. package/dist/analytics/analytics-summary.js.map +1 -1
  42. package/dist/analytics/metrics-collector.d.ts.map +1 -1
  43. package/dist/analytics/metrics-collector.js +9 -1
  44. package/dist/analytics/metrics-collector.js.map +1 -1
  45. package/dist/analytics/query-engine.d.ts.map +1 -1
  46. package/dist/analytics/query-engine.js +21 -3
  47. package/dist/analytics/query-engine.js.map +1 -1
  48. package/dist/analytics/token-tracker.js +3 -3
  49. package/dist/analytics/token-tracker.js.map +1 -1
  50. package/dist/analytics/transcript-scanner.d.ts.map +1 -1
  51. package/dist/analytics/transcript-scanner.js +1 -0
  52. package/dist/analytics/transcript-scanner.js.map +1 -1
  53. package/dist/audit/logger.d.ts +28 -0
  54. package/dist/audit/logger.d.ts.map +1 -0
  55. package/dist/audit/logger.js +78 -0
  56. package/dist/audit/logger.js.map +1 -0
  57. package/dist/audit/verify-cli.d.ts +2 -0
  58. package/dist/audit/verify-cli.d.ts.map +1 -0
  59. package/dist/audit/verify-cli.js +10 -0
  60. package/dist/audit/verify-cli.js.map +1 -0
  61. package/dist/core/hud-config.d.ts +19 -0
  62. package/dist/core/hud-config.d.ts.map +1 -0
  63. package/dist/core/hud-config.js +6 -0
  64. package/dist/core/hud-config.js.map +1 -0
  65. package/dist/core/job-types.d.ts +22 -0
  66. package/dist/core/job-types.d.ts.map +1 -0
  67. package/dist/core/job-types.js +6 -0
  68. package/dist/core/job-types.js.map +1 -0
  69. package/dist/features/diagnostics/error-matcher.d.ts +12 -0
  70. package/dist/features/diagnostics/error-matcher.d.ts.map +1 -0
  71. package/dist/features/diagnostics/error-matcher.js +41 -0
  72. package/dist/features/diagnostics/error-matcher.js.map +1 -0
  73. package/dist/features/diagnostics/index.d.ts +3 -0
  74. package/dist/features/diagnostics/index.d.ts.map +1 -0
  75. package/dist/features/diagnostics/index.js +3 -0
  76. package/dist/features/diagnostics/index.js.map +1 -0
  77. package/dist/features/diagnostics/solution-suggester.d.ts +12 -0
  78. package/dist/features/diagnostics/solution-suggester.d.ts.map +1 -0
  79. package/dist/features/diagnostics/solution-suggester.js +46 -0
  80. package/dist/features/diagnostics/solution-suggester.js.map +1 -0
  81. package/dist/features/diagnostics/types.d.ts +25 -0
  82. package/dist/features/diagnostics/types.d.ts.map +1 -0
  83. package/dist/features/diagnostics/types.js +5 -0
  84. package/dist/features/diagnostics/types.js.map +1 -0
  85. package/dist/features/state-manager/__tests__/cache.test.js +17 -17
  86. package/dist/features/state-manager/__tests__/cache.test.js.map +1 -1
  87. package/dist/features/state-manager/__tests__/encryption-performance.test.d.ts +2 -0
  88. package/dist/features/state-manager/__tests__/encryption-performance.test.d.ts.map +1 -0
  89. package/dist/features/state-manager/__tests__/encryption-performance.test.js +42 -0
  90. package/dist/features/state-manager/__tests__/encryption-performance.test.js.map +1 -0
  91. package/dist/features/state-manager/__tests__/encryption.test.d.ts +2 -0
  92. package/dist/features/state-manager/__tests__/encryption.test.d.ts.map +1 -0
  93. package/dist/features/state-manager/__tests__/encryption.test.js +68 -0
  94. package/dist/features/state-manager/__tests__/encryption.test.js.map +1 -0
  95. package/dist/features/state-manager/encryption.d.ts +24 -0
  96. package/dist/features/state-manager/encryption.d.ts.map +1 -0
  97. package/dist/features/state-manager/encryption.js +86 -0
  98. package/dist/features/state-manager/encryption.js.map +1 -0
  99. package/dist/features/state-manager/index.d.ts +4 -0
  100. package/dist/features/state-manager/index.d.ts.map +1 -1
  101. package/dist/features/state-manager/index.js +94 -6
  102. package/dist/features/state-manager/index.js.map +1 -1
  103. package/dist/features/state-manager/tiered-writer.d.ts +44 -0
  104. package/dist/features/state-manager/tiered-writer.d.ts.map +1 -0
  105. package/dist/features/state-manager/tiered-writer.js +76 -0
  106. package/dist/features/state-manager/tiered-writer.js.map +1 -0
  107. package/dist/features/state-manager/wal.d.ts +21 -0
  108. package/dist/features/state-manager/wal.d.ts.map +1 -0
  109. package/dist/features/state-manager/wal.js +75 -0
  110. package/dist/features/state-manager/wal.js.map +1 -0
  111. package/dist/features/task-templates/index.d.ts +13 -0
  112. package/dist/features/task-templates/index.d.ts.map +1 -0
  113. package/dist/features/task-templates/index.js +31 -0
  114. package/dist/features/task-templates/index.js.map +1 -0
  115. package/dist/features/task-templates/wizard-integration.d.ts +15 -0
  116. package/dist/features/task-templates/wizard-integration.d.ts.map +1 -0
  117. package/dist/features/task-templates/wizard-integration.js +27 -0
  118. package/dist/features/task-templates/wizard-integration.js.map +1 -0
  119. package/dist/features/wizard/__tests__/engine.test.d.ts +2 -0
  120. package/dist/features/wizard/__tests__/engine.test.d.ts.map +1 -0
  121. package/dist/features/wizard/__tests__/engine.test.js +78 -0
  122. package/dist/features/wizard/__tests__/engine.test.js.map +1 -0
  123. package/dist/features/wizard/__tests__/recommendation-engine.test.d.ts +2 -0
  124. package/dist/features/wizard/__tests__/recommendation-engine.test.d.ts.map +1 -0
  125. package/dist/features/wizard/__tests__/recommendation-engine.test.js +43 -0
  126. package/dist/features/wizard/__tests__/recommendation-engine.test.js.map +1 -0
  127. package/dist/features/wizard/engine.d.ts +15 -0
  128. package/dist/features/wizard/engine.d.ts.map +1 -0
  129. package/dist/features/wizard/engine.js +74 -0
  130. package/dist/features/wizard/engine.js.map +1 -0
  131. package/dist/features/wizard/index.d.ts +8 -0
  132. package/dist/features/wizard/index.d.ts.map +1 -0
  133. package/dist/features/wizard/index.js +7 -0
  134. package/dist/features/wizard/index.js.map +1 -0
  135. package/dist/features/wizard/questions.d.ts +6 -0
  136. package/dist/features/wizard/questions.d.ts.map +1 -0
  137. package/dist/features/wizard/questions.js +64 -0
  138. package/dist/features/wizard/questions.js.map +1 -0
  139. package/dist/features/wizard/recommendation-engine.d.ts +6 -0
  140. package/dist/features/wizard/recommendation-engine.d.ts.map +1 -0
  141. package/dist/features/wizard/recommendation-engine.js +33 -0
  142. package/dist/features/wizard/recommendation-engine.js.map +1 -0
  143. package/dist/features/wizard/types.d.ts +23 -0
  144. package/dist/features/wizard/types.d.ts.map +1 -0
  145. package/dist/features/wizard/types.js +5 -0
  146. package/dist/features/wizard/types.js.map +1 -0
  147. package/dist/features/workflow-recommender/context-analyzer.d.ts +6 -0
  148. package/dist/features/workflow-recommender/context-analyzer.d.ts.map +1 -0
  149. package/dist/features/workflow-recommender/context-analyzer.js +20 -0
  150. package/dist/features/workflow-recommender/context-analyzer.js.map +1 -0
  151. package/dist/features/workflow-recommender/index.d.ts +8 -0
  152. package/dist/features/workflow-recommender/index.d.ts.map +1 -0
  153. package/dist/features/workflow-recommender/index.js +7 -0
  154. package/dist/features/workflow-recommender/index.js.map +1 -0
  155. package/dist/features/workflow-recommender/intent-classifier.d.ts +6 -0
  156. package/dist/features/workflow-recommender/intent-classifier.d.ts.map +1 -0
  157. package/dist/features/workflow-recommender/intent-classifier.js +24 -0
  158. package/dist/features/workflow-recommender/intent-classifier.js.map +1 -0
  159. package/dist/features/workflow-recommender/recommendation-engine.d.ts +6 -0
  160. package/dist/features/workflow-recommender/recommendation-engine.d.ts.map +1 -0
  161. package/dist/features/workflow-recommender/recommendation-engine.js +110 -0
  162. package/dist/features/workflow-recommender/recommendation-engine.js.map +1 -0
  163. package/dist/features/workflow-recommender/types.d.ts +20 -0
  164. package/dist/features/workflow-recommender/types.d.ts.map +1 -0
  165. package/dist/features/workflow-recommender/types.js +5 -0
  166. package/dist/features/workflow-recommender/types.js.map +1 -0
  167. package/dist/hooks/__tests__/bridge-normalize.test.d.ts +2 -0
  168. package/dist/hooks/__tests__/bridge-normalize.test.d.ts.map +1 -0
  169. package/dist/hooks/__tests__/bridge-normalize.test.js +90 -0
  170. package/dist/hooks/__tests__/bridge-normalize.test.js.map +1 -0
  171. package/dist/hooks/__tests__/bridge-security.test.js +23 -41
  172. package/dist/hooks/__tests__/bridge-security.test.js.map +1 -1
  173. package/dist/hooks/auto-slash-command/__tests__/detector.test.d.ts +2 -0
  174. package/dist/hooks/auto-slash-command/__tests__/detector.test.d.ts.map +1 -0
  175. package/dist/hooks/auto-slash-command/__tests__/detector.test.js +70 -0
  176. package/dist/hooks/auto-slash-command/__tests__/detector.test.js.map +1 -0
  177. package/dist/hooks/auto-slash-command/__tests__/executor.test.d.ts +2 -0
  178. package/dist/hooks/auto-slash-command/__tests__/executor.test.d.ts.map +1 -0
  179. package/dist/hooks/auto-slash-command/__tests__/executor.test.js +55 -0
  180. package/dist/hooks/auto-slash-command/__tests__/executor.test.js.map +1 -0
  181. package/dist/hooks/auto-slash-command/__tests__/index.test.d.ts +2 -0
  182. package/dist/hooks/auto-slash-command/__tests__/index.test.d.ts.map +1 -0
  183. package/dist/hooks/auto-slash-command/__tests__/index.test.js +50 -0
  184. package/dist/hooks/auto-slash-command/__tests__/index.test.js.map +1 -0
  185. package/dist/hooks/autopilot/__tests__/prompts.test.js +19 -1
  186. package/dist/hooks/autopilot/__tests__/prompts.test.js.map +1 -1
  187. package/dist/hooks/autopilot/enforcement.d.ts +1 -1
  188. package/dist/hooks/autopilot/enforcement.d.ts.map +1 -1
  189. package/dist/hooks/autopilot/enforcement.js +1 -1
  190. package/dist/hooks/autopilot/enforcement.js.map +1 -1
  191. package/dist/hooks/bridge-normalize.d.ts +43 -3
  192. package/dist/hooks/bridge-normalize.d.ts.map +1 -1
  193. package/dist/hooks/bridge-normalize.js +110 -15
  194. package/dist/hooks/bridge-normalize.js.map +1 -1
  195. package/dist/hooks/bridge-types.d.ts +48 -0
  196. package/dist/hooks/bridge-types.d.ts.map +1 -0
  197. package/dist/hooks/bridge-types.js +6 -0
  198. package/dist/hooks/bridge-types.js.map +1 -0
  199. package/dist/hooks/bridge.d.ts +1 -43
  200. package/dist/hooks/bridge.d.ts.map +1 -1
  201. package/dist/hooks/bridge.js +18 -2
  202. package/dist/hooks/bridge.js.map +1 -1
  203. package/dist/hooks/dependency-analyzer.d.ts +32 -0
  204. package/dist/hooks/dependency-analyzer.d.ts.map +1 -0
  205. package/dist/hooks/dependency-analyzer.js +199 -0
  206. package/dist/hooks/dependency-analyzer.js.map +1 -0
  207. package/dist/hooks/index.d.ts +2 -1
  208. package/dist/hooks/index.d.ts.map +1 -1
  209. package/dist/hooks/index.js.map +1 -1
  210. package/dist/hooks/learner/__tests__/detector.test.d.ts +2 -0
  211. package/dist/hooks/learner/__tests__/detector.test.d.ts.map +1 -0
  212. package/dist/hooks/learner/__tests__/detector.test.js +170 -0
  213. package/dist/hooks/learner/__tests__/detector.test.js.map +1 -0
  214. package/dist/hooks/learner/__tests__/index.test.d.ts +2 -0
  215. package/dist/hooks/learner/__tests__/index.test.d.ts.map +1 -0
  216. package/dist/hooks/learner/__tests__/index.test.js +48 -0
  217. package/dist/hooks/learner/__tests__/index.test.js.map +1 -0
  218. package/dist/hooks/learner/__tests__/matcher.test.d.ts +2 -0
  219. package/dist/hooks/learner/__tests__/matcher.test.d.ts.map +1 -0
  220. package/dist/hooks/learner/__tests__/matcher.test.js +114 -0
  221. package/dist/hooks/learner/__tests__/matcher.test.js.map +1 -0
  222. package/dist/hooks/learner/__tests__/promotion.test.d.ts +2 -0
  223. package/dist/hooks/learner/__tests__/promotion.test.d.ts.map +1 -0
  224. package/dist/hooks/learner/__tests__/promotion.test.js +146 -0
  225. package/dist/hooks/learner/__tests__/promotion.test.js.map +1 -0
  226. package/dist/hooks/learner/__tests__/validator.test.d.ts +2 -0
  227. package/dist/hooks/learner/__tests__/validator.test.d.ts.map +1 -0
  228. package/dist/hooks/learner/__tests__/validator.test.js +123 -0
  229. package/dist/hooks/learner/__tests__/validator.test.js.map +1 -0
  230. package/dist/hooks/learner/__tests__/writer.test.d.ts +2 -0
  231. package/dist/hooks/learner/__tests__/writer.test.d.ts.map +1 -0
  232. package/dist/hooks/learner/__tests__/writer.test.js +141 -0
  233. package/dist/hooks/learner/__tests__/writer.test.js.map +1 -0
  234. package/dist/hooks/learner/detection-hook.js +2 -2
  235. package/dist/hooks/learner/detection-hook.js.map +1 -1
  236. package/dist/hooks/parallel-executor.d.ts +24 -0
  237. package/dist/hooks/parallel-executor.d.ts.map +1 -0
  238. package/dist/hooks/parallel-executor.js +82 -0
  239. package/dist/hooks/parallel-executor.js.map +1 -0
  240. package/dist/hooks/persistent-mode/index.d.ts +2 -21
  241. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  242. package/dist/hooks/persistent-mode/index.js +4 -85
  243. package/dist/hooks/persistent-mode/index.js.map +1 -1
  244. package/dist/hooks/persistent-mode/tool-error.d.ts +15 -0
  245. package/dist/hooks/persistent-mode/tool-error.d.ts.map +1 -0
  246. package/dist/hooks/persistent-mode/tool-error.js +80 -0
  247. package/dist/hooks/persistent-mode/tool-error.js.map +1 -0
  248. package/dist/hooks/pre-compact/index.d.ts.map +1 -1
  249. package/dist/hooks/pre-compact/index.js +0 -1
  250. package/dist/hooks/pre-compact/index.js.map +1 -1
  251. package/dist/hooks/project-memory/learner.d.ts +13 -1
  252. package/dist/hooks/project-memory/learner.d.ts.map +1 -1
  253. package/dist/hooks/project-memory/learner.js +24 -12
  254. package/dist/hooks/project-memory/learner.js.map +1 -1
  255. package/dist/hooks/race-detector.d.ts +51 -0
  256. package/dist/hooks/race-detector.d.ts.map +1 -0
  257. package/dist/hooks/race-detector.js +121 -0
  258. package/dist/hooks/race-detector.js.map +1 -0
  259. package/dist/hooks/ralph/__tests__/loop.test.d.ts +2 -0
  260. package/dist/hooks/ralph/__tests__/loop.test.d.ts.map +1 -0
  261. package/dist/hooks/ralph/__tests__/loop.test.js +268 -0
  262. package/dist/hooks/ralph/__tests__/loop.test.js.map +1 -0
  263. package/dist/hooks/ralph/__tests__/prd.test.d.ts +2 -0
  264. package/dist/hooks/ralph/__tests__/prd.test.d.ts.map +1 -0
  265. package/dist/hooks/ralph/__tests__/prd.test.js +197 -0
  266. package/dist/hooks/ralph/__tests__/prd.test.js.map +1 -0
  267. package/dist/hooks/ralph/__tests__/progress.test.d.ts +2 -0
  268. package/dist/hooks/ralph/__tests__/progress.test.d.ts.map +1 -0
  269. package/dist/hooks/ralph/__tests__/progress.test.js +120 -0
  270. package/dist/hooks/ralph/__tests__/progress.test.js.map +1 -0
  271. package/dist/hooks/ralph/__tests__/verifier.test.d.ts +2 -0
  272. package/dist/hooks/ralph/__tests__/verifier.test.d.ts.map +1 -0
  273. package/dist/hooks/ralph/__tests__/verifier.test.js +75 -0
  274. package/dist/hooks/ralph/__tests__/verifier.test.js.map +1 -0
  275. package/dist/hooks/recovery/__tests__/context-window.test.d.ts +2 -0
  276. package/dist/hooks/recovery/__tests__/context-window.test.d.ts.map +1 -0
  277. package/dist/hooks/recovery/__tests__/context-window.test.js +131 -0
  278. package/dist/hooks/recovery/__tests__/context-window.test.js.map +1 -0
  279. package/dist/hooks/recovery/__tests__/edit-error.test.d.ts +2 -0
  280. package/dist/hooks/recovery/__tests__/edit-error.test.d.ts.map +1 -0
  281. package/dist/hooks/recovery/__tests__/edit-error.test.js +88 -0
  282. package/dist/hooks/recovery/__tests__/edit-error.test.js.map +1 -0
  283. package/dist/hooks/recovery/__tests__/index.test.d.ts +2 -0
  284. package/dist/hooks/recovery/__tests__/index.test.d.ts.map +1 -0
  285. package/dist/hooks/recovery/__tests__/index.test.js +270 -0
  286. package/dist/hooks/recovery/__tests__/index.test.js.map +1 -0
  287. package/dist/hooks/recovery/__tests__/session-recovery.test.d.ts +2 -0
  288. package/dist/hooks/recovery/__tests__/session-recovery.test.d.ts.map +1 -0
  289. package/dist/hooks/recovery/__tests__/session-recovery.test.js +129 -0
  290. package/dist/hooks/recovery/__tests__/session-recovery.test.js.map +1 -0
  291. package/dist/hooks/recovery/__tests__/storage.test.d.ts +2 -0
  292. package/dist/hooks/recovery/__tests__/storage.test.d.ts.map +1 -0
  293. package/dist/hooks/recovery/__tests__/storage.test.js +549 -0
  294. package/dist/hooks/recovery/__tests__/storage.test.js.map +1 -0
  295. package/dist/hooks/rules-injector/__tests__/finder.test.d.ts +2 -0
  296. package/dist/hooks/rules-injector/__tests__/finder.test.d.ts.map +1 -0
  297. package/dist/hooks/rules-injector/__tests__/finder.test.js +68 -0
  298. package/dist/hooks/rules-injector/__tests__/finder.test.js.map +1 -0
  299. package/dist/hooks/rules-injector/__tests__/index.test.d.ts +2 -0
  300. package/dist/hooks/rules-injector/__tests__/index.test.d.ts.map +1 -0
  301. package/dist/hooks/rules-injector/__tests__/index.test.js +58 -0
  302. package/dist/hooks/rules-injector/__tests__/index.test.js.map +1 -0
  303. package/dist/hooks/rules-injector/__tests__/matcher.test.d.ts +2 -0
  304. package/dist/hooks/rules-injector/__tests__/matcher.test.d.ts.map +1 -0
  305. package/dist/hooks/rules-injector/__tests__/matcher.test.js +86 -0
  306. package/dist/hooks/rules-injector/__tests__/matcher.test.js.map +1 -0
  307. package/dist/hooks/rules-injector/__tests__/parser.test.d.ts +2 -0
  308. package/dist/hooks/rules-injector/__tests__/parser.test.d.ts.map +1 -0
  309. package/dist/hooks/rules-injector/__tests__/parser.test.js +86 -0
  310. package/dist/hooks/rules-injector/__tests__/parser.test.js.map +1 -0
  311. package/dist/hooks/session-end/__tests__/index.test.d.ts +2 -0
  312. package/dist/hooks/session-end/__tests__/index.test.d.ts.map +1 -0
  313. package/dist/hooks/session-end/__tests__/index.test.js +77 -0
  314. package/dist/hooks/session-end/__tests__/index.test.js.map +1 -0
  315. package/dist/hooks/session-end/callbacks.d.ts +1 -1
  316. package/dist/hooks/session-end/index.d.ts +2 -21
  317. package/dist/hooks/session-end/index.d.ts.map +1 -1
  318. package/dist/hooks/session-end/index.js.map +1 -1
  319. package/dist/hooks/session-end/types.d.ts +26 -0
  320. package/dist/hooks/session-end/types.d.ts.map +1 -0
  321. package/dist/hooks/session-end/types.js +6 -0
  322. package/dist/hooks/session-end/types.js.map +1 -0
  323. package/dist/hooks/setup/__tests__/index.test.d.ts +2 -0
  324. package/dist/hooks/setup/__tests__/index.test.d.ts.map +1 -0
  325. package/dist/hooks/setup/__tests__/index.test.js +68 -0
  326. package/dist/hooks/setup/__tests__/index.test.js.map +1 -0
  327. package/dist/hooks/team-pipeline/__tests__/state.test.d.ts +2 -0
  328. package/dist/hooks/team-pipeline/__tests__/state.test.d.ts.map +1 -0
  329. package/dist/hooks/team-pipeline/__tests__/state.test.js +94 -0
  330. package/dist/hooks/team-pipeline/__tests__/state.test.js.map +1 -0
  331. package/dist/hud/elements/autopilot.d.ts +1 -1
  332. package/dist/hud/elements/autopilot.d.ts.map +1 -1
  333. package/dist/hud/state.d.ts.map +1 -1
  334. package/dist/hud/state.js +69 -1
  335. package/dist/hud/state.js.map +1 -1
  336. package/dist/hud/types.d.ts +2 -15
  337. package/dist/hud/types.d.ts.map +1 -1
  338. package/dist/hud/types.js.map +1 -1
  339. package/dist/lib/__tests__/validateMode.test.d.ts +2 -0
  340. package/dist/lib/__tests__/validateMode.test.d.ts.map +1 -0
  341. package/dist/lib/__tests__/validateMode.test.js +61 -0
  342. package/dist/lib/__tests__/validateMode.test.js.map +1 -0
  343. package/dist/lib/path-validator.d.ts +25 -0
  344. package/dist/lib/path-validator.d.ts.map +1 -0
  345. package/dist/lib/path-validator.js +81 -0
  346. package/dist/lib/path-validator.js.map +1 -0
  347. package/dist/lib/validateMode.d.ts +3 -0
  348. package/dist/lib/validateMode.d.ts.map +1 -1
  349. package/dist/lib/validateMode.js +28 -2
  350. package/dist/lib/validateMode.js.map +1 -1
  351. package/dist/mcp/__tests__/codex-core.test.d.ts +2 -0
  352. package/dist/mcp/__tests__/codex-core.test.d.ts.map +1 -0
  353. package/dist/mcp/__tests__/codex-core.test.js +143 -0
  354. package/dist/mcp/__tests__/codex-core.test.js.map +1 -0
  355. package/dist/mcp/__tests__/gemini-core.test.d.ts +2 -0
  356. package/dist/mcp/__tests__/gemini-core.test.d.ts.map +1 -0
  357. package/dist/mcp/__tests__/gemini-core.test.js +53 -0
  358. package/dist/mcp/__tests__/gemini-core.test.js.map +1 -0
  359. package/dist/mcp/__tests__/job-state-db-deprecation.test.js +48 -1
  360. package/dist/mcp/__tests__/job-state-db-deprecation.test.js.map +1 -1
  361. package/dist/mcp/__tests__/omc-tools-server.test.d.ts +2 -0
  362. package/dist/mcp/__tests__/omc-tools-server.test.d.ts.map +1 -0
  363. package/dist/mcp/__tests__/omc-tools-server.test.js +108 -0
  364. package/dist/mcp/__tests__/omc-tools-server.test.js.map +1 -0
  365. package/dist/mcp/job-state-db.d.ts +1 -1
  366. package/dist/mcp/job-state-db.d.ts.map +1 -1
  367. package/dist/mcp/prompt-persistence.d.ts +2 -17
  368. package/dist/mcp/prompt-persistence.d.ts.map +1 -1
  369. package/dist/mcp/prompt-persistence.js.map +1 -1
  370. package/dist/team/__tests__/deadlock-detector.test.d.ts +2 -0
  371. package/dist/team/__tests__/deadlock-detector.test.d.ts.map +1 -0
  372. package/dist/team/__tests__/deadlock-detector.test.js +50 -0
  373. package/dist/team/__tests__/deadlock-detector.test.js.map +1 -0
  374. package/dist/team/__tests__/dependency-graph.test.d.ts +2 -0
  375. package/dist/team/__tests__/dependency-graph.test.d.ts.map +1 -0
  376. package/dist/team/__tests__/dependency-graph.test.js +29 -0
  377. package/dist/team/__tests__/dependency-graph.test.js.map +1 -0
  378. package/dist/team/capabilities.d.ts +1 -2
  379. package/dist/team/capabilities.d.ts.map +1 -1
  380. package/dist/team/capabilities.js.map +1 -1
  381. package/dist/team/deadlock-detector.d.ts +16 -0
  382. package/dist/team/deadlock-detector.d.ts.map +1 -0
  383. package/dist/team/deadlock-detector.js +52 -0
  384. package/dist/team/deadlock-detector.js.map +1 -0
  385. package/dist/team/dependency-graph.d.ts +23 -0
  386. package/dist/team/dependency-graph.d.ts.map +1 -0
  387. package/dist/team/dependency-graph.js +35 -0
  388. package/dist/team/dependency-graph.js.map +1 -0
  389. package/dist/team/index.d.ts +3 -0
  390. package/dist/team/index.d.ts.map +1 -1
  391. package/dist/team/index.js +2 -0
  392. package/dist/team/index.js.map +1 -1
  393. package/dist/team/types.d.ts +15 -4
  394. package/dist/team/types.d.ts.map +1 -1
  395. package/dist/team/types.js +0 -1
  396. package/dist/team/types.js.map +1 -1
  397. package/dist/team/unified-team.d.ts +2 -11
  398. package/dist/team/unified-team.d.ts.map +1 -1
  399. package/dist/team/unified-team.js.map +1 -1
  400. package/dist/tools/__tests__/ast-tools.test.d.ts +2 -0
  401. package/dist/tools/__tests__/ast-tools.test.d.ts.map +1 -0
  402. package/dist/tools/__tests__/ast-tools.test.js +178 -0
  403. package/dist/tools/__tests__/ast-tools.test.js.map +1 -0
  404. package/dist/tools/__tests__/lsp-tools.test.d.ts +2 -0
  405. package/dist/tools/__tests__/lsp-tools.test.d.ts.map +1 -0
  406. package/dist/tools/__tests__/lsp-tools.test.js +252 -0
  407. package/dist/tools/__tests__/lsp-tools.test.js.map +1 -0
  408. package/dist/tools/diagnostics/__tests__/index.test.d.ts +2 -0
  409. package/dist/tools/diagnostics/__tests__/index.test.d.ts.map +1 -0
  410. package/dist/tools/diagnostics/__tests__/index.test.js +111 -0
  411. package/dist/tools/diagnostics/__tests__/index.test.js.map +1 -0
  412. package/dist/tools/diagnostics/__tests__/lsp-aggregator.test.d.ts +2 -0
  413. package/dist/tools/diagnostics/__tests__/lsp-aggregator.test.d.ts.map +1 -0
  414. package/dist/tools/diagnostics/__tests__/lsp-aggregator.test.js +120 -0
  415. package/dist/tools/diagnostics/__tests__/lsp-aggregator.test.js.map +1 -0
  416. package/dist/tools/diagnostics/__tests__/tsc-runner.test.d.ts +2 -0
  417. package/dist/tools/diagnostics/__tests__/tsc-runner.test.d.ts.map +1 -0
  418. package/dist/tools/diagnostics/__tests__/tsc-runner.test.js +86 -0
  419. package/dist/tools/diagnostics/__tests__/tsc-runner.test.js.map +1 -0
  420. package/dist/tools/diagnostics/constants.d.ts +5 -0
  421. package/dist/tools/diagnostics/constants.d.ts.map +1 -0
  422. package/dist/tools/diagnostics/constants.js +5 -0
  423. package/dist/tools/diagnostics/constants.js.map +1 -0
  424. package/dist/tools/diagnostics/index.d.ts +2 -1
  425. package/dist/tools/diagnostics/index.d.ts.map +1 -1
  426. package/dist/tools/diagnostics/index.js +2 -1
  427. package/dist/tools/diagnostics/index.js.map +1 -1
  428. package/dist/tools/diagnostics/lsp-aggregator.js +1 -1
  429. package/dist/tools/diagnostics/lsp-aggregator.js.map +1 -1
  430. package/dist/tools/lsp/__tests__/utils.test.d.ts +2 -0
  431. package/dist/tools/lsp/__tests__/utils.test.d.ts.map +1 -0
  432. package/dist/tools/lsp/__tests__/utils.test.js +338 -0
  433. package/dist/tools/lsp/__tests__/utils.test.js.map +1 -0
  434. package/dist/tools/lsp/utils.d.ts.map +1 -1
  435. package/dist/tools/lsp/utils.js +2 -2
  436. package/dist/tools/lsp/utils.js.map +1 -1
  437. package/dist/tools/python-repl/__tests__/bridge-manager.test.d.ts +2 -0
  438. package/dist/tools/python-repl/__tests__/bridge-manager.test.d.ts.map +1 -0
  439. package/dist/tools/python-repl/__tests__/bridge-manager.test.js +338 -0
  440. package/dist/tools/python-repl/__tests__/bridge-manager.test.js.map +1 -0
  441. package/dist/tools/python-repl/__tests__/socket-client.test.d.ts +2 -0
  442. package/dist/tools/python-repl/__tests__/socket-client.test.d.ts.map +1 -0
  443. package/dist/tools/python-repl/__tests__/socket-client.test.js +155 -0
  444. package/dist/tools/python-repl/__tests__/socket-client.test.js.map +1 -0
  445. package/dist/tools/python-repl/bridge-manager.d.ts +4 -0
  446. package/dist/tools/python-repl/bridge-manager.d.ts.map +1 -1
  447. package/dist/tools/python-repl/bridge-manager.js +4 -1
  448. package/dist/tools/python-repl/bridge-manager.js.map +1 -1
  449. package/docs/guides/task-templates-guide.md +153 -0
  450. package/docs/guides/troubleshooting-guide.md +110 -0
  451. package/docs/guides/wizard-user-guide.md +85 -0
  452. package/docs/guides/workflow-recommendation-guide.md +97 -0
  453. package/docs/reviews/ultrapower-security/review_critic.md +450 -0
  454. package/docs/reviews/ultrapower-tech-review/review_tech.md +180 -0
  455. package/docs/troubleshooting/agent-timeouts.md +37 -0
  456. package/docs/troubleshooting/common-errors.md +37 -0
  457. package/docs/troubleshooting/hook-failures.md +29 -0
  458. package/docs/troubleshooting/performance-issues.md +41 -0
  459. package/docs/troubleshooting/state-corruption.md +36 -0
  460. package/hooks/run-hook.cmd +0 -0
  461. package/hooks/session-start +0 -0
  462. package/package.json +1 -1
  463. package/scripts/analyze-dependencies.ts +47 -0
  464. package/scripts/analyze-hook-coverage.ts +55 -0
  465. package/scripts/performance-regression.ts +28 -0
  466. package/scripts/persistent-mode.cjs +605 -605
  467. package/scripts/profiling.ts +95 -0
  468. package/scripts/run-profiling.ts +64 -0
  469. package/scripts/test-parallel-execution.ts +72 -0
  470. package/scripts/test-race-detection.ts +57 -0
  471. package/scripts/test-tiered-writer.ts +60 -0
  472. package/scripts/test-wal-integration.ts +29 -0
  473. package/scripts/test-wal.ts +48 -0
  474. package/skills/next-step-router/SKILL.md +17 -0
  475. package/skills/systematic-debugging/find-polluter.sh +0 -0
  476. package/skills/wizard/SKILL.md +103 -72
  477. package/skills/writing-skills/graphviz-conventions.dot +171 -171
  478. package/skills/writing-skills/render-graphs.js +0 -0
  479. package/templates/axiom/scripts/Check-Memory.ps1 +68 -68
  480. package/templates/axiom/scripts/Poll-Memory.ps1 +36 -36
  481. package/templates/axiom/scripts/Watch-Memory.ps1 +149 -149
  482. package/templates/axiom/scripts/agent-runner.ps1 +101 -101
  483. package/templates/axiom/scripts/start-reviews.ps1 +19 -19
  484. package/templates/tasks/README.md +45 -0
  485. package/templates/tasks/bug-fix.md +37 -0
  486. package/templates/tasks/code-review.md +36 -0
  487. package/templates/tasks/feature-development.md +43 -0
  488. package/templates/tasks/refactoring.md +37 -0
  489. package/templates/tasks/security-audit.md +37 -0
@@ -1,605 +1,605 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * OMC Persistent Mode Hook (Node.js)
5
- * Minimal continuation enforcer for all OMC modes.
6
- * Stripped down for reliability — no optional imports, no PRD, no notepad pruning.
7
- *
8
- * Supported modes: ralph, autopilot, ultrapilot, swarm, ultrawork, ultraqa, pipeline
9
- */
10
-
11
- const {
12
- existsSync,
13
- readFileSync,
14
- writeFileSync,
15
- readdirSync,
16
- mkdirSync,
17
- } = require("fs");
18
- const { join, dirname, resolve, normalize } = require("path");
19
- const { homedir } = require("os");
20
-
21
- async function readStdin(timeoutMs = 5000) {
22
- return new Promise((resolve) => {
23
- const chunks = [];
24
- let settled = false;
25
- const timeout = setTimeout(() => {
26
- if (!settled) { settled = true; process.stdin.removeAllListeners(); process.stdin.destroy(); resolve(Buffer.concat(chunks).toString("utf-8")); }
27
- }, timeoutMs);
28
- process.stdin.on("data", (chunk) => { chunks.push(chunk); });
29
- process.stdin.on("end", () => { if (!settled) { settled = true; clearTimeout(timeout); resolve(Buffer.concat(chunks).toString("utf-8")); } });
30
- process.stdin.on("error", () => { if (!settled) { settled = true; clearTimeout(timeout); resolve(""); } });
31
- if (process.stdin.readableEnded) { if (!settled) { settled = true; clearTimeout(timeout); resolve(Buffer.concat(chunks).toString("utf-8")); } }
32
- });
33
- }
34
-
35
- function readJsonFile(path) {
36
- try {
37
- if (!existsSync(path)) return null;
38
- return JSON.parse(readFileSync(path, "utf-8"));
39
- } catch {
40
- return null;
41
- }
42
- }
43
-
44
- function writeJsonFile(path, data) {
45
- try {
46
- // Ensure directory exists
47
- const dir = dirname(path);
48
- if (dir && dir !== "." && !existsSync(dir)) {
49
- mkdirSync(dir, { recursive: true });
50
- }
51
- writeFileSync(path, JSON.stringify(data, null, 2));
52
- return true;
53
- } catch {
54
- return false;
55
- }
56
- }
57
-
58
- /**
59
- * Send stop notification (fire-and-forget, non-blocking).
60
- * Only notifies on first stop to avoid spam.
61
- */
62
- async function sendStopNotification(modeName, stateData, sessionId, directory) {
63
- // Only notify once per mode activation
64
- if (stateData._stopNotified) return;
65
-
66
- try {
67
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
68
- if (!pluginRoot) return;
69
-
70
- const { pathToFileURL } = require('url');
71
- const { notify } = await import(pathToFileURL(join(pluginRoot, 'dist', 'notifications', 'index.js')).href);
72
-
73
- await notify('session-stop', {
74
- sessionId: sessionId,
75
- projectPath: directory,
76
- activeMode: modeName,
77
- iteration: stateData.iteration || stateData.reinforcement_count || 1,
78
- maxIterations: stateData.max_iterations || stateData.max_reinforcements || 100,
79
- incompleteTasks: undefined, // Caller can override
80
- }).catch(() => {});
81
-
82
- // Mark as notified to prevent duplicate notifications
83
- stateData._stopNotified = true;
84
- } catch {
85
- // Notification module not available, skip silently
86
- }
87
- }
88
-
89
- /**
90
- * Staleness threshold for mode states (2 hours in milliseconds).
91
- * States older than this are treated as inactive to prevent stale state
92
- * from causing the stop hook to malfunction in new sessions.
93
- */
94
- const STALE_STATE_THRESHOLD_MS = 2 * 60 * 60 * 1000; // 2 hours
95
-
96
- /**
97
- * Check if a state is stale based on its timestamps.
98
- * A state is considered stale if it hasn't been updated recently.
99
- * We check both `last_checked_at` and `started_at` - using whichever is more recent.
100
- */
101
- function isStaleState(state) {
102
- if (!state) return true;
103
-
104
- const lastChecked = state.last_checked_at
105
- ? new Date(state.last_checked_at).getTime()
106
- : 0;
107
- const startedAt = state.started_at ? new Date(state.started_at).getTime() : 0;
108
- const mostRecent = Math.max(lastChecked, startedAt);
109
-
110
- if (mostRecent === 0) return true; // No valid timestamps
111
-
112
- const age = Date.now() - mostRecent;
113
- return age > STALE_STATE_THRESHOLD_MS;
114
- }
115
-
116
- /**
117
- * Normalize a path for comparison.
118
- */
119
- function normalizePath(p) {
120
- if (!p) return "";
121
- let normalized = resolve(p);
122
- normalized = normalize(normalized);
123
- normalized = normalized.replace(/[\/\\]+$/, "");
124
- if (process.platform === "win32") {
125
- normalized = normalized.toLowerCase();
126
- }
127
- return normalized;
128
- }
129
-
130
- /**
131
- * Check if a state belongs to the requesting session.
132
- * When sessionId is known: require exact match with state.session_id.
133
- * When sessionId is empty/unknown: only match state without session_id (legacy compat).
134
- */
135
- function isSessionMatch(state, sessionId) {
136
- if (!state) return false;
137
- if (sessionId) {
138
- // Session is known: require exact match
139
- return state.session_id === sessionId;
140
- }
141
- // No session_id from hook: only match legacy state (no session_id in state)
142
- return !state.session_id;
143
- }
144
-
145
- /**
146
- * Check if a state belongs to the current project.
147
- */
148
- function isStateForCurrentProject(
149
- state,
150
- currentDirectory,
151
- isGlobalState = false,
152
- ) {
153
- if (!state) return true;
154
-
155
- if (!state.project_path) {
156
- if (isGlobalState) {
157
- return false;
158
- }
159
- return true;
160
- }
161
-
162
- return normalizePath(state.project_path) === normalizePath(currentDirectory);
163
- }
164
-
165
- /**
166
- * Read state file from local location only.
167
- */
168
- function readStateFile(stateDir, filename) {
169
- const localPath = join(stateDir, filename);
170
- const state = readJsonFile(localPath);
171
- return { state, path: localPath, isGlobal: false };
172
- }
173
-
174
- /**
175
- * Read state file with session-scoped path support and fallback to legacy path.
176
- */
177
- function readStateFileWithSession(stateDir, filename, sessionId) {
178
- // Try session-scoped path first (and ONLY) when sessionId is available
179
- if (sessionId && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) {
180
- const sessionsDir = join(stateDir, 'sessions', sessionId);
181
- const sessionPath = join(sessionsDir, filename);
182
- const state = readJsonFile(sessionPath);
183
- if (state) {
184
- return { state, path: sessionPath, isGlobal: false };
185
- }
186
- // Session path not found — do NOT fall back to legacy
187
- return { state: null, path: null, isGlobal: false };
188
- }
189
- // No sessionId: fall back to legacy path (backward compat)
190
- return readStateFile(stateDir, filename);
191
- }
192
-
193
- /**
194
- * Count incomplete Tasks from Claude Code's native Task system.
195
- */
196
- function countIncompleteTasks(sessionId) {
197
- if (!sessionId || typeof sessionId !== "string") return 0;
198
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) return 0;
199
-
200
- const cfgDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
201
- const taskDir = join(cfgDir, "tasks", sessionId);
202
- if (!existsSync(taskDir)) return 0;
203
-
204
- let count = 0;
205
- try {
206
- const files = readdirSync(taskDir).filter(
207
- (f) => f.endsWith(".json") && f !== ".lock",
208
- );
209
- for (const file of files) {
210
- try {
211
- const content = readFileSync(join(taskDir, file), "utf-8");
212
- const task = JSON.parse(content);
213
- if (task.status === "pending" || task.status === "in_progress") count++;
214
- } catch {
215
- /* skip */
216
- }
217
- }
218
- } catch {
219
- /* skip */
220
- }
221
- return count;
222
- }
223
-
224
- function countIncompleteTodos(sessionId, projectDir) {
225
- let count = 0;
226
-
227
- // Session-specific todos only (no global scan)
228
- if (
229
- sessionId &&
230
- typeof sessionId === "string" &&
231
- /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)
232
- ) {
233
- const sessionTodoPath = join(
234
- homedir(),
235
- ".claude",
236
- "todos",
237
- `${sessionId}.json`,
238
- );
239
- try {
240
- const data = readJsonFile(sessionTodoPath);
241
- const todos = Array.isArray(data)
242
- ? data
243
- : Array.isArray(data?.todos)
244
- ? data.todos
245
- : [];
246
- count += todos.filter(
247
- (t) => t.status !== "completed" && t.status !== "cancelled",
248
- ).length;
249
- } catch {
250
- /* skip */
251
- }
252
- }
253
-
254
- // Project-local todos only
255
- for (const path of [
256
- join(projectDir, ".omc", "todos.json"),
257
- join(projectDir, ".claude", "todos.json"),
258
- ]) {
259
- try {
260
- const data = readJsonFile(path);
261
- const todos = Array.isArray(data)
262
- ? data
263
- : Array.isArray(data?.todos)
264
- ? data.todos
265
- : [];
266
- count += todos.filter(
267
- (t) => t.status !== "completed" && t.status !== "cancelled",
268
- ).length;
269
- } catch {
270
- /* skip */
271
- }
272
- }
273
-
274
- return count;
275
- }
276
-
277
- /**
278
- * Detect if stop was triggered by context-limit related reasons.
279
- * When context is exhausted, Claude Code needs to stop so it can compact.
280
- * Blocking these stops causes a deadlock: can't compact because can't stop,
281
- * can't continue because context is full.
282
- *
283
- * See: https://github.com/liangjie559567/ultrapower/issues/213
284
- */
285
- function isContextLimitStop(data) {
286
- const reason = (data.stop_reason || data.stopReason || "").toLowerCase();
287
-
288
- const contextPatterns = [
289
- "context_limit",
290
- "context_window",
291
- "context_exceeded",
292
- "context_full",
293
- "max_context",
294
- "token_limit",
295
- "max_tokens",
296
- "conversation_too_long",
297
- "input_too_long",
298
- ];
299
-
300
- if (contextPatterns.some((p) => reason.includes(p))) {
301
- return true;
302
- }
303
-
304
- const endTurnReason = (
305
- data.end_turn_reason ||
306
- data.endTurnReason ||
307
- ""
308
- ).toLowerCase();
309
- if (endTurnReason && contextPatterns.some((p) => endTurnReason.includes(p))) {
310
- return true;
311
- }
312
-
313
- return false;
314
- }
315
-
316
- /**
317
- * Detect if stop was triggered by user abort (Ctrl+C, cancel button, etc.)
318
- */
319
- function isUserAbort(data) {
320
- if (data.user_requested || data.userRequested) return true;
321
-
322
- const reason = (data.stop_reason || data.stopReason || "").toLowerCase();
323
- // Exact-match patterns: short generic words that cause false positives with .includes()
324
- const exactPatterns = ["aborted", "abort", "cancel", "interrupt"];
325
- // Substring patterns: compound words safe for .includes() matching
326
- const substringPatterns = [
327
- "user_cancel",
328
- "user_interrupt",
329
- "ctrl_c",
330
- "manual_stop",
331
- ];
332
-
333
- return (
334
- exactPatterns.some((p) => reason === p) ||
335
- substringPatterns.some((p) => reason.includes(p))
336
- );
337
- }
338
-
339
- async function main() {
340
- try {
341
- const input = await readStdin();
342
- let data = {};
343
- try {
344
- data = JSON.parse(input);
345
- } catch {}
346
-
347
- const directory = data.cwd || data.directory || process.cwd();
348
- const sessionId = data.session_id || data.sessionId || "";
349
- const stateDir = join(directory, ".omc", "state");
350
-
351
- // CRITICAL: Never block context-limit stops.
352
- // Blocking these causes a deadlock where Claude Code cannot compact.
353
- // See: https://github.com/liangjie559567/ultrapower/issues/213
354
- if (isContextLimitStop(data)) {
355
- console.log(JSON.stringify({ continue: true, suppressOutput: true }));
356
- return;
357
- }
358
-
359
- // Respect user abort (Ctrl+C, cancel)
360
- if (isUserAbort(data)) {
361
- console.log(JSON.stringify({ continue: true, suppressOutput: true }));
362
- return;
363
- }
364
-
365
- // Read all mode states (session-scoped with legacy fallback)
366
- const ralph = readStateFileWithSession(stateDir, "ralph-state.json", sessionId);
367
- const autopilot = readStateFileWithSession(stateDir, "autopilot-state.json", sessionId);
368
- const ultrapilot = readStateFileWithSession(stateDir, "ultrapilot-state.json", sessionId);
369
- const ultrawork = readStateFileWithSession(stateDir, "ultrawork-state.json", sessionId);
370
- const ultraqa = readStateFileWithSession(stateDir, "ultraqa-state.json", sessionId);
371
- const pipeline = readStateFileWithSession(stateDir, "pipeline-state.json", sessionId);
372
-
373
- // Swarm uses swarm-summary.json (not swarm-state.json) + marker file
374
- const swarmMarker = existsSync(join(stateDir, "swarm-active.marker"));
375
- const swarmSummary = readJsonFile(join(stateDir, "swarm-summary.json"));
376
-
377
- // Count incomplete items (session-specific + project-local only)
378
- const taskCount = countIncompleteTasks(sessionId);
379
- const todoCount = countIncompleteTodos(sessionId, directory);
380
- const totalIncomplete = taskCount + todoCount;
381
-
382
- // Priority 1: Ralph Loop (explicit persistence mode)
383
- // Skip if state is stale (older than 2 hours) - prevents blocking new sessions
384
- if (ralph.state?.active && !isStaleState(ralph.state) && isSessionMatch(ralph.state, sessionId)) {
385
- const iteration = ralph.state.iteration || 1;
386
- const maxIter = ralph.state.max_iterations || 100;
387
-
388
- if (iteration < maxIter) {
389
- ralph.state.iteration = iteration + 1;
390
- ralph.state.last_checked_at = new Date().toISOString();
391
- writeJsonFile(ralph.path, ralph.state);
392
-
393
- // Fire-and-forget notification
394
- sendStopNotification('ralph', ralph.state, sessionId, directory).catch(() => {});
395
-
396
- console.log(
397
- JSON.stringify({
398
- decision: "block",
399
- reason: `[RALPH LOOP - ITERATION ${iteration + 1}/${maxIter}] Work is NOT done. Continue working.\nWhen FULLY complete (after Architect verification), run /ultrapower:cancel to cleanly exit ralph mode and clean up all state files. If cancel fails, retry with /ultrapower:cancel --force.\n${ralph.state.prompt ? `Task: ${ralph.state.prompt}` : ""}`,
400
- }),
401
- );
402
- return;
403
- }
404
- }
405
-
406
- // Priority 2: Autopilot (high-level orchestration)
407
- if (autopilot.state?.active && !isStaleState(autopilot.state) && isSessionMatch(autopilot.state, sessionId)) {
408
- const phase = autopilot.state.phase || "unknown";
409
- if (phase !== "complete") {
410
- const newCount = (autopilot.state.reinforcement_count || 0) + 1;
411
- if (newCount <= 20) {
412
- autopilot.state.reinforcement_count = newCount;
413
- autopilot.state.last_checked_at = new Date().toISOString();
414
- writeJsonFile(autopilot.path, autopilot.state);
415
-
416
- // Fire-and-forget notification
417
- sendStopNotification('autopilot', autopilot.state, sessionId, directory).catch(() => {});
418
-
419
- console.log(
420
- JSON.stringify({
421
- decision: "block",
422
- reason: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working. When all phases are complete, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
423
- }),
424
- );
425
- return;
426
- }
427
- }
428
- }
429
-
430
- // Priority 3: Ultrapilot (parallel autopilot)
431
- if (ultrapilot.state?.active && !isStaleState(ultrapilot.state) && isSessionMatch(ultrapilot.state, sessionId)) {
432
- const workers = ultrapilot.state.workers || [];
433
- const incomplete = workers.filter(
434
- (w) => w.status !== "complete" && w.status !== "failed",
435
- ).length;
436
- if (incomplete > 0) {
437
- const newCount = (ultrapilot.state.reinforcement_count || 0) + 1;
438
- if (newCount <= 20) {
439
- ultrapilot.state.reinforcement_count = newCount;
440
- ultrapilot.state.last_checked_at = new Date().toISOString();
441
- writeJsonFile(ultrapilot.path, ultrapilot.state);
442
-
443
- // Fire-and-forget notification
444
- sendStopNotification('ultrapilot', ultrapilot.state, sessionId, directory).catch(() => {});
445
-
446
- console.log(
447
- JSON.stringify({
448
- decision: "block",
449
- reason: `[ULTRAPILOT] ${incomplete} workers still running. Continue working. When all workers complete, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
450
- }),
451
- );
452
- return;
453
- }
454
- }
455
- }
456
-
457
- // Priority 4: Swarm (coordinated agents with SQLite)
458
- if (swarmMarker && swarmSummary?.active && !isStaleState(swarmSummary)) {
459
- const pending =
460
- (swarmSummary.tasks_pending || 0) + (swarmSummary.tasks_claimed || 0);
461
- if (pending > 0) {
462
- const newCount = (swarmSummary.reinforcement_count || 0) + 1;
463
- if (newCount <= 15) {
464
- swarmSummary.reinforcement_count = newCount;
465
- swarmSummary.last_checked_at = new Date().toISOString();
466
- writeJsonFile(join(stateDir, "swarm-summary.json"), swarmSummary);
467
-
468
- // Fire-and-forget notification
469
- sendStopNotification('swarm', swarmSummary, sessionId, directory).catch(() => {});
470
-
471
- console.log(
472
- JSON.stringify({
473
- decision: "block",
474
- reason: `[SWARM ACTIVE] ${pending} tasks remain. Continue working. When all tasks are done, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
475
- }),
476
- );
477
- return;
478
- }
479
- }
480
- }
481
-
482
- // Priority 5: Pipeline (sequential stages)
483
- if (pipeline.state?.active && !isStaleState(pipeline.state) && isSessionMatch(pipeline.state, sessionId)) {
484
- const currentStage = pipeline.state.current_stage || 0;
485
- const totalStages = pipeline.state.stages?.length || 0;
486
- if (currentStage < totalStages) {
487
- const newCount = (pipeline.state.reinforcement_count || 0) + 1;
488
- if (newCount <= 15) {
489
- pipeline.state.reinforcement_count = newCount;
490
- pipeline.state.last_checked_at = new Date().toISOString();
491
- writeJsonFile(pipeline.path, pipeline.state);
492
-
493
- // Fire-and-forget notification
494
- sendStopNotification('pipeline', pipeline.state, sessionId, directory).catch(() => {});
495
-
496
- console.log(
497
- JSON.stringify({
498
- decision: "block",
499
- reason: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue working. When all stages complete, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
500
- }),
501
- );
502
- return;
503
- }
504
- }
505
- }
506
-
507
- // Priority 6: UltraQA (QA cycling)
508
- if (ultraqa.state?.active && !isStaleState(ultraqa.state) && isSessionMatch(ultraqa.state, sessionId)) {
509
- const cycle = ultraqa.state.cycle || 1;
510
- const maxCycles = ultraqa.state.max_cycles || 10;
511
- if (cycle < maxCycles && !ultraqa.state.all_passing) {
512
- ultraqa.state.cycle = cycle + 1;
513
- ultraqa.state.last_checked_at = new Date().toISOString();
514
- writeJsonFile(ultraqa.path, ultraqa.state);
515
-
516
- // Fire-and-forget notification
517
- sendStopNotification('ultraqa', ultraqa.state, sessionId, directory).catch(() => {});
518
-
519
- console.log(
520
- JSON.stringify({
521
- decision: "block",
522
- reason: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing. When all tests pass, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
523
- }),
524
- );
525
- return;
526
- }
527
- }
528
-
529
- // Priority 7: Ultrawork - ALWAYS continue while active (not just when tasks exist)
530
- // This prevents false stops from bash errors, transient failures, etc.
531
- // Session isolation: only block if state belongs to this session (issue #311)
532
- // Project isolation: only block if state belongs to this project
533
- if (
534
- ultrawork.state?.active &&
535
- !isStaleState(ultrawork.state) &&
536
- isSessionMatch(ultrawork.state, sessionId) &&
537
- isStateForCurrentProject(ultrawork.state, directory, ultrawork.isGlobal)
538
- ) {
539
- const newCount = (ultrawork.state.reinforcement_count || 0) + 1;
540
- const maxReinforcements = ultrawork.state.max_reinforcements || 50;
541
-
542
- if (newCount > maxReinforcements) {
543
- // Max reinforcements reached - allow stop
544
- console.log(JSON.stringify({ continue: true, suppressOutput: true }));
545
- return;
546
- }
547
-
548
- ultrawork.state.reinforcement_count = newCount;
549
- ultrawork.state.last_checked_at = new Date().toISOString();
550
- writeJsonFile(ultrawork.path, ultrawork.state);
551
-
552
- // Fire-and-forget notification
553
- sendStopNotification('ultrawork', ultrawork.state, sessionId, directory).catch(() => {});
554
-
555
- let reason = `[ULTRAWORK #${newCount}/${maxReinforcements}] Mode active.`;
556
-
557
- if (totalIncomplete > 0) {
558
- const itemType = taskCount > 0 ? "Tasks" : "todos";
559
- reason += ` ${totalIncomplete} incomplete ${itemType} remain. Continue working.`;
560
- } else if (newCount >= 3) {
561
- // Only suggest cancel after minimum iterations (guard against no-tasks-created scenario)
562
- reason += ` If all work is complete, run /ultrapower:cancel to cleanly exit ultrawork mode and clean up state files. If cancel fails, retry with /ultrapower:cancel --force. Otherwise, continue working.`;
563
- } else {
564
- // Early iterations with no tasks yet - just tell LLM to continue
565
- reason += ` Continue working - create Tasks to track your progress.`;
566
- }
567
-
568
- if (ultrawork.state.original_prompt) {
569
- reason += `\nTask: ${ultrawork.state.original_prompt}`;
570
- }
571
-
572
- console.log(JSON.stringify({ decision: "block", reason }));
573
- return;
574
- }
575
-
576
- // No blocking needed — Claude is truly idle.
577
- // Send session-idle notification (fire-and-forget) so external integrations
578
- // (Telegram, Discord) know the session went idle without any active mode.
579
- if (sessionId) {
580
- try {
581
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
582
- if (pluginRoot) {
583
- const { pathToFileURL } = require('url');
584
- import(pathToFileURL(join(pluginRoot, 'dist', 'notifications', 'index.js')).href)
585
- .then(({ notify }) =>
586
- notify('session-idle', {
587
- sessionId,
588
- projectPath: directory,
589
- }).catch(() => {})
590
- )
591
- .catch(() => {});
592
- }
593
- } catch {
594
- // Notification module not available, skip silently
595
- }
596
- }
597
- console.log(JSON.stringify({ continue: true, suppressOutput: true }));
598
- } catch (error) {
599
- // On any error, allow stop rather than blocking forever
600
- console.error(`[persistent-mode] Error: ${error.message}`);
601
- console.log(JSON.stringify({ continue: true, suppressOutput: true }));
602
- }
603
- }
604
-
605
- main();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * OMC Persistent Mode Hook (Node.js)
5
+ * Minimal continuation enforcer for all OMC modes.
6
+ * Stripped down for reliability — no optional imports, no PRD, no notepad pruning.
7
+ *
8
+ * Supported modes: ralph, autopilot, ultrapilot, swarm, ultrawork, ultraqa, pipeline
9
+ */
10
+
11
+ const {
12
+ existsSync,
13
+ readFileSync,
14
+ writeFileSync,
15
+ readdirSync,
16
+ mkdirSync,
17
+ } = require("fs");
18
+ const { join, dirname, resolve, normalize } = require("path");
19
+ const { homedir } = require("os");
20
+
21
+ async function readStdin(timeoutMs = 5000) {
22
+ return new Promise((resolve) => {
23
+ const chunks = [];
24
+ let settled = false;
25
+ const timeout = setTimeout(() => {
26
+ if (!settled) { settled = true; process.stdin.removeAllListeners(); process.stdin.destroy(); resolve(Buffer.concat(chunks).toString("utf-8")); }
27
+ }, timeoutMs);
28
+ process.stdin.on("data", (chunk) => { chunks.push(chunk); });
29
+ process.stdin.on("end", () => { if (!settled) { settled = true; clearTimeout(timeout); resolve(Buffer.concat(chunks).toString("utf-8")); } });
30
+ process.stdin.on("error", () => { if (!settled) { settled = true; clearTimeout(timeout); resolve(""); } });
31
+ if (process.stdin.readableEnded) { if (!settled) { settled = true; clearTimeout(timeout); resolve(Buffer.concat(chunks).toString("utf-8")); } }
32
+ });
33
+ }
34
+
35
+ function readJsonFile(path) {
36
+ try {
37
+ if (!existsSync(path)) return null;
38
+ return JSON.parse(readFileSync(path, "utf-8"));
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function writeJsonFile(path, data) {
45
+ try {
46
+ // Ensure directory exists
47
+ const dir = dirname(path);
48
+ if (dir && dir !== "." && !existsSync(dir)) {
49
+ mkdirSync(dir, { recursive: true });
50
+ }
51
+ writeFileSync(path, JSON.stringify(data, null, 2));
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Send stop notification (fire-and-forget, non-blocking).
60
+ * Only notifies on first stop to avoid spam.
61
+ */
62
+ async function sendStopNotification(modeName, stateData, sessionId, directory) {
63
+ // Only notify once per mode activation
64
+ if (stateData._stopNotified) return;
65
+
66
+ try {
67
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
68
+ if (!pluginRoot) return;
69
+
70
+ const { pathToFileURL } = require('url');
71
+ const { notify } = await import(pathToFileURL(join(pluginRoot, 'dist', 'notifications', 'index.js')).href);
72
+
73
+ await notify('session-stop', {
74
+ sessionId: sessionId,
75
+ projectPath: directory,
76
+ activeMode: modeName,
77
+ iteration: stateData.iteration || stateData.reinforcement_count || 1,
78
+ maxIterations: stateData.max_iterations || stateData.max_reinforcements || 100,
79
+ incompleteTasks: undefined, // Caller can override
80
+ }).catch(() => {});
81
+
82
+ // Mark as notified to prevent duplicate notifications
83
+ stateData._stopNotified = true;
84
+ } catch {
85
+ // Notification module not available, skip silently
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Staleness threshold for mode states (2 hours in milliseconds).
91
+ * States older than this are treated as inactive to prevent stale state
92
+ * from causing the stop hook to malfunction in new sessions.
93
+ */
94
+ const STALE_STATE_THRESHOLD_MS = 2 * 60 * 60 * 1000; // 2 hours
95
+
96
+ /**
97
+ * Check if a state is stale based on its timestamps.
98
+ * A state is considered stale if it hasn't been updated recently.
99
+ * We check both `last_checked_at` and `started_at` - using whichever is more recent.
100
+ */
101
+ function isStaleState(state) {
102
+ if (!state) return true;
103
+
104
+ const lastChecked = state.last_checked_at
105
+ ? new Date(state.last_checked_at).getTime()
106
+ : 0;
107
+ const startedAt = state.started_at ? new Date(state.started_at).getTime() : 0;
108
+ const mostRecent = Math.max(lastChecked, startedAt);
109
+
110
+ if (mostRecent === 0) return true; // No valid timestamps
111
+
112
+ const age = Date.now() - mostRecent;
113
+ return age > STALE_STATE_THRESHOLD_MS;
114
+ }
115
+
116
+ /**
117
+ * Normalize a path for comparison.
118
+ */
119
+ function normalizePath(p) {
120
+ if (!p) return "";
121
+ let normalized = resolve(p);
122
+ normalized = normalize(normalized);
123
+ normalized = normalized.replace(/[\/\\]+$/, "");
124
+ if (process.platform === "win32") {
125
+ normalized = normalized.toLowerCase();
126
+ }
127
+ return normalized;
128
+ }
129
+
130
+ /**
131
+ * Check if a state belongs to the requesting session.
132
+ * When sessionId is known: require exact match with state.session_id.
133
+ * When sessionId is empty/unknown: only match state without session_id (legacy compat).
134
+ */
135
+ function isSessionMatch(state, sessionId) {
136
+ if (!state) return false;
137
+ if (sessionId) {
138
+ // Session is known: require exact match
139
+ return state.session_id === sessionId;
140
+ }
141
+ // No session_id from hook: only match legacy state (no session_id in state)
142
+ return !state.session_id;
143
+ }
144
+
145
+ /**
146
+ * Check if a state belongs to the current project.
147
+ */
148
+ function isStateForCurrentProject(
149
+ state,
150
+ currentDirectory,
151
+ isGlobalState = false,
152
+ ) {
153
+ if (!state) return true;
154
+
155
+ if (!state.project_path) {
156
+ if (isGlobalState) {
157
+ return false;
158
+ }
159
+ return true;
160
+ }
161
+
162
+ return normalizePath(state.project_path) === normalizePath(currentDirectory);
163
+ }
164
+
165
+ /**
166
+ * Read state file from local location only.
167
+ */
168
+ function readStateFile(stateDir, filename) {
169
+ const localPath = join(stateDir, filename);
170
+ const state = readJsonFile(localPath);
171
+ return { state, path: localPath, isGlobal: false };
172
+ }
173
+
174
+ /**
175
+ * Read state file with session-scoped path support and fallback to legacy path.
176
+ */
177
+ function readStateFileWithSession(stateDir, filename, sessionId) {
178
+ // Try session-scoped path first (and ONLY) when sessionId is available
179
+ if (sessionId && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) {
180
+ const sessionsDir = join(stateDir, 'sessions', sessionId);
181
+ const sessionPath = join(sessionsDir, filename);
182
+ const state = readJsonFile(sessionPath);
183
+ if (state) {
184
+ return { state, path: sessionPath, isGlobal: false };
185
+ }
186
+ // Session path not found — do NOT fall back to legacy
187
+ return { state: null, path: null, isGlobal: false };
188
+ }
189
+ // No sessionId: fall back to legacy path (backward compat)
190
+ return readStateFile(stateDir, filename);
191
+ }
192
+
193
+ /**
194
+ * Count incomplete Tasks from Claude Code's native Task system.
195
+ */
196
+ function countIncompleteTasks(sessionId) {
197
+ if (!sessionId || typeof sessionId !== "string") return 0;
198
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) return 0;
199
+
200
+ const cfgDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
201
+ const taskDir = join(cfgDir, "tasks", sessionId);
202
+ if (!existsSync(taskDir)) return 0;
203
+
204
+ let count = 0;
205
+ try {
206
+ const files = readdirSync(taskDir).filter(
207
+ (f) => f.endsWith(".json") && f !== ".lock",
208
+ );
209
+ for (const file of files) {
210
+ try {
211
+ const content = readFileSync(join(taskDir, file), "utf-8");
212
+ const task = JSON.parse(content);
213
+ if (task.status === "pending" || task.status === "in_progress") count++;
214
+ } catch {
215
+ /* skip */
216
+ }
217
+ }
218
+ } catch {
219
+ /* skip */
220
+ }
221
+ return count;
222
+ }
223
+
224
+ function countIncompleteTodos(sessionId, projectDir) {
225
+ let count = 0;
226
+
227
+ // Session-specific todos only (no global scan)
228
+ if (
229
+ sessionId &&
230
+ typeof sessionId === "string" &&
231
+ /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)
232
+ ) {
233
+ const sessionTodoPath = join(
234
+ homedir(),
235
+ ".claude",
236
+ "todos",
237
+ `${sessionId}.json`,
238
+ );
239
+ try {
240
+ const data = readJsonFile(sessionTodoPath);
241
+ const todos = Array.isArray(data)
242
+ ? data
243
+ : Array.isArray(data?.todos)
244
+ ? data.todos
245
+ : [];
246
+ count += todos.filter(
247
+ (t) => t.status !== "completed" && t.status !== "cancelled",
248
+ ).length;
249
+ } catch {
250
+ /* skip */
251
+ }
252
+ }
253
+
254
+ // Project-local todos only
255
+ for (const path of [
256
+ join(projectDir, ".omc", "todos.json"),
257
+ join(projectDir, ".claude", "todos.json"),
258
+ ]) {
259
+ try {
260
+ const data = readJsonFile(path);
261
+ const todos = Array.isArray(data)
262
+ ? data
263
+ : Array.isArray(data?.todos)
264
+ ? data.todos
265
+ : [];
266
+ count += todos.filter(
267
+ (t) => t.status !== "completed" && t.status !== "cancelled",
268
+ ).length;
269
+ } catch {
270
+ /* skip */
271
+ }
272
+ }
273
+
274
+ return count;
275
+ }
276
+
277
+ /**
278
+ * Detect if stop was triggered by context-limit related reasons.
279
+ * When context is exhausted, Claude Code needs to stop so it can compact.
280
+ * Blocking these stops causes a deadlock: can't compact because can't stop,
281
+ * can't continue because context is full.
282
+ *
283
+ * See: https://github.com/liangjie559567/ultrapower/issues/213
284
+ */
285
+ function isContextLimitStop(data) {
286
+ const reason = (data.stop_reason || data.stopReason || "").toLowerCase();
287
+
288
+ const contextPatterns = [
289
+ "context_limit",
290
+ "context_window",
291
+ "context_exceeded",
292
+ "context_full",
293
+ "max_context",
294
+ "token_limit",
295
+ "max_tokens",
296
+ "conversation_too_long",
297
+ "input_too_long",
298
+ ];
299
+
300
+ if (contextPatterns.some((p) => reason.includes(p))) {
301
+ return true;
302
+ }
303
+
304
+ const endTurnReason = (
305
+ data.end_turn_reason ||
306
+ data.endTurnReason ||
307
+ ""
308
+ ).toLowerCase();
309
+ if (endTurnReason && contextPatterns.some((p) => endTurnReason.includes(p))) {
310
+ return true;
311
+ }
312
+
313
+ return false;
314
+ }
315
+
316
+ /**
317
+ * Detect if stop was triggered by user abort (Ctrl+C, cancel button, etc.)
318
+ */
319
+ function isUserAbort(data) {
320
+ if (data.user_requested || data.userRequested) return true;
321
+
322
+ const reason = (data.stop_reason || data.stopReason || "").toLowerCase();
323
+ // Exact-match patterns: short generic words that cause false positives with .includes()
324
+ const exactPatterns = ["aborted", "abort", "cancel", "interrupt"];
325
+ // Substring patterns: compound words safe for .includes() matching
326
+ const substringPatterns = [
327
+ "user_cancel",
328
+ "user_interrupt",
329
+ "ctrl_c",
330
+ "manual_stop",
331
+ ];
332
+
333
+ return (
334
+ exactPatterns.some((p) => reason === p) ||
335
+ substringPatterns.some((p) => reason.includes(p))
336
+ );
337
+ }
338
+
339
+ async function main() {
340
+ try {
341
+ const input = await readStdin();
342
+ let data = {};
343
+ try {
344
+ data = JSON.parse(input);
345
+ } catch {}
346
+
347
+ const directory = data.cwd || data.directory || process.cwd();
348
+ const sessionId = data.session_id || data.sessionId || "";
349
+ const stateDir = join(directory, ".omc", "state");
350
+
351
+ // CRITICAL: Never block context-limit stops.
352
+ // Blocking these causes a deadlock where Claude Code cannot compact.
353
+ // See: https://github.com/liangjie559567/ultrapower/issues/213
354
+ if (isContextLimitStop(data)) {
355
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
356
+ return;
357
+ }
358
+
359
+ // Respect user abort (Ctrl+C, cancel)
360
+ if (isUserAbort(data)) {
361
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
362
+ return;
363
+ }
364
+
365
+ // Read all mode states (session-scoped with legacy fallback)
366
+ const ralph = readStateFileWithSession(stateDir, "ralph-state.json", sessionId);
367
+ const autopilot = readStateFileWithSession(stateDir, "autopilot-state.json", sessionId);
368
+ const ultrapilot = readStateFileWithSession(stateDir, "ultrapilot-state.json", sessionId);
369
+ const ultrawork = readStateFileWithSession(stateDir, "ultrawork-state.json", sessionId);
370
+ const ultraqa = readStateFileWithSession(stateDir, "ultraqa-state.json", sessionId);
371
+ const pipeline = readStateFileWithSession(stateDir, "pipeline-state.json", sessionId);
372
+
373
+ // Swarm uses swarm-summary.json (not swarm-state.json) + marker file
374
+ const swarmMarker = existsSync(join(stateDir, "swarm-active.marker"));
375
+ const swarmSummary = readJsonFile(join(stateDir, "swarm-summary.json"));
376
+
377
+ // Count incomplete items (session-specific + project-local only)
378
+ const taskCount = countIncompleteTasks(sessionId);
379
+ const todoCount = countIncompleteTodos(sessionId, directory);
380
+ const totalIncomplete = taskCount + todoCount;
381
+
382
+ // Priority 1: Ralph Loop (explicit persistence mode)
383
+ // Skip if state is stale (older than 2 hours) - prevents blocking new sessions
384
+ if (ralph.state?.active && !isStaleState(ralph.state) && isSessionMatch(ralph.state, sessionId)) {
385
+ const iteration = ralph.state.iteration || 1;
386
+ const maxIter = ralph.state.max_iterations || 100;
387
+
388
+ if (iteration < maxIter) {
389
+ ralph.state.iteration = iteration + 1;
390
+ ralph.state.last_checked_at = new Date().toISOString();
391
+ writeJsonFile(ralph.path, ralph.state);
392
+
393
+ // Fire-and-forget notification
394
+ sendStopNotification('ralph', ralph.state, sessionId, directory).catch(() => {});
395
+
396
+ console.log(
397
+ JSON.stringify({
398
+ decision: "block",
399
+ reason: `[RALPH LOOP - ITERATION ${iteration + 1}/${maxIter}] Work is NOT done. Continue working.\nWhen FULLY complete (after Architect verification), run /ultrapower:cancel to cleanly exit ralph mode and clean up all state files. If cancel fails, retry with /ultrapower:cancel --force.\n${ralph.state.prompt ? `Task: ${ralph.state.prompt}` : ""}`,
400
+ }),
401
+ );
402
+ return;
403
+ }
404
+ }
405
+
406
+ // Priority 2: Autopilot (high-level orchestration)
407
+ if (autopilot.state?.active && !isStaleState(autopilot.state) && isSessionMatch(autopilot.state, sessionId)) {
408
+ const phase = autopilot.state.phase || "unknown";
409
+ if (phase !== "complete") {
410
+ const newCount = (autopilot.state.reinforcement_count || 0) + 1;
411
+ if (newCount <= 20) {
412
+ autopilot.state.reinforcement_count = newCount;
413
+ autopilot.state.last_checked_at = new Date().toISOString();
414
+ writeJsonFile(autopilot.path, autopilot.state);
415
+
416
+ // Fire-and-forget notification
417
+ sendStopNotification('autopilot', autopilot.state, sessionId, directory).catch(() => {});
418
+
419
+ console.log(
420
+ JSON.stringify({
421
+ decision: "block",
422
+ reason: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working. When all phases are complete, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
423
+ }),
424
+ );
425
+ return;
426
+ }
427
+ }
428
+ }
429
+
430
+ // Priority 3: Ultrapilot (parallel autopilot)
431
+ if (ultrapilot.state?.active && !isStaleState(ultrapilot.state) && isSessionMatch(ultrapilot.state, sessionId)) {
432
+ const workers = ultrapilot.state.workers || [];
433
+ const incomplete = workers.filter(
434
+ (w) => w.status !== "complete" && w.status !== "failed",
435
+ ).length;
436
+ if (incomplete > 0) {
437
+ const newCount = (ultrapilot.state.reinforcement_count || 0) + 1;
438
+ if (newCount <= 20) {
439
+ ultrapilot.state.reinforcement_count = newCount;
440
+ ultrapilot.state.last_checked_at = new Date().toISOString();
441
+ writeJsonFile(ultrapilot.path, ultrapilot.state);
442
+
443
+ // Fire-and-forget notification
444
+ sendStopNotification('ultrapilot', ultrapilot.state, sessionId, directory).catch(() => {});
445
+
446
+ console.log(
447
+ JSON.stringify({
448
+ decision: "block",
449
+ reason: `[ULTRAPILOT] ${incomplete} workers still running. Continue working. When all workers complete, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
450
+ }),
451
+ );
452
+ return;
453
+ }
454
+ }
455
+ }
456
+
457
+ // Priority 4: Swarm (coordinated agents with SQLite)
458
+ if (swarmMarker && swarmSummary?.active && !isStaleState(swarmSummary)) {
459
+ const pending =
460
+ (swarmSummary.tasks_pending || 0) + (swarmSummary.tasks_claimed || 0);
461
+ if (pending > 0) {
462
+ const newCount = (swarmSummary.reinforcement_count || 0) + 1;
463
+ if (newCount <= 15) {
464
+ swarmSummary.reinforcement_count = newCount;
465
+ swarmSummary.last_checked_at = new Date().toISOString();
466
+ writeJsonFile(join(stateDir, "swarm-summary.json"), swarmSummary);
467
+
468
+ // Fire-and-forget notification
469
+ sendStopNotification('swarm', swarmSummary, sessionId, directory).catch(() => {});
470
+
471
+ console.log(
472
+ JSON.stringify({
473
+ decision: "block",
474
+ reason: `[SWARM ACTIVE] ${pending} tasks remain. Continue working. When all tasks are done, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
475
+ }),
476
+ );
477
+ return;
478
+ }
479
+ }
480
+ }
481
+
482
+ // Priority 5: Pipeline (sequential stages)
483
+ if (pipeline.state?.active && !isStaleState(pipeline.state) && isSessionMatch(pipeline.state, sessionId)) {
484
+ const currentStage = pipeline.state.current_stage || 0;
485
+ const totalStages = pipeline.state.stages?.length || 0;
486
+ if (currentStage < totalStages) {
487
+ const newCount = (pipeline.state.reinforcement_count || 0) + 1;
488
+ if (newCount <= 15) {
489
+ pipeline.state.reinforcement_count = newCount;
490
+ pipeline.state.last_checked_at = new Date().toISOString();
491
+ writeJsonFile(pipeline.path, pipeline.state);
492
+
493
+ // Fire-and-forget notification
494
+ sendStopNotification('pipeline', pipeline.state, sessionId, directory).catch(() => {});
495
+
496
+ console.log(
497
+ JSON.stringify({
498
+ decision: "block",
499
+ reason: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue working. When all stages complete, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
500
+ }),
501
+ );
502
+ return;
503
+ }
504
+ }
505
+ }
506
+
507
+ // Priority 6: UltraQA (QA cycling)
508
+ if (ultraqa.state?.active && !isStaleState(ultraqa.state) && isSessionMatch(ultraqa.state, sessionId)) {
509
+ const cycle = ultraqa.state.cycle || 1;
510
+ const maxCycles = ultraqa.state.max_cycles || 10;
511
+ if (cycle < maxCycles && !ultraqa.state.all_passing) {
512
+ ultraqa.state.cycle = cycle + 1;
513
+ ultraqa.state.last_checked_at = new Date().toISOString();
514
+ writeJsonFile(ultraqa.path, ultraqa.state);
515
+
516
+ // Fire-and-forget notification
517
+ sendStopNotification('ultraqa', ultraqa.state, sessionId, directory).catch(() => {});
518
+
519
+ console.log(
520
+ JSON.stringify({
521
+ decision: "block",
522
+ reason: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing. When all tests pass, run /ultrapower:cancel to cleanly exit and clean up state files. If cancel fails, retry with /ultrapower:cancel --force.`,
523
+ }),
524
+ );
525
+ return;
526
+ }
527
+ }
528
+
529
+ // Priority 7: Ultrawork - ALWAYS continue while active (not just when tasks exist)
530
+ // This prevents false stops from bash errors, transient failures, etc.
531
+ // Session isolation: only block if state belongs to this session (issue #311)
532
+ // Project isolation: only block if state belongs to this project
533
+ if (
534
+ ultrawork.state?.active &&
535
+ !isStaleState(ultrawork.state) &&
536
+ isSessionMatch(ultrawork.state, sessionId) &&
537
+ isStateForCurrentProject(ultrawork.state, directory, ultrawork.isGlobal)
538
+ ) {
539
+ const newCount = (ultrawork.state.reinforcement_count || 0) + 1;
540
+ const maxReinforcements = ultrawork.state.max_reinforcements || 50;
541
+
542
+ if (newCount > maxReinforcements) {
543
+ // Max reinforcements reached - allow stop
544
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
545
+ return;
546
+ }
547
+
548
+ ultrawork.state.reinforcement_count = newCount;
549
+ ultrawork.state.last_checked_at = new Date().toISOString();
550
+ writeJsonFile(ultrawork.path, ultrawork.state);
551
+
552
+ // Fire-and-forget notification
553
+ sendStopNotification('ultrawork', ultrawork.state, sessionId, directory).catch(() => {});
554
+
555
+ let reason = `[ULTRAWORK #${newCount}/${maxReinforcements}] Mode active.`;
556
+
557
+ if (totalIncomplete > 0) {
558
+ const itemType = taskCount > 0 ? "Tasks" : "todos";
559
+ reason += ` ${totalIncomplete} incomplete ${itemType} remain. Continue working.`;
560
+ } else if (newCount >= 3) {
561
+ // Only suggest cancel after minimum iterations (guard against no-tasks-created scenario)
562
+ reason += ` If all work is complete, run /ultrapower:cancel to cleanly exit ultrawork mode and clean up state files. If cancel fails, retry with /ultrapower:cancel --force. Otherwise, continue working.`;
563
+ } else {
564
+ // Early iterations with no tasks yet - just tell LLM to continue
565
+ reason += ` Continue working - create Tasks to track your progress.`;
566
+ }
567
+
568
+ if (ultrawork.state.original_prompt) {
569
+ reason += `\nTask: ${ultrawork.state.original_prompt}`;
570
+ }
571
+
572
+ console.log(JSON.stringify({ decision: "block", reason }));
573
+ return;
574
+ }
575
+
576
+ // No blocking needed — Claude is truly idle.
577
+ // Send session-idle notification (fire-and-forget) so external integrations
578
+ // (Telegram, Discord) know the session went idle without any active mode.
579
+ if (sessionId) {
580
+ try {
581
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
582
+ if (pluginRoot) {
583
+ const { pathToFileURL } = require('url');
584
+ import(pathToFileURL(join(pluginRoot, 'dist', 'notifications', 'index.js')).href)
585
+ .then(({ notify }) =>
586
+ notify('session-idle', {
587
+ sessionId,
588
+ projectPath: directory,
589
+ }).catch(() => {})
590
+ )
591
+ .catch(() => {});
592
+ }
593
+ } catch {
594
+ // Notification module not available, skip silently
595
+ }
596
+ }
597
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
598
+ } catch (error) {
599
+ // On any error, allow stop rather than blocking forever
600
+ console.error(`[persistent-mode] Error: ${error.message}`);
601
+ console.log(JSON.stringify({ continue: true, suppressOutput: true }));
602
+ }
603
+ }
604
+
605
+ main();