factory-factory 0.2.3 → 0.2.5

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 (364) hide show
  1. package/README.md +2 -2
  2. package/dist/client/assets/{_basePickBy-ClLqD23W.js → _basePickBy-BXw54oMQ.js} +1 -1
  3. package/dist/client/assets/{_baseUniq-DVVSJ599.js → _baseUniq-_2-lN-JZ.js} +1 -1
  4. package/dist/client/assets/{arc-DCPJ-atk.js → arc-DaCKgXo4.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-VXUJARFQ-dvBXEdHJ.js → architectureDiagram-VXUJARFQ-D5dlDvyJ.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-VD42YOAC-BnlsKN_S.js → blockDiagram-VD42YOAC-CEAiD2S_.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-YG6GDRKO-AMG1QOcD.js → c4Diagram-YG6GDRKO-DTNRx2b5.js} +1 -1
  8. package/dist/client/assets/channel-BusoDFL5.js +1 -0
  9. package/dist/client/assets/{chunk-4BX2VUAB-CvbHsudf.js → chunk-4BX2VUAB-yvp0WNZ0.js} +1 -1
  10. package/dist/client/assets/{chunk-55IACEB6-CtFa9sig.js → chunk-55IACEB6-Cse3ExNd.js} +1 -1
  11. package/dist/client/assets/{chunk-B4BG7PRW-sjqCuFZK.js → chunk-B4BG7PRW-Bl2GpPLn.js} +1 -1
  12. package/dist/client/assets/{chunk-DI55MBZ5-vsqDRqHG.js → chunk-DI55MBZ5-CV5Cqkss.js} +1 -1
  13. package/dist/client/assets/{chunk-FMBD7UC4-lEVbnFWf.js → chunk-FMBD7UC4-CP3yjlO6.js} +1 -1
  14. package/dist/client/assets/{chunk-QN33PNHL-laYvT_kS.js → chunk-QN33PNHL-DUdJaEPB.js} +1 -1
  15. package/dist/client/assets/{chunk-QZHKN3VN-Cs-LwO3R.js → chunk-QZHKN3VN-DYTUnPIc.js} +1 -1
  16. package/dist/client/assets/{chunk-TZMSLE5B-GQQ1aq3v.js → chunk-TZMSLE5B-CtWiqR24.js} +1 -1
  17. package/dist/client/assets/classDiagram-2ON5EDUG-C3RMRasv.js +1 -0
  18. package/dist/client/assets/classDiagram-v2-WZHVMYZB-C3RMRasv.js +1 -0
  19. package/dist/client/assets/clone-D6B-nWvg.js +1 -0
  20. package/dist/client/assets/{cose-bilkent-S5V4N54A-Y_FrS_Az.js → cose-bilkent-S5V4N54A-B67RdhOB.js} +1 -1
  21. package/dist/client/assets/{dagre-6UL2VRFP-DQIwKP-6.js → dagre-6UL2VRFP-D9u_oxJL.js} +1 -1
  22. package/dist/client/assets/{diagram-PSM6KHXK-jFQ4tsW4.js → diagram-PSM6KHXK-DOWfPnpa.js} +1 -1
  23. package/dist/client/assets/{diagram-QEK2KX5R-0kFBcfBt.js → diagram-QEK2KX5R-CqJwZbAA.js} +1 -1
  24. package/dist/client/assets/{diagram-S2PKOQOG-Cx_fwFHF.js → diagram-S2PKOQOG-D0ri9ztm.js} +1 -1
  25. package/dist/client/assets/{erDiagram-Q2GNP2WA-CUgHzWnq.js → erDiagram-Q2GNP2WA-0cRKPUY7.js} +1 -1
  26. package/dist/client/assets/{flowDiagram-NV44I4VS-DBPSyGKN.js → flowDiagram-NV44I4VS-Cj_5-ww1.js} +1 -1
  27. package/dist/client/assets/{ganttDiagram-JELNMOA3-BPAxxsuh.js → ganttDiagram-JELNMOA3-Cwm2arni.js} +1 -1
  28. package/dist/client/assets/{gitGraphDiagram-NY62KEGX-BHLuayx7.js → gitGraphDiagram-NY62KEGX-DKyLuO26.js} +1 -1
  29. package/dist/client/assets/{graph-hSnQ7U0p.js → graph-BhRkLhV1.js} +1 -1
  30. package/dist/client/assets/{index-CwTQdVJd.js → index-1HR_eYGL.js} +220 -182
  31. package/dist/client/assets/index-DHENAn70.css +1 -0
  32. package/dist/client/assets/{infoDiagram-WHAUD3N6-DqA4KMDO.js → infoDiagram-WHAUD3N6-DuzPbELj.js} +1 -1
  33. package/dist/client/assets/{journeyDiagram-XKPGCS4Q-DlVpIfsm.js → journeyDiagram-XKPGCS4Q-BfearpNr.js} +1 -1
  34. package/dist/client/assets/{kanban-definition-3W4ZIXB7-Bn7zJ_Qs.js → kanban-definition-3W4ZIXB7-R5KMtAj4.js} +1 -1
  35. package/dist/client/assets/{layout-BgfjjiLB.js → layout-B-tL36G2.js} +1 -1
  36. package/dist/client/assets/{linear-pgYyXM7v.js → linear-CvIBiHN-.js} +1 -1
  37. package/dist/client/assets/{mindmap-definition-VGOIOE7T-CU2bFQ_O.js → mindmap-definition-VGOIOE7T-CHYN0wBz.js} +1 -1
  38. package/dist/client/assets/{pieDiagram-ADFJNKIX-CdkFaWPc.js → pieDiagram-ADFJNKIX-LkA8WwD-.js} +1 -1
  39. package/dist/client/assets/{quadrantDiagram-AYHSOK5B-gp1mJ5Yc.js → quadrantDiagram-AYHSOK5B-B5s8Qc9j.js} +1 -1
  40. package/dist/client/assets/{requirementDiagram-UZGBJVZJ-BO59auEv.js → requirementDiagram-UZGBJVZJ-BMLpjBJA.js} +1 -1
  41. package/dist/client/assets/{sankeyDiagram-TZEHDZUN-BrZ8rlwL.js → sankeyDiagram-TZEHDZUN-BIj9RGlp.js} +1 -1
  42. package/dist/client/assets/{sequenceDiagram-WL72ISMW-LojphsDR.js → sequenceDiagram-WL72ISMW-uGM-Jh_O.js} +1 -1
  43. package/dist/client/assets/{stateDiagram-FKZM4ZOC-8bPGJ9iG.js → stateDiagram-FKZM4ZOC-CQQhnUok.js} +1 -1
  44. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-BUJPEM7D.js +1 -0
  45. package/dist/client/assets/{terminal-instance-YWv2YE4B.js → terminal-instance-DpK3PfW4.js} +1 -1
  46. package/dist/client/assets/{timeline-definition-IT6M3QCI-1mTwPI08.js → timeline-definition-IT6M3QCI-BusXMYfI.js} +1 -1
  47. package/dist/client/assets/{treemap-KMMF4GRG-VwmVQuy1.js → treemap-KMMF4GRG-XF3-w8dz.js} +1 -1
  48. package/dist/client/assets/{xychartDiagram-PRI3JC2R-zPQ-EL8y.js → xychartDiagram-PRI3JC2R-DhKTXxb3.js} +1 -1
  49. package/dist/client/index.html +2 -2
  50. package/dist/prisma/generated/enums.d.ts +0 -1
  51. package/dist/prisma/generated/enums.d.ts.map +1 -1
  52. package/dist/prisma/generated/enums.js +0 -1
  53. package/dist/prisma/generated/enums.js.map +1 -1
  54. package/dist/prisma/generated/internal/class.js +2 -2
  55. package/dist/prisma/generated/internal/class.js.map +1 -1
  56. package/dist/prisma/generated/internal/prismaNamespace.d.ts +0 -1
  57. package/dist/prisma/generated/internal/prismaNamespace.d.ts.map +1 -1
  58. package/dist/prisma/generated/internal/prismaNamespace.js +0 -1
  59. package/dist/prisma/generated/internal/prismaNamespace.js.map +1 -1
  60. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts +0 -1
  61. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  62. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js +0 -1
  63. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  64. package/dist/prisma/generated/models/UserSettings.d.ts +1 -29
  65. package/dist/prisma/generated/models/UserSettings.d.ts.map +1 -1
  66. package/dist/prompts/ratchet/dispatch.md +31 -0
  67. package/dist/prompts/workflows/ratchet.md +22 -36
  68. package/dist/src/backend/app-context.d.ts +4 -0
  69. package/dist/src/backend/app-context.d.ts.map +1 -1
  70. package/dist/src/backend/app-context.js +4 -0
  71. package/dist/src/backend/app-context.js.map +1 -1
  72. package/dist/src/backend/claude/protocol.d.ts +3 -0
  73. package/dist/src/backend/claude/protocol.d.ts.map +1 -1
  74. package/dist/src/backend/claude/protocol.js +63 -10
  75. package/dist/src/backend/claude/protocol.js.map +1 -1
  76. package/dist/src/backend/claude/protocol.test.js +188 -0
  77. package/dist/src/backend/claude/protocol.test.js.map +1 -1
  78. package/dist/src/backend/claude/registry.d.ts.map +1 -1
  79. package/dist/src/backend/claude/registry.js +12 -1
  80. package/dist/src/backend/claude/registry.js.map +1 -1
  81. package/dist/src/backend/claude/session.d.ts.map +1 -1
  82. package/dist/src/backend/claude/session.js +40 -6
  83. package/dist/src/backend/claude/session.js.map +1 -1
  84. package/dist/src/backend/claude/session.test.js +46 -0
  85. package/dist/src/backend/claude/session.test.js.map +1 -1
  86. package/dist/src/backend/claude/types.d.ts +28 -3
  87. package/dist/src/backend/claude/types.d.ts.map +1 -1
  88. package/dist/src/backend/claude/types.js +28 -0
  89. package/dist/src/backend/claude/types.js.map +1 -1
  90. package/dist/src/backend/interceptors/pr-detection.interceptor.d.ts.map +1 -1
  91. package/dist/src/backend/interceptors/pr-detection.interceptor.js +19 -23
  92. package/dist/src/backend/interceptors/pr-detection.interceptor.js.map +1 -1
  93. package/dist/src/backend/prompts/markdown-loader.d.ts +22 -0
  94. package/dist/src/backend/prompts/markdown-loader.d.ts.map +1 -0
  95. package/dist/src/backend/prompts/markdown-loader.js +77 -0
  96. package/dist/src/backend/prompts/markdown-loader.js.map +1 -0
  97. package/dist/src/backend/prompts/markdown-loader.test.d.ts +2 -0
  98. package/dist/src/backend/prompts/markdown-loader.test.d.ts.map +1 -0
  99. package/dist/src/backend/prompts/markdown-loader.test.js +190 -0
  100. package/dist/src/backend/prompts/markdown-loader.test.js.map +1 -0
  101. package/dist/src/backend/prompts/quick-actions.d.ts.map +1 -1
  102. package/dist/src/backend/prompts/quick-actions.js +34 -92
  103. package/dist/src/backend/prompts/quick-actions.js.map +1 -1
  104. package/dist/src/backend/prompts/ratchet-dispatch.d.ts +3 -0
  105. package/dist/src/backend/prompts/ratchet-dispatch.d.ts.map +1 -0
  106. package/dist/src/backend/prompts/ratchet-dispatch.js +53 -0
  107. package/dist/src/backend/prompts/ratchet-dispatch.js.map +1 -0
  108. package/dist/src/backend/prompts/ratchet-dispatch.test.d.ts +2 -0
  109. package/dist/src/backend/prompts/ratchet-dispatch.test.d.ts.map +1 -0
  110. package/dist/src/backend/prompts/ratchet-dispatch.test.js +34 -0
  111. package/dist/src/backend/prompts/ratchet-dispatch.test.js.map +1 -0
  112. package/dist/src/backend/prompts/workflows.d.ts.map +1 -1
  113. package/dist/src/backend/prompts/workflows.js +23 -76
  114. package/dist/src/backend/prompts/workflows.js.map +1 -1
  115. package/dist/src/backend/resource_accessors/user-settings.accessor.d.ts +0 -1
  116. package/dist/src/backend/resource_accessors/user-settings.accessor.d.ts.map +1 -1
  117. package/dist/src/backend/resource_accessors/user-settings.accessor.js.map +1 -1
  118. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts +4 -3
  119. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts.map +1 -1
  120. package/dist/src/backend/resource_accessors/workspace.accessor.js +4 -2
  121. package/dist/src/backend/resource_accessors/workspace.accessor.js.map +1 -1
  122. package/dist/src/backend/routers/websocket/chat.handler.d.ts.map +1 -1
  123. package/dist/src/backend/routers/websocket/chat.handler.js +1 -30
  124. package/dist/src/backend/routers/websocket/chat.handler.js.map +1 -1
  125. package/dist/src/backend/services/chat-event-forwarder.service.d.ts.map +1 -1
  126. package/dist/src/backend/services/chat-event-forwarder.service.js +9 -13
  127. package/dist/src/backend/services/chat-event-forwarder.service.js.map +1 -1
  128. package/dist/src/backend/services/chat-message-handlers/attachment-processing.d.ts +14 -0
  129. package/dist/src/backend/services/chat-message-handlers/attachment-processing.d.ts.map +1 -0
  130. package/dist/src/backend/services/chat-message-handlers/attachment-processing.js +81 -0
  131. package/dist/src/backend/services/chat-message-handlers/attachment-processing.js.map +1 -0
  132. package/dist/src/backend/services/chat-message-handlers/attachment-processing.test.d.ts +2 -0
  133. package/dist/src/backend/services/chat-message-handlers/attachment-processing.test.d.ts.map +1 -0
  134. package/dist/src/backend/services/chat-message-handlers/attachment-processing.test.js +315 -0
  135. package/dist/src/backend/services/chat-message-handlers/attachment-processing.test.js.map +1 -0
  136. package/dist/src/backend/services/chat-message-handlers/handlers/get-history.handler.d.ts +2 -1
  137. package/dist/src/backend/services/chat-message-handlers/handlers/get-history.handler.d.ts.map +1 -1
  138. package/dist/src/backend/services/chat-message-handlers/handlers/get-history.handler.js.map +1 -1
  139. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.d.ts +2 -1
  140. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.d.ts.map +1 -1
  141. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.js +7 -3
  142. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.js.map +1 -1
  143. package/dist/src/backend/services/chat-message-handlers/handlers/handlers.test.js +156 -6
  144. package/dist/src/backend/services/chat-message-handlers/handlers/handlers.test.js.map +1 -1
  145. package/dist/src/backend/services/chat-message-handlers/handlers/list-sessions.handler.d.ts +2 -1
  146. package/dist/src/backend/services/chat-message-handlers/handlers/list-sessions.handler.d.ts.map +1 -1
  147. package/dist/src/backend/services/chat-message-handlers/handlers/list-sessions.handler.js.map +1 -1
  148. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.d.ts +2 -1
  149. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.d.ts.map +1 -1
  150. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.js +41 -31
  151. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.js.map +1 -1
  152. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.d.ts +2 -1
  153. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.d.ts.map +1 -1
  154. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.js +1 -2
  155. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.js.map +1 -1
  156. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.d.ts +2 -1
  157. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.d.ts.map +1 -1
  158. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.js +1 -2
  159. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.js.map +1 -1
  160. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.d.ts +2 -1
  161. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.d.ts.map +1 -1
  162. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.js +6 -7
  163. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.js.map +1 -1
  164. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.d.ts +2 -1
  165. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.d.ts.map +1 -1
  166. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.js +1 -2
  167. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.js.map +1 -1
  168. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.d.ts +2 -1
  169. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.d.ts.map +1 -1
  170. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.js +8 -9
  171. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.js.map +1 -1
  172. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.d.ts +2 -1
  173. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.d.ts.map +1 -1
  174. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.js +4 -5
  175. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.js.map +1 -1
  176. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.d.ts +2 -1
  177. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.d.ts.map +1 -1
  178. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.js +4 -5
  179. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.js.map +1 -1
  180. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.d.ts +2 -1
  181. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.d.ts.map +1 -1
  182. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.js +4 -2
  183. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.js.map +1 -1
  184. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.d.ts +2 -1
  185. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.d.ts.map +1 -1
  186. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.js +7 -2
  187. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.js.map +1 -1
  188. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.d.ts +2 -1
  189. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.d.ts.map +1 -1
  190. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.js +1 -2
  191. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.js.map +1 -1
  192. package/dist/src/backend/services/chat-message-handlers/registry.d.ts +5 -1
  193. package/dist/src/backend/services/chat-message-handlers/registry.d.ts.map +1 -1
  194. package/dist/src/backend/services/chat-message-handlers/registry.js.map +1 -1
  195. package/dist/src/backend/services/chat-message-handlers.service.d.ts +0 -2
  196. package/dist/src/backend/services/chat-message-handlers.service.d.ts.map +1 -1
  197. package/dist/src/backend/services/chat-message-handlers.service.js +6 -68
  198. package/dist/src/backend/services/chat-message-handlers.service.js.map +1 -1
  199. package/dist/src/backend/services/chat-transport-adapter.service.d.ts +2 -0
  200. package/dist/src/backend/services/chat-transport-adapter.service.d.ts.map +1 -1
  201. package/dist/src/backend/services/chat-transport-adapter.service.js +32 -1
  202. package/dist/src/backend/services/chat-transport-adapter.service.js.map +1 -1
  203. package/dist/src/backend/services/chat-transport-adapter.service.test.js +14 -0
  204. package/dist/src/backend/services/chat-transport-adapter.service.test.js.map +1 -1
  205. package/dist/src/backend/services/data-backup.service.d.ts +3 -144
  206. package/dist/src/backend/services/data-backup.service.d.ts.map +1 -1
  207. package/dist/src/backend/services/data-backup.service.js +119 -115
  208. package/dist/src/backend/services/data-backup.service.js.map +1 -1
  209. package/dist/src/backend/services/data-backup.service.test.d.ts +2 -0
  210. package/dist/src/backend/services/data-backup.service.test.d.ts.map +1 -0
  211. package/dist/src/backend/services/data-backup.service.test.js +602 -0
  212. package/dist/src/backend/services/data-backup.service.test.js.map +1 -0
  213. package/dist/src/backend/services/file-lock.service.d.ts.map +1 -1
  214. package/dist/src/backend/services/file-lock.service.js +3 -5
  215. package/dist/src/backend/services/file-lock.service.js.map +1 -1
  216. package/dist/src/backend/services/file-lock.service.test.js +55 -0
  217. package/dist/src/backend/services/file-lock.service.test.js.map +1 -1
  218. package/dist/src/backend/services/github-cli.service.d.ts.map +1 -1
  219. package/dist/src/backend/services/github-cli.service.js +118 -19
  220. package/dist/src/backend/services/github-cli.service.js.map +1 -1
  221. package/dist/src/backend/services/github-cli.service.test.js +126 -0
  222. package/dist/src/backend/services/github-cli.service.test.js.map +1 -1
  223. package/dist/src/backend/services/index.d.ts +1 -0
  224. package/dist/src/backend/services/index.d.ts.map +1 -1
  225. package/dist/src/backend/services/index.js +1 -0
  226. package/dist/src/backend/services/index.js.map +1 -1
  227. package/dist/src/backend/services/message-state-machine.d.ts.map +1 -1
  228. package/dist/src/backend/services/message-state-machine.js +11 -5
  229. package/dist/src/backend/services/message-state-machine.js.map +1 -1
  230. package/dist/src/backend/services/message-state.service.d.ts +16 -18
  231. package/dist/src/backend/services/message-state.service.d.ts.map +1 -1
  232. package/dist/src/backend/services/message-state.service.js +8 -14
  233. package/dist/src/backend/services/message-state.service.js.map +1 -1
  234. package/dist/src/backend/services/message-state.service.test.js +36 -45
  235. package/dist/src/backend/services/message-state.service.test.js.map +1 -1
  236. package/dist/src/backend/services/pr-snapshot.service.d.ts +9 -1
  237. package/dist/src/backend/services/pr-snapshot.service.d.ts.map +1 -1
  238. package/dist/src/backend/services/pr-snapshot.service.js +53 -3
  239. package/dist/src/backend/services/pr-snapshot.service.js.map +1 -1
  240. package/dist/src/backend/services/pr-snapshot.service.test.js +102 -47
  241. package/dist/src/backend/services/pr-snapshot.service.test.js.map +1 -1
  242. package/dist/src/backend/services/ratchet.service.d.ts +14 -71
  243. package/dist/src/backend/services/ratchet.service.d.ts.map +1 -1
  244. package/dist/src/backend/services/ratchet.service.js +198 -439
  245. package/dist/src/backend/services/ratchet.service.js.map +1 -1
  246. package/dist/src/backend/services/ratchet.service.test.js +210 -358
  247. package/dist/src/backend/services/ratchet.service.test.js.map +1 -1
  248. package/dist/src/backend/services/run-script-state-machine.service.d.ts +27 -0
  249. package/dist/src/backend/services/run-script-state-machine.service.d.ts.map +1 -0
  250. package/dist/src/backend/services/run-script-state-machine.service.js +149 -0
  251. package/dist/src/backend/services/run-script-state-machine.service.js.map +1 -0
  252. package/dist/src/backend/services/run-script-state-machine.service.test.d.ts +2 -0
  253. package/dist/src/backend/services/run-script-state-machine.service.test.d.ts.map +1 -0
  254. package/dist/src/backend/services/run-script-state-machine.service.test.js +453 -0
  255. package/dist/src/backend/services/run-script-state-machine.service.test.js.map +1 -0
  256. package/dist/src/backend/services/run-script.service.d.ts +0 -7
  257. package/dist/src/backend/services/run-script.service.d.ts.map +1 -1
  258. package/dist/src/backend/services/run-script.service.js +145 -76
  259. package/dist/src/backend/services/run-script.service.js.map +1 -1
  260. package/dist/src/backend/services/scheduler.service.d.ts.map +1 -1
  261. package/dist/src/backend/services/scheduler.service.js +16 -7
  262. package/dist/src/backend/services/scheduler.service.js.map +1 -1
  263. package/dist/src/backend/services/scheduler.service.test.js +41 -5
  264. package/dist/src/backend/services/scheduler.service.test.js.map +1 -1
  265. package/dist/src/backend/services/session-runtime-store.service.d.ts +38 -0
  266. package/dist/src/backend/services/session-runtime-store.service.d.ts.map +1 -0
  267. package/dist/src/backend/services/session-runtime-store.service.js +111 -0
  268. package/dist/src/backend/services/session-runtime-store.service.js.map +1 -0
  269. package/dist/src/backend/services/session-runtime-store.service.test.d.ts +2 -0
  270. package/dist/src/backend/services/session-runtime-store.service.test.d.ts.map +1 -0
  271. package/dist/src/backend/services/session-runtime-store.service.test.js +15 -0
  272. package/dist/src/backend/services/session-runtime-store.service.test.js.map +1 -0
  273. package/dist/src/backend/services/session.repository.d.ts +2 -0
  274. package/dist/src/backend/services/session.repository.d.ts.map +1 -1
  275. package/dist/src/backend/services/session.repository.js +3 -0
  276. package/dist/src/backend/services/session.repository.js.map +1 -1
  277. package/dist/src/backend/services/session.service.d.ts +0 -1
  278. package/dist/src/backend/services/session.service.d.ts.map +1 -1
  279. package/dist/src/backend/services/session.service.js +18 -23
  280. package/dist/src/backend/services/session.service.js.map +1 -1
  281. package/dist/src/backend/services/session.service.test.js +210 -12
  282. package/dist/src/backend/services/session.service.test.js.map +1 -1
  283. package/dist/src/backend/services/workspace-creation.service.test.js +0 -2
  284. package/dist/src/backend/services/workspace-creation.service.test.js.map +1 -1
  285. package/dist/src/backend/services/workspace-flow-state.service.d.ts.map +1 -1
  286. package/dist/src/backend/services/workspace-flow-state.service.js +0 -1
  287. package/dist/src/backend/services/workspace-flow-state.service.js.map +1 -1
  288. package/dist/src/backend/services/workspace-query.service.d.ts +1 -0
  289. package/dist/src/backend/services/workspace-query.service.d.ts.map +1 -1
  290. package/dist/src/backend/services/workspace-query.service.js +8 -0
  291. package/dist/src/backend/services/workspace-query.service.js.map +1 -1
  292. package/dist/src/backend/services/worktree-lifecycle.service.d.ts.map +1 -1
  293. package/dist/src/backend/services/worktree-lifecycle.service.js +139 -19
  294. package/dist/src/backend/services/worktree-lifecycle.service.js.map +1 -1
  295. package/dist/src/backend/services/worktree-lifecycle.service.test.js +48 -1
  296. package/dist/src/backend/services/worktree-lifecycle.service.test.js.map +1 -1
  297. package/dist/src/backend/trpc/admin.trpc.d.ts +130 -7
  298. package/dist/src/backend/trpc/admin.trpc.d.ts.map +1 -1
  299. package/dist/src/backend/trpc/admin.trpc.js +2 -1
  300. package/dist/src/backend/trpc/admin.trpc.js.map +1 -1
  301. package/dist/src/backend/trpc/github.trpc.d.ts +14 -0
  302. package/dist/src/backend/trpc/github.trpc.d.ts.map +1 -1
  303. package/dist/src/backend/trpc/github.trpc.js +28 -0
  304. package/dist/src/backend/trpc/github.trpc.js.map +1 -1
  305. package/dist/src/backend/trpc/index.d.ts +149 -20
  306. package/dist/src/backend/trpc/index.d.ts.map +1 -1
  307. package/dist/src/backend/trpc/session.trpc.d.ts +3 -3
  308. package/dist/src/backend/trpc/user-settings.trpc.d.ts +0 -3
  309. package/dist/src/backend/trpc/user-settings.trpc.d.ts.map +1 -1
  310. package/dist/src/backend/trpc/user-settings.trpc.js +0 -1
  311. package/dist/src/backend/trpc/user-settings.trpc.js.map +1 -1
  312. package/dist/src/backend/trpc/workspace/run-script.trpc.d.ts +0 -7
  313. package/dist/src/backend/trpc/workspace/run-script.trpc.d.ts.map +1 -1
  314. package/dist/src/backend/trpc/workspace.trpc.d.ts +2 -7
  315. package/dist/src/backend/trpc/workspace.trpc.d.ts.map +1 -1
  316. package/dist/src/backend/trpc/workspace.trpc.js +13 -1
  317. package/dist/src/backend/trpc/workspace.trpc.js.map +1 -1
  318. package/dist/src/shared/ci-status.d.ts +9 -0
  319. package/dist/src/shared/ci-status.d.ts.map +1 -0
  320. package/dist/src/shared/ci-status.js +44 -0
  321. package/dist/src/shared/ci-status.js.map +1 -0
  322. package/dist/src/shared/claude/index.d.ts +1 -0
  323. package/dist/src/shared/claude/index.d.ts.map +1 -1
  324. package/dist/src/shared/claude/index.js +1 -0
  325. package/dist/src/shared/claude/index.js.map +1 -1
  326. package/dist/src/shared/claude/protocol.d.ts +6 -3
  327. package/dist/src/shared/claude/protocol.d.ts.map +1 -1
  328. package/dist/src/shared/claude/protocol.js.map +1 -1
  329. package/dist/src/shared/schemas/export-data.schema.d.ts +865 -0
  330. package/dist/src/shared/schemas/export-data.schema.d.ts.map +1 -0
  331. package/dist/src/shared/schemas/export-data.schema.js +167 -0
  332. package/dist/src/shared/schemas/export-data.schema.js.map +1 -0
  333. package/dist/src/shared/schemas/persisted-stores.schema.d.ts +19 -0
  334. package/dist/src/shared/schemas/persisted-stores.schema.d.ts.map +1 -0
  335. package/dist/src/shared/schemas/persisted-stores.schema.js +17 -0
  336. package/dist/src/shared/schemas/persisted-stores.schema.js.map +1 -0
  337. package/dist/src/shared/session-runtime.d.ts +17 -0
  338. package/dist/src/shared/session-runtime.d.ts.map +1 -0
  339. package/dist/src/shared/session-runtime.js +9 -0
  340. package/dist/src/shared/session-runtime.js.map +1 -0
  341. package/dist/src/shared/websocket/chat-message.schema.d.ts +24 -11
  342. package/dist/src/shared/websocket/chat-message.schema.d.ts.map +1 -1
  343. package/dist/src/shared/websocket/chat-message.schema.js +4 -1
  344. package/dist/src/shared/websocket/chat-message.schema.js.map +1 -1
  345. package/dist/src/shared/websocket/index.d.ts +1 -1
  346. package/dist/src/shared/websocket/index.d.ts.map +1 -1
  347. package/dist/src/shared/websocket/index.js.map +1 -1
  348. package/dist/src/shared/workspace-sidebar-status.d.ts +20 -0
  349. package/dist/src/shared/workspace-sidebar-status.d.ts.map +1 -0
  350. package/dist/src/shared/workspace-sidebar-status.js +73 -0
  351. package/dist/src/shared/workspace-sidebar-status.js.map +1 -0
  352. package/dist/tsconfig.backend.tsbuildinfo +1 -1
  353. package/package.json +2 -2
  354. package/prisma/migrations/20260206170000_remove_merge_conflict_state/migration.sql +7 -0
  355. package/prisma/migrations/20260206174726_remove_ratchet_auto_fix_conflicts/migration.sql +33 -0
  356. package/prisma/schema.prisma +0 -2
  357. package/prompts/ratchet/dispatch.md +31 -0
  358. package/prompts/workflows/ratchet.md +22 -36
  359. package/dist/client/assets/channel-DWeb1p1N.js +0 -1
  360. package/dist/client/assets/classDiagram-2ON5EDUG-DkYgN_SG.js +0 -1
  361. package/dist/client/assets/classDiagram-v2-WZHVMYZB-DkYgN_SG.js +0 -1
  362. package/dist/client/assets/clone-WKF0ejf5.js +0 -1
  363. package/dist/client/assets/index-DCpIgwSv.css +0 -1
  364. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-BFf_HK07.js +0 -1
@@ -1,27 +1,16 @@
1
1
  import { CIStatus, RatchetState, SessionStatus } from '../../../prisma/generated/client.js';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
- vi.mock('../db', () => ({
4
- prisma: {
5
- $transaction: vi.fn(),
6
- },
7
- }));
8
- vi.mock('../resource_accessors/user-settings.accessor', () => ({
9
- userSettingsAccessor: {
10
- get: vi.fn(),
11
- },
12
- }));
13
3
  vi.mock('../resource_accessors/workspace.accessor', () => ({
14
4
  workspaceAccessor: {
15
5
  findWithPRsForRatchet: vi.fn(),
16
6
  findForRatchetById: vi.fn(),
17
- findById: vi.fn(),
18
7
  update: vi.fn(),
19
8
  },
20
9
  }));
21
10
  vi.mock('../resource_accessors/claude-session.accessor', () => ({
22
11
  claudeSessionAccessor: {
23
- create: vi.fn(),
24
12
  findById: vi.fn(),
13
+ findByWorkspaceId: vi.fn(),
25
14
  },
26
15
  }));
27
16
  vi.mock('./github-cli.service', () => ({
@@ -30,27 +19,25 @@ vi.mock('./github-cli.service', () => ({
30
19
  getPRFullDetails: vi.fn(),
31
20
  getReviewComments: vi.fn(),
32
21
  computeCIStatus: vi.fn(),
33
- mergePR: vi.fn(),
22
+ getAuthenticatedUsername: vi.fn(),
23
+ },
24
+ }));
25
+ vi.mock('./fixer-session.service', () => ({
26
+ fixerSessionService: {
27
+ acquireAndDispatch: vi.fn(),
34
28
  },
35
29
  }));
36
30
  vi.mock('./session.service', () => ({
37
31
  sessionService: {
38
- getClient: vi.fn(),
39
- isSessionActive: vi.fn(),
40
- startClaudeSession: vi.fn(),
32
+ isSessionRunning: vi.fn(),
33
+ stopClaudeSession: vi.fn(),
41
34
  },
42
35
  }));
43
36
  vi.mock('./message-state.service', () => ({
44
37
  messageStateService: {
45
- getRecentUserAndAgentMessages: vi.fn(),
46
38
  injectCommittedUserMessage: vi.fn(),
47
39
  },
48
40
  }));
49
- vi.mock('./config.service', () => ({
50
- configService: {
51
- getMaxSessionsPerWorkspace: vi.fn(() => 5),
52
- },
53
- }));
54
41
  vi.mock('./logger.service', () => ({
55
42
  createLogger: () => ({
56
43
  debug: vi.fn(),
@@ -60,448 +47,313 @@ vi.mock('./logger.service', () => ({
60
47
  }),
61
48
  }));
62
49
  import { claudeSessionAccessor } from '../resource_accessors/claude-session.accessor.js';
63
- import { userSettingsAccessor } from '../resource_accessors/user-settings.accessor.js';
64
50
  import { workspaceAccessor } from '../resource_accessors/workspace.accessor.js';
51
+ import { fixerSessionService } from './fixer-session.service.js';
52
+ import { githubCLIService } from './github-cli.service.js';
65
53
  import { ratchetService } from './ratchet.service.js';
66
54
  import { sessionService } from './session.service.js';
67
- describe('ratchet service', () => {
55
+ describe('ratchet service (state-change + idle dispatch)', () => {
68
56
  beforeEach(() => {
69
57
  vi.clearAllMocks();
70
58
  ratchetService.isShuttingDown = false;
59
+ vi.mocked(githubCLIService.getAuthenticatedUsername).mockResolvedValue(null);
71
60
  });
72
- it('checks workspaces even when global ratchet default is off', async () => {
73
- vi.mocked(userSettingsAccessor.get).mockResolvedValue({
74
- ratchetEnabled: false,
75
- ratchetAutoFixCi: true,
76
- ratchetAutoFixConflicts: true,
77
- ratchetAutoFixReviews: true,
78
- ratchetAutoMerge: false,
79
- ratchetAllowedReviewers: null,
80
- });
61
+ it('checks workspaces and processes each', async () => {
81
62
  vi.mocked(workspaceAccessor.findWithPRsForRatchet).mockResolvedValue([
82
63
  {
83
64
  id: 'ws-1',
84
65
  prUrl: 'https://github.com/example/repo/pull/1',
85
66
  prNumber: 1,
67
+ prState: 'OPEN',
68
+ prCiStatus: CIStatus.UNKNOWN,
86
69
  ratchetEnabled: true,
87
70
  ratchetState: RatchetState.IDLE,
88
71
  ratchetActiveSessionId: null,
89
72
  ratchetLastCiRunId: null,
90
- ratchetLastNotifiedState: null,
91
73
  prReviewLastCheckedAt: null,
92
74
  },
93
75
  ]);
94
- const processWorkspaceSpy = vi
95
- .spyOn(ratchetService, 'processWorkspace')
96
- .mockResolvedValue({
76
+ vi.spyOn(ratchetService, 'processWorkspace').mockResolvedValue({
97
77
  workspaceId: 'ws-1',
98
78
  previousState: RatchetState.IDLE,
99
79
  newState: RatchetState.IDLE,
100
80
  action: { type: 'WAITING', reason: 'noop' },
101
81
  });
102
82
  const result = await ratchetService.checkAllWorkspaces();
103
- expect(processWorkspaceSpy).toHaveBeenCalledTimes(1);
104
83
  expect(result.checked).toBe(1);
105
84
  });
106
- it('does not execute ratchet actions when workspace ratcheting is off', async () => {
85
+ it('does not dispatch when workspace ratcheting is disabled', async () => {
107
86
  const workspace = {
108
- id: 'ws-2',
87
+ id: 'ws-disabled',
109
88
  prUrl: 'https://github.com/example/repo/pull/2',
110
89
  prNumber: 2,
90
+ prState: 'OPEN',
91
+ prCiStatus: CIStatus.UNKNOWN,
111
92
  ratchetEnabled: false,
112
93
  ratchetState: RatchetState.IDLE,
113
94
  ratchetActiveSessionId: null,
114
95
  ratchetLastCiRunId: null,
115
- ratchetLastNotifiedState: null,
116
96
  prReviewLastCheckedAt: null,
117
97
  };
118
- const prStateInfo = {
119
- ciStatus: 'FAILURE',
120
- mergeStateStatus: 'CLEAN',
98
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
99
+ ciStatus: CIStatus.FAILURE,
100
+ snapshotKey: '2026-01-02T00:00:00Z',
121
101
  hasChangesRequested: false,
122
- hasNewReviewComments: false,
123
- failedChecks: [],
124
- ciRunId: '1001',
125
- reviews: [],
126
- comments: [],
127
- reviewComments: [],
128
- newReviewComments: [],
129
- newPRComments: [],
102
+ latestReviewActivityAtMs: null,
130
103
  prState: 'OPEN',
131
104
  prNumber: 2,
132
- };
133
- vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue(prStateInfo);
134
- vi.spyOn(ratchetService, 'determineRatchetState').mockReturnValue(RatchetState.CI_FAILED);
135
- const executeRatchetActionSpy = vi.spyOn(ratchetService, 'executeRatchetAction');
136
- vi.mocked(workspaceAccessor.findById).mockResolvedValue({
137
- ratchetEnabled: false,
138
105
  });
139
106
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
140
- const result = await ratchetService.processWorkspace(workspace, {
141
- autoFixCi: true,
142
- autoFixConflicts: true,
143
- autoFixReviews: true,
144
- autoMerge: false,
145
- allowedReviewers: [],
146
- });
147
- expect(executeRatchetActionSpy).not.toHaveBeenCalled();
107
+ const triggerSpy = vi.spyOn(ratchetService, 'triggerFixer');
108
+ const result = await ratchetService.processWorkspace(workspace);
109
+ expect(triggerSpy).not.toHaveBeenCalled();
148
110
  expect(result).toMatchObject({
149
111
  action: { type: 'DISABLED', reason: 'Workspace ratcheting disabled' },
150
- previousState: RatchetState.IDLE,
151
112
  newState: RatchetState.IDLE,
152
113
  });
153
- expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, expect.objectContaining({ ratchetState: RatchetState.IDLE }));
154
114
  });
155
- it('does not execute ratchet actions when ratcheting is disabled mid-check', async () => {
115
+ it('does not dispatch when workspace is not idle', async () => {
156
116
  const workspace = {
157
- id: 'ws-3',
117
+ id: 'ws-busy',
158
118
  prUrl: 'https://github.com/example/repo/pull/3',
159
119
  prNumber: 3,
120
+ prState: 'OPEN',
121
+ prCiStatus: CIStatus.UNKNOWN,
160
122
  ratchetEnabled: true,
161
123
  ratchetState: RatchetState.IDLE,
162
124
  ratchetActiveSessionId: null,
163
125
  ratchetLastCiRunId: null,
164
- ratchetLastNotifiedState: null,
165
- prReviewLastCheckedAt: null,
126
+ prReviewLastCheckedAt: new Date('2026-01-01T00:00:00Z'),
166
127
  };
167
- const prStateInfo = {
168
- ciStatus: 'FAILURE',
169
- mergeStateStatus: 'CLEAN',
128
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
129
+ ciStatus: CIStatus.SUCCESS,
130
+ snapshotKey: '2026-01-02T00:00:00Z',
170
131
  hasChangesRequested: false,
171
- hasNewReviewComments: false,
172
- failedChecks: [],
173
- ciRunId: '1002',
174
- reviews: [],
175
- comments: [],
176
- reviewComments: [],
177
- newReviewComments: [],
178
- newPRComments: [],
132
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
179
133
  prState: 'OPEN',
180
134
  prNumber: 3,
135
+ });
136
+ vi.mocked(workspaceAccessor.update).mockResolvedValue({});
137
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([
138
+ {
139
+ id: 'chat-1',
140
+ workflow: 'default-followup',
141
+ status: 'RUNNING',
142
+ },
143
+ ]);
144
+ const result = await ratchetService.processWorkspace(workspace);
145
+ expect(result).toMatchObject({
146
+ action: {
147
+ type: 'WAITING',
148
+ reason: 'Workspace is not idle (active non-ratchet chat session)',
149
+ },
150
+ });
151
+ });
152
+ it('dispatches when PR state changed since last ratchet', async () => {
153
+ const workspace = {
154
+ id: 'ws-change',
155
+ prUrl: 'https://github.com/example/repo/pull/4',
156
+ prNumber: 4,
157
+ prState: 'OPEN',
158
+ prCiStatus: CIStatus.UNKNOWN,
159
+ ratchetEnabled: true,
160
+ ratchetState: RatchetState.IDLE,
161
+ ratchetActiveSessionId: null,
162
+ ratchetLastCiRunId: '2026-01-01T00:00:00Z',
163
+ prReviewLastCheckedAt: new Date('2026-01-01T00:00:00Z'),
181
164
  };
182
- vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue(prStateInfo);
183
- vi.spyOn(ratchetService, 'determineRatchetState').mockReturnValue(RatchetState.CI_FAILED);
184
- const executeRatchetActionSpy = vi.spyOn(ratchetService, 'executeRatchetAction');
185
- vi.mocked(workspaceAccessor.findById).mockResolvedValue({
186
- ratchetEnabled: false,
165
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
166
+ ciStatus: CIStatus.FAILURE,
167
+ snapshotKey: '2026-01-02T00:00:00Z',
168
+ hasChangesRequested: false,
169
+ latestReviewActivityAtMs: null,
170
+ prState: 'OPEN',
171
+ prNumber: 4,
187
172
  });
188
173
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
189
- const result = await ratchetService.processWorkspace(workspace, {
190
- autoFixCi: true,
191
- autoFixConflicts: true,
192
- autoFixReviews: true,
193
- autoMerge: false,
194
- allowedReviewers: [],
174
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
175
+ vi.mocked(fixerSessionService.acquireAndDispatch).mockResolvedValue({
176
+ status: 'started',
177
+ sessionId: 'ratchet-session',
178
+ promptSent: true,
195
179
  });
196
- expect(executeRatchetActionSpy).not.toHaveBeenCalled();
180
+ const result = await ratchetService.processWorkspace(workspace);
197
181
  expect(result).toMatchObject({
198
- action: { type: 'DISABLED', reason: 'Workspace ratcheting disabled' },
199
- newState: RatchetState.IDLE,
182
+ action: { type: 'TRIGGERED_FIXER' },
200
183
  });
201
- expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, expect.objectContaining({ ratchetState: RatchetState.IDLE }));
184
+ const finalUpdatePayload = vi.mocked(workspaceAccessor.update).mock.calls.at(-1)?.[1];
185
+ expect(finalUpdatePayload.ratchetLastCiRunId).toBe('2026-01-02T00:00:00Z');
186
+ expect(finalUpdatePayload).toHaveProperty('prReviewLastCheckedAt');
202
187
  });
203
- });
204
- describe('Ratchet CI regression behavior', () => {
205
- beforeEach(() => {
206
- vi.clearAllMocks();
207
- });
208
- it('treats CI UNKNOWN as CI_RUNNING', () => {
209
- const state = ratchetService.determineRatchetState({
210
- ciStatus: CIStatus.UNKNOWN,
211
- mergeStateStatus: 'CLEAN',
188
+ it('does not dispatch when PR state unchanged since last dispatch', async () => {
189
+ const workspace = {
190
+ id: 'ws-unchanged',
191
+ prUrl: 'https://github.com/example/repo/pull/5',
192
+ prNumber: 5,
193
+ prState: 'OPEN',
194
+ prCiStatus: CIStatus.UNKNOWN,
195
+ ratchetEnabled: true,
196
+ ratchetState: RatchetState.READY,
197
+ ratchetActiveSessionId: null,
198
+ ratchetLastCiRunId: '2026-01-02T00:00:00Z',
199
+ prReviewLastCheckedAt: new Date('2026-01-02T00:00:00Z'),
200
+ };
201
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
202
+ const action = await ratchetService.evaluateAndDispatch(workspace, {
203
+ ciStatus: CIStatus.FAILURE,
204
+ snapshotKey: '2026-01-02T00:00:00Z',
212
205
  hasChangesRequested: false,
213
- hasNewReviewComments: false,
214
- failedChecks: [],
215
- ciRunId: null,
216
- reviews: [],
217
- comments: [],
218
- reviewComments: [],
219
- newReviewComments: [],
220
- newPRComments: [],
206
+ latestReviewActivityAtMs: null,
221
207
  prState: 'OPEN',
222
- prNumber: 12,
208
+ prNumber: 5,
209
+ });
210
+ expect(action).toEqual({
211
+ type: 'WAITING',
212
+ reason: 'PR state unchanged since last ratchet dispatch',
223
213
  });
224
- expect(state).toBe(RatchetState.CI_RUNNING);
225
214
  });
226
- it('re-triggers fixer when active session is unreachable and CI fails', async () => {
215
+ it('does not dispatch repeatedly for unchanged CHANGES_REQUESTED state', async () => {
227
216
  const workspace = {
228
- id: 'ws-unreachable',
217
+ id: 'ws-review-unchanged',
229
218
  prUrl: 'https://github.com/example/repo/pull/55',
230
219
  prNumber: 55,
220
+ prState: 'OPEN',
221
+ prCiStatus: CIStatus.UNKNOWN,
231
222
  ratchetEnabled: true,
232
223
  ratchetState: RatchetState.REVIEW_PENDING,
233
- ratchetActiveSessionId: 'session-stale',
234
- ratchetLastCiRunId: '2001',
235
- ratchetLastNotifiedState: RatchetState.CI_FAILED,
236
- prReviewLastCheckedAt: null,
224
+ ratchetActiveSessionId: null,
225
+ ratchetLastCiRunId: 'ci:SUCCESS|changes-requested:1735776000000',
226
+ prReviewLastCheckedAt: new Date('2026-01-02T00:00:00Z'),
237
227
  };
238
- vi.mocked(claudeSessionAccessor.findById).mockResolvedValue({
239
- id: 'session-stale',
240
- status: SessionStatus.RUNNING,
241
- });
242
- vi.mocked(sessionService.getClient).mockReturnValue(undefined);
243
- vi.mocked(workspaceAccessor.update).mockResolvedValue({});
244
- const triggerFixerSpy = vi
245
- .spyOn(ratchetService, 'triggerFixer')
246
- .mockResolvedValue({
247
- type: 'TRIGGERED_FIXER',
248
- sessionId: 'new-fixer',
249
- fixerType: 'ci',
250
- promptSent: true,
251
- });
252
- const result = await ratchetService.executeRatchetAction(workspace, RatchetState.CI_FAILED, {
253
- ciStatus: CIStatus.FAILURE,
254
- mergeStateStatus: 'CLEAN',
255
- hasChangesRequested: false,
256
- hasNewReviewComments: false,
257
- failedChecks: [],
258
- ciRunId: '3001',
259
- reviews: [],
260
- comments: [],
261
- reviewComments: [],
262
- newReviewComments: [],
263
- newPRComments: [],
228
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
229
+ const triggerSpy = vi.spyOn(ratchetService, 'triggerFixer');
230
+ const action = await ratchetService.evaluateAndDispatch(workspace, {
231
+ ciStatus: CIStatus.SUCCESS,
232
+ snapshotKey: 'ci:SUCCESS|changes-requested:1735776000000',
233
+ hasChangesRequested: true,
234
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
264
235
  prState: 'OPEN',
265
236
  prNumber: 55,
266
- }, {
267
- autoFixCi: true,
268
- autoFixConflicts: true,
269
- autoFixReviews: true,
270
- autoMerge: false,
271
- allowedReviewers: [],
272
237
  });
273
- expect(triggerFixerSpy).toHaveBeenCalledTimes(1);
274
- expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, {
275
- ratchetActiveSessionId: null,
276
- });
277
- expect(result).toEqual({
278
- type: 'TRIGGERED_FIXER',
279
- sessionId: 'new-fixer',
280
- fixerType: 'ci',
281
- promptSent: true,
238
+ expect(triggerSpy).not.toHaveBeenCalled();
239
+ expect(action).toEqual({
240
+ type: 'WAITING',
241
+ reason: 'PR state unchanged since last ratchet dispatch',
282
242
  });
283
243
  });
284
- it('notifies on a new CI run even when state is already CI_FAILED', () => {
285
- const shouldNotify = ratchetService.shouldNotifyActiveFixer(RatchetState.CI_FAILED, RatchetState.CI_FAILED, '4002', '4001');
286
- expect(shouldNotify).toBe(true);
244
+ it('treats closed PR as IDLE and does not dispatch', () => {
245
+ const state = ratchetService.determineRatchetState({
246
+ ciStatus: CIStatus.SUCCESS,
247
+ snapshotKey: '2026-01-02T00:00:00Z',
248
+ hasChangesRequested: false,
249
+ latestReviewActivityAtMs: null,
250
+ prState: 'CLOSED',
251
+ prNumber: 6,
252
+ });
253
+ expect(state).toBe(RatchetState.IDLE);
287
254
  });
288
- it('does not consume CI run id when ratcheting is disabled', async () => {
255
+ it('handles prompt delivery failure by clearing active session and stopping runner', async () => {
289
256
  const workspace = {
290
- id: 'ws-disabled',
291
- prUrl: 'https://github.com/example/repo/pull/77',
292
- prNumber: 77,
257
+ id: 'ws-prompt-fail',
258
+ prUrl: 'https://github.com/example/repo/pull/7',
259
+ prNumber: 7,
260
+ prState: 'OPEN',
261
+ prCiStatus: CIStatus.UNKNOWN,
293
262
  ratchetEnabled: true,
294
263
  ratchetState: RatchetState.IDLE,
295
264
  ratchetActiveSessionId: null,
296
265
  ratchetLastCiRunId: null,
297
- ratchetLastNotifiedState: null,
298
266
  prReviewLastCheckedAt: null,
299
267
  };
300
268
  vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
301
- ciStatus: CIStatus.FAILURE,
302
- mergeStateStatus: 'CLEAN',
303
- hasChangesRequested: false,
304
- hasNewReviewComments: false,
305
- failedChecks: [],
306
- ciRunId: '5001',
307
- reviews: [],
308
- comments: [],
309
- reviewComments: [],
310
- newReviewComments: [],
311
- newPRComments: [],
269
+ ciStatus: CIStatus.SUCCESS,
270
+ snapshotKey: '2026-01-02T00:00:00Z',
271
+ hasChangesRequested: true,
272
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
312
273
  prState: 'OPEN',
313
- prNumber: 77,
274
+ prNumber: 7,
314
275
  });
315
- vi.spyOn(ratchetService, 'determineRatchetState').mockReturnValue(RatchetState.CI_FAILED);
316
- vi.mocked(workspaceAccessor.findById).mockResolvedValue({ ratchetEnabled: false });
317
276
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
318
- await ratchetService.processWorkspace(workspace, {
319
- autoFixCi: true,
320
- autoFixConflicts: true,
321
- autoFixReviews: true,
322
- autoMerge: false,
323
- allowedReviewers: [],
277
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
278
+ vi.mocked(fixerSessionService.acquireAndDispatch).mockResolvedValue({
279
+ status: 'started',
280
+ sessionId: 'ratchet-session',
281
+ promptSent: false,
282
+ });
283
+ vi.mocked(sessionService.isSessionRunning).mockReturnValue(true);
284
+ vi.mocked(sessionService.stopClaudeSession).mockResolvedValue();
285
+ const result = await ratchetService.processWorkspace(workspace);
286
+ expect(result).toMatchObject({
287
+ action: { type: 'ERROR', error: 'Failed to deliver initial ratchet prompt' },
324
288
  });
325
- const updateCalls = vi.mocked(workspaceAccessor.update).mock.calls;
326
- const finalUpdatePayload = updateCalls[updateCalls.length - 1]?.[1];
327
- expect(finalUpdatePayload).not.toHaveProperty('ratchetLastCiRunId');
289
+ expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, {
290
+ ratchetActiveSessionId: null,
291
+ });
292
+ expect(sessionService.stopClaudeSession).toHaveBeenCalledWith('ratchet-session');
328
293
  });
329
- it('returns run id "0" when parsed from details url', () => {
330
- const signature = ratchetService.extractFailedCiSignature([
331
- {
332
- name: 'unit-tests',
333
- conclusion: 'FAILURE',
334
- detailsUrl: 'https://github.com/acme/repo/actions/runs/0',
335
- },
336
- {
337
- name: 'lint',
338
- conclusion: 'FAILURE',
339
- detailsUrl: 'https://github.com/acme/repo/actions/runs/42',
340
- },
341
- ]);
342
- expect(signature).toBe('lint:42|unit-tests:0');
294
+ it('does not dispatch on a clean PR with no new review activity', async () => {
295
+ const workspace = {
296
+ id: 'ws-clean',
297
+ prUrl: 'https://github.com/example/repo/pull/8',
298
+ prNumber: 8,
299
+ prState: 'OPEN',
300
+ prCiStatus: CIStatus.UNKNOWN,
301
+ ratchetEnabled: true,
302
+ ratchetState: RatchetState.READY,
303
+ ratchetActiveSessionId: null,
304
+ ratchetLastCiRunId: '2026-01-01T00:00:00Z',
305
+ prReviewLastCheckedAt: new Date('2026-01-02T00:00:00Z'),
306
+ };
307
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
308
+ const triggerSpy = vi.spyOn(ratchetService, 'triggerFixer');
309
+ const action = await ratchetService.evaluateAndDispatch(workspace, {
310
+ ciStatus: CIStatus.SUCCESS,
311
+ snapshotKey: '2026-01-03T00:00:00Z',
312
+ hasChangesRequested: false,
313
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
314
+ prState: 'OPEN',
315
+ prNumber: 8,
316
+ });
317
+ expect(triggerSpy).not.toHaveBeenCalled();
318
+ expect(action).toEqual({
319
+ type: 'WAITING',
320
+ reason: 'PR is clean (green CI and no new review activity)',
321
+ });
343
322
  });
344
- it('clears stale active session linkage when IDLE session has no client', async () => {
323
+ it('clears active ratchet session immediately when runtime is not running', async () => {
345
324
  const workspace = {
346
- id: 'ws-idle',
347
- prUrl: 'https://github.com/example/repo/pull/88',
348
- prNumber: 88,
325
+ id: 'ws-stale-active',
326
+ prUrl: 'https://github.com/example/repo/pull/9',
327
+ prNumber: 9,
328
+ prState: 'OPEN',
329
+ prCiStatus: CIStatus.UNKNOWN,
349
330
  ratchetEnabled: true,
350
331
  ratchetState: RatchetState.CI_FAILED,
351
- ratchetActiveSessionId: 'session-idle',
352
- ratchetLastCiRunId: 'sig-old',
353
- ratchetLastNotifiedState: RatchetState.CI_FAILED,
332
+ ratchetActiveSessionId: 'ratchet-session',
333
+ ratchetLastCiRunId: null,
354
334
  prReviewLastCheckedAt: null,
355
335
  };
356
336
  vi.mocked(claudeSessionAccessor.findById).mockResolvedValue({
357
- id: 'session-idle',
358
- status: SessionStatus.IDLE,
337
+ id: 'ratchet-session',
338
+ status: SessionStatus.RUNNING,
359
339
  });
360
- vi.mocked(sessionService.getClient).mockReturnValue(undefined);
340
+ vi.mocked(sessionService.isSessionRunning).mockReturnValue(false);
361
341
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
362
- const triggerFixerSpy = vi
363
- .spyOn(ratchetService, 'triggerFixer')
364
- .mockResolvedValue({
365
- type: 'TRIGGERED_FIXER',
366
- sessionId: 'session-idle',
367
- fixerType: 'ci',
368
- promptSent: true,
369
- });
370
- await ratchetService.executeRatchetAction(workspace, RatchetState.CI_FAILED, {
371
- ciStatus: CIStatus.FAILURE,
372
- mergeStateStatus: 'CLEAN',
373
- hasChangesRequested: false,
374
- hasNewReviewComments: false,
375
- failedChecks: [],
376
- ciRunId: 'sig-new',
377
- reviews: [],
378
- comments: [],
379
- reviewComments: [],
380
- newReviewComments: [],
381
- newPRComments: [],
382
- prState: 'OPEN',
383
- prNumber: 88,
384
- }, {
385
- autoFixCi: true,
386
- autoFixConflicts: true,
387
- autoFixReviews: true,
388
- autoMerge: false,
389
- allowedReviewers: [],
390
- });
391
- expect(triggerFixerSpy).toHaveBeenCalledTimes(1);
342
+ const action = await ratchetService.getActiveRatchetSession(workspace);
343
+ expect(action).toBeNull();
392
344
  expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, {
393
345
  ratchetActiveSessionId: null,
394
346
  });
395
347
  });
396
- });
397
- describe('Ratchet Comment Detection', () => {
398
- const lastCheckedAt = new Date('2024-01-01T12:00:00Z').getTime();
399
- describe('New comment detection', () => {
400
- it('should detect comments created after lastCheckedAt', () => {
401
- const comment = {
402
- id: '1',
403
- author: { login: 'reviewer1' },
404
- body: 'New comment',
405
- createdAt: '2024-01-01T13:00:00Z',
406
- updatedAt: '2024-01-01T13:00:00Z',
407
- url: 'https://github.com/test/pr/1',
408
- };
409
- const createdTime = new Date(comment.createdAt).getTime();
410
- const updatedTime = new Date(comment.updatedAt).getTime();
411
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
412
- expect(isNewOrEdited).toBe(true);
413
- });
414
- it('should not detect comments created before lastCheckedAt', () => {
415
- const comment = {
416
- id: '1',
417
- author: { login: 'reviewer1' },
418
- body: 'Old comment',
419
- createdAt: '2024-01-01T11:00:00Z',
420
- updatedAt: '2024-01-01T11:00:00Z',
421
- url: 'https://github.com/test/pr/1',
422
- };
423
- const createdTime = new Date(comment.createdAt).getTime();
424
- const updatedTime = new Date(comment.updatedAt).getTime();
425
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
426
- expect(isNewOrEdited).toBe(false);
427
- });
428
- });
429
- describe('Edited comment detection', () => {
430
- it('should detect comments edited after lastCheckedAt', () => {
431
- const comment = {
432
- id: '1',
433
- author: { login: 'reviewer1' },
434
- body: 'Edited comment',
435
- createdAt: '2024-01-01T11:00:00Z',
436
- updatedAt: '2024-01-01T13:00:00Z',
437
- url: 'https://github.com/test/pr/1',
438
- };
439
- const createdTime = new Date(comment.createdAt).getTime();
440
- const updatedTime = new Date(comment.updatedAt).getTime();
441
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
442
- expect(isNewOrEdited).toBe(true);
443
- });
444
- it('should detect comments with updatedAt exactly equal to lastCheckedAt as not new', () => {
445
- const comment = {
446
- id: '1',
447
- author: { login: 'reviewer1' },
448
- body: 'Comment at boundary',
449
- createdAt: '2024-01-01T11:00:00Z',
450
- updatedAt: '2024-01-01T12:00:00Z',
451
- url: 'https://github.com/test/pr/1',
452
- };
453
- const createdTime = new Date(comment.createdAt).getTime();
454
- const updatedTime = new Date(comment.updatedAt).getTime();
455
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
456
- expect(isNewOrEdited).toBe(false);
457
- });
458
- it('should not detect old unedited comments', () => {
459
- const comment = {
460
- id: '1',
461
- author: { login: 'reviewer1' },
462
- body: 'Old unedited comment',
463
- createdAt: '2024-01-01T10:00:00Z',
464
- updatedAt: '2024-01-01T10:00:00Z',
465
- url: 'https://github.com/test/pr/1',
466
- };
467
- const createdTime = new Date(comment.createdAt).getTime();
468
- const updatedTime = new Date(comment.updatedAt).getTime();
469
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
470
- expect(isNewOrEdited).toBe(false);
471
- });
472
- });
473
- describe('Reviewer filtering with edited comments', () => {
474
- it('should apply reviewer filter to edited comments', () => {
475
- const allowedReviewers = ['reviewer1', 'reviewer2'];
476
- const filterByReviewer = allowedReviewers.length > 0;
477
- const comments = [
478
- {
479
- id: '1',
480
- author: { login: 'reviewer1' },
481
- body: 'Allowed reviewer edit',
482
- createdAt: '2024-01-01T11:00:00Z',
483
- updatedAt: '2024-01-01T13:00:00Z',
484
- url: 'https://github.com/test/pr/1',
485
- },
486
- {
487
- id: '2',
488
- author: { login: 'reviewer3' },
489
- body: 'Disallowed reviewer edit',
490
- createdAt: '2024-01-01T11:00:00Z',
491
- updatedAt: '2024-01-01T13:00:00Z',
492
- url: 'https://github.com/test/pr/2',
493
- },
494
- ];
495
- const filteredComments = comments.filter((comment) => {
496
- const createdTime = new Date(comment.createdAt).getTime();
497
- const updatedTime = new Date(comment.updatedAt).getTime();
498
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
499
- const isAllowedReviewer = !filterByReviewer || allowedReviewers.includes(comment.author.login);
500
- return isNewOrEdited && isAllowedReviewer;
501
- });
502
- expect(filteredComments).toHaveLength(1);
503
- expect(filteredComments[0].author.login).toBe('reviewer1');
504
- });
348
+ it('ignores review activity authored by the authenticated user', () => {
349
+ const latestActivity = ratchetService.computeLatestReviewActivityAtMs({
350
+ reviews: [
351
+ { submittedAt: '2026-01-02T00:00:00Z', author: { login: 'ratchet-bot' } },
352
+ { submittedAt: '2026-01-01T00:00:00Z', author: { login: 'reviewer' } },
353
+ ],
354
+ comments: [{ updatedAt: '2026-01-02T01:00:00Z', author: { login: 'ratchet-bot' } }],
355
+ }, [{ updatedAt: '2026-01-01T02:00:00Z', author: { login: 'reviewer2' } }], 'ratchet-bot');
356
+ expect(latestActivity).toBe(new Date('2026-01-01T02:00:00Z').getTime());
505
357
  });
506
358
  });
507
359
  //# sourceMappingURL=ratchet.service.test.js.map