factory-factory 0.2.4 → 0.2.6

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 (437) hide show
  1. package/dist/client/assets/{_basePickBy-CCB8XrLV.js → _basePickBy-B1TXLukC.js} +1 -1
  2. package/dist/client/assets/{_baseUniq-CnpEKy-E.js → _baseUniq-C997CyNN.js} +1 -1
  3. package/dist/client/assets/{arc-BVt3NpFL.js → arc-Dj9rabaY.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-VXUJARFQ-DQXtRVG1.js → architectureDiagram-VXUJARFQ-CDb4dzGu.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-VD42YOAC-C_GKWlYL.js → blockDiagram-VD42YOAC-C0GqgSqb.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-YG6GDRKO-njABqJg1.js → c4Diagram-YG6GDRKO-DwI5UcIR.js} +1 -1
  7. package/dist/client/assets/channel-CUWT3YnL.js +1 -0
  8. package/dist/client/assets/{chunk-4BX2VUAB-DNMCzzfR.js → chunk-4BX2VUAB-B-xUvS2Q.js} +1 -1
  9. package/dist/client/assets/{chunk-55IACEB6-CajLRh0F.js → chunk-55IACEB6-DnfB7mO6.js} +1 -1
  10. package/dist/client/assets/{chunk-B4BG7PRW-BLSFZNLe.js → chunk-B4BG7PRW-C3mlyOKh.js} +1 -1
  11. package/dist/client/assets/{chunk-DI55MBZ5-DtQPQERJ.js → chunk-DI55MBZ5-CWrY69vI.js} +1 -1
  12. package/dist/client/assets/{chunk-FMBD7UC4-BhtFvMT5.js → chunk-FMBD7UC4-DqIuy8cX.js} +1 -1
  13. package/dist/client/assets/{chunk-QN33PNHL-DBHUU683.js → chunk-QN33PNHL-DaQ7aatm.js} +1 -1
  14. package/dist/client/assets/{chunk-QZHKN3VN-B1rA-SBe.js → chunk-QZHKN3VN-DIAhn72l.js} +1 -1
  15. package/dist/client/assets/{chunk-TZMSLE5B-BJUlg6Pf.js → chunk-TZMSLE5B-Vv2yea82.js} +1 -1
  16. package/dist/client/assets/classDiagram-2ON5EDUG-DIs9Yb8N.js +1 -0
  17. package/dist/client/assets/classDiagram-v2-WZHVMYZB-DIs9Yb8N.js +1 -0
  18. package/dist/client/assets/clone-DWe4ixM1.js +1 -0
  19. package/dist/client/assets/{cose-bilkent-S5V4N54A-CyjRxz-A.js → cose-bilkent-S5V4N54A-DCt0h-5b.js} +1 -1
  20. package/dist/client/assets/{dagre-6UL2VRFP-CURGwkx9.js → dagre-6UL2VRFP-Dx9zq5ck.js} +1 -1
  21. package/dist/client/assets/{diagram-PSM6KHXK-CaOIVsbD.js → diagram-PSM6KHXK-DWDEKgzo.js} +1 -1
  22. package/dist/client/assets/{diagram-QEK2KX5R-0zoz9z8_.js → diagram-QEK2KX5R-DWNDqpN2.js} +1 -1
  23. package/dist/client/assets/{diagram-S2PKOQOG-D6WyG3pJ.js → diagram-S2PKOQOG-DG0T0gs6.js} +1 -1
  24. package/dist/client/assets/{erDiagram-Q2GNP2WA-BwfTai9D.js → erDiagram-Q2GNP2WA-CqDgOfYs.js} +1 -1
  25. package/dist/client/assets/{flowDiagram-NV44I4VS-z6idjLXa.js → flowDiagram-NV44I4VS-V_QvSvWm.js} +1 -1
  26. package/dist/client/assets/{ganttDiagram-JELNMOA3-BLAK0GDW.js → ganttDiagram-JELNMOA3-BCPELgz0.js} +1 -1
  27. package/dist/client/assets/{gitGraphDiagram-NY62KEGX-DsC9TQow.js → gitGraphDiagram-NY62KEGX-D31RxlxC.js} +1 -1
  28. package/dist/client/assets/{graph-72LNxX96.js → graph-D1yJ-cNO.js} +1 -1
  29. package/dist/client/assets/index-CgaNRc7X.js +436 -0
  30. package/dist/client/assets/index-EHCwHZbd.css +1 -0
  31. package/dist/client/assets/{infoDiagram-WHAUD3N6-CInk5tUs.js → infoDiagram-WHAUD3N6-BIE2gcL7.js} +1 -1
  32. package/dist/client/assets/{journeyDiagram-XKPGCS4Q-nTrHP_HR.js → journeyDiagram-XKPGCS4Q-B5q1A61b.js} +1 -1
  33. package/dist/client/assets/{kanban-definition-3W4ZIXB7-xiF9MwcX.js → kanban-definition-3W4ZIXB7-D7FRiZXq.js} +1 -1
  34. package/dist/client/assets/{layout-BxYnyUX2.js → layout-X-mlIEi7.js} +1 -1
  35. package/dist/client/assets/{linear-BOpSSl-n.js → linear-CGpwoS11.js} +1 -1
  36. package/dist/client/assets/{mindmap-definition-VGOIOE7T-BxYEWuiO.js → mindmap-definition-VGOIOE7T-XMWWZCmt.js} +1 -1
  37. package/dist/client/assets/{pieDiagram-ADFJNKIX-DHQkQuZp.js → pieDiagram-ADFJNKIX-4wqJE3Xv.js} +1 -1
  38. package/dist/client/assets/{quadrantDiagram-AYHSOK5B--dbc4LsQ.js → quadrantDiagram-AYHSOK5B-ApAId2lG.js} +1 -1
  39. package/dist/client/assets/{requirementDiagram-UZGBJVZJ-Dr-h39fn.js → requirementDiagram-UZGBJVZJ-CNkajZw2.js} +1 -1
  40. package/dist/client/assets/{sankeyDiagram-TZEHDZUN-BwKHqT4R.js → sankeyDiagram-TZEHDZUN-BFLGpYF1.js} +1 -1
  41. package/dist/client/assets/{sequenceDiagram-WL72ISMW-DizdB6A9.js → sequenceDiagram-WL72ISMW-DB-mQnOQ.js} +1 -1
  42. package/dist/client/assets/{stateDiagram-FKZM4ZOC-C-J72Wtl.js → stateDiagram-FKZM4ZOC-7gdB28fK.js} +1 -1
  43. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-6YMfUfib.js +1 -0
  44. package/dist/client/assets/{terminal-instance-qIJHj7KS.js → terminal-instance-BHOJvhaZ.js} +1 -1
  45. package/dist/client/assets/{timeline-definition-IT6M3QCI-DAGw_FKd.js → timeline-definition-IT6M3QCI-Cc-mzevR.js} +1 -1
  46. package/dist/client/assets/{treemap-KMMF4GRG-Bo4f4Al3.js → treemap-KMMF4GRG-BSCzOTjP.js} +1 -1
  47. package/dist/client/assets/{xychartDiagram-PRI3JC2R-CAJ9NVad.js → xychartDiagram-PRI3JC2R-DA09PDY1.js} +1 -1
  48. package/dist/client/index.html +2 -2
  49. package/dist/prisma/generated/internal/class.js +2 -2
  50. package/dist/prisma/generated/internal/class.js.map +1 -1
  51. package/dist/prisma/generated/internal/prismaNamespace.d.ts +1 -4
  52. package/dist/prisma/generated/internal/prismaNamespace.d.ts.map +1 -1
  53. package/dist/prisma/generated/internal/prismaNamespace.js +1 -4
  54. package/dist/prisma/generated/internal/prismaNamespace.js.map +1 -1
  55. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts +1 -4
  56. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  57. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js +1 -4
  58. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  59. package/dist/prisma/generated/models/ClaudeSession.d.ts +36 -1
  60. package/dist/prisma/generated/models/ClaudeSession.d.ts.map +1 -1
  61. package/dist/prisma/generated/models/UserSettings.d.ts +1 -107
  62. package/dist/prisma/generated/models/UserSettings.d.ts.map +1 -1
  63. package/dist/prompts/ratchet/dispatch.md +31 -0
  64. package/dist/src/backend/app-context.d.ts +4 -4
  65. package/dist/src/backend/app-context.d.ts.map +1 -1
  66. package/dist/src/backend/app-context.js +4 -4
  67. package/dist/src/backend/app-context.js.map +1 -1
  68. package/dist/src/backend/claude/protocol.d.ts +2 -2
  69. package/dist/src/backend/claude/protocol.d.ts.map +1 -1
  70. package/dist/src/backend/claude/protocol.js +13 -4
  71. package/dist/src/backend/claude/protocol.js.map +1 -1
  72. package/dist/src/backend/claude/protocol.test.js +18 -0
  73. package/dist/src/backend/claude/protocol.test.js.map +1 -1
  74. package/dist/src/backend/claude/session.d.ts +6 -10
  75. package/dist/src/backend/claude/session.d.ts.map +1 -1
  76. package/dist/src/backend/claude/session.js +169 -38
  77. package/dist/src/backend/claude/session.js.map +1 -1
  78. package/dist/src/backend/claude/session.test.js +259 -20
  79. package/dist/src/backend/claude/session.test.js.map +1 -1
  80. package/dist/src/backend/claude/types.d.ts +3 -0
  81. package/dist/src/backend/claude/types.d.ts.map +1 -1
  82. package/dist/src/backend/claude/types.js +5 -0
  83. package/dist/src/backend/claude/types.js.map +1 -1
  84. package/dist/src/backend/prompts/markdown-loader.d.ts +22 -0
  85. package/dist/src/backend/prompts/markdown-loader.d.ts.map +1 -0
  86. package/dist/src/backend/prompts/markdown-loader.js +77 -0
  87. package/dist/src/backend/prompts/markdown-loader.js.map +1 -0
  88. package/dist/src/backend/prompts/markdown-loader.test.d.ts +2 -0
  89. package/dist/src/backend/prompts/markdown-loader.test.d.ts.map +1 -0
  90. package/dist/src/backend/prompts/markdown-loader.test.js +190 -0
  91. package/dist/src/backend/prompts/markdown-loader.test.js.map +1 -0
  92. package/dist/src/backend/prompts/quick-actions.d.ts.map +1 -1
  93. package/dist/src/backend/prompts/quick-actions.js +34 -92
  94. package/dist/src/backend/prompts/quick-actions.js.map +1 -1
  95. package/dist/src/backend/prompts/ratchet-dispatch.d.ts +3 -0
  96. package/dist/src/backend/prompts/ratchet-dispatch.d.ts.map +1 -0
  97. package/dist/src/backend/prompts/ratchet-dispatch.js +53 -0
  98. package/dist/src/backend/prompts/ratchet-dispatch.js.map +1 -0
  99. package/dist/src/backend/prompts/ratchet-dispatch.test.d.ts +2 -0
  100. package/dist/src/backend/prompts/ratchet-dispatch.test.d.ts.map +1 -0
  101. package/dist/src/backend/prompts/ratchet-dispatch.test.js +34 -0
  102. package/dist/src/backend/prompts/ratchet-dispatch.test.js.map +1 -0
  103. package/dist/src/backend/prompts/workflows.d.ts.map +1 -1
  104. package/dist/src/backend/prompts/workflows.js +23 -76
  105. package/dist/src/backend/prompts/workflows.js.map +1 -1
  106. package/dist/src/backend/resource_accessors/claude-session.accessor.d.ts +2 -0
  107. package/dist/src/backend/resource_accessors/claude-session.accessor.d.ts.map +1 -1
  108. package/dist/src/backend/resource_accessors/claude-session.accessor.js +1 -0
  109. package/dist/src/backend/resource_accessors/claude-session.accessor.js.map +1 -1
  110. package/dist/src/backend/resource_accessors/user-settings.accessor.d.ts +0 -4
  111. package/dist/src/backend/resource_accessors/user-settings.accessor.d.ts.map +1 -1
  112. package/dist/src/backend/resource_accessors/user-settings.accessor.js.map +1 -1
  113. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts +4 -3
  114. package/dist/src/backend/resource_accessors/workspace.accessor.d.ts.map +1 -1
  115. package/dist/src/backend/resource_accessors/workspace.accessor.js +4 -2
  116. package/dist/src/backend/resource_accessors/workspace.accessor.js.map +1 -1
  117. package/dist/src/backend/routers/websocket/chat.handler.d.ts.map +1 -1
  118. package/dist/src/backend/routers/websocket/chat.handler.js +1 -32
  119. package/dist/src/backend/routers/websocket/chat.handler.js.map +1 -1
  120. package/dist/src/backend/routers/websocket/dev-logs.handler.d.ts.map +1 -1
  121. package/dist/src/backend/routers/websocket/dev-logs.handler.js +1 -2
  122. package/dist/src/backend/routers/websocket/dev-logs.handler.js.map +1 -1
  123. package/dist/src/backend/routers/websocket/terminal.handler.d.ts.map +1 -1
  124. package/dist/src/backend/routers/websocket/terminal.handler.js +3 -4
  125. package/dist/src/backend/routers/websocket/terminal.handler.js.map +1 -1
  126. package/dist/src/backend/services/chat-event-forwarder.service.d.ts +11 -2
  127. package/dist/src/backend/services/chat-event-forwarder.service.d.ts.map +1 -1
  128. package/dist/src/backend/services/chat-event-forwarder.service.js +254 -138
  129. package/dist/src/backend/services/chat-event-forwarder.service.js.map +1 -1
  130. package/dist/src/backend/services/chat-event-forwarder.service.test.d.ts +2 -0
  131. package/dist/src/backend/services/chat-event-forwarder.service.test.d.ts.map +1 -0
  132. package/dist/src/backend/services/chat-event-forwarder.service.test.js +133 -0
  133. package/dist/src/backend/services/chat-event-forwarder.service.test.js.map +1 -0
  134. package/dist/src/backend/services/chat-message-handlers/handlers/list-sessions.handler.d.ts +2 -1
  135. package/dist/src/backend/services/chat-message-handlers/handlers/list-sessions.handler.d.ts.map +1 -1
  136. package/dist/src/backend/services/chat-message-handlers/handlers/list-sessions.handler.js.map +1 -1
  137. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.d.ts +2 -1
  138. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.d.ts.map +1 -1
  139. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.js +24 -89
  140. package/dist/src/backend/services/chat-message-handlers/handlers/load-session.handler.js.map +1 -1
  141. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.d.ts +2 -1
  142. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.d.ts.map +1 -1
  143. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.js +5 -6
  144. package/dist/src/backend/services/chat-message-handlers/handlers/permission-response.handler.js.map +1 -1
  145. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.d.ts +2 -1
  146. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.d.ts.map +1 -1
  147. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.js +5 -6
  148. package/dist/src/backend/services/chat-message-handlers/handlers/question-response.handler.js.map +1 -1
  149. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.d.ts +2 -1
  150. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.d.ts.map +1 -1
  151. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.js +32 -14
  152. package/dist/src/backend/services/chat-message-handlers/handlers/queue-message.handler.js.map +1 -1
  153. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.d.ts +2 -1
  154. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.d.ts.map +1 -1
  155. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.js +8 -6
  156. package/dist/src/backend/services/chat-message-handlers/handlers/remove-queued-message.handler.js.map +1 -1
  157. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.d.ts +2 -1
  158. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.d.ts.map +1 -1
  159. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.js +8 -9
  160. package/dist/src/backend/services/chat-message-handlers/handlers/rewind-files.handler.js.map +1 -1
  161. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.d.ts +2 -1
  162. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.d.ts.map +1 -1
  163. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.js +3 -5
  164. package/dist/src/backend/services/chat-message-handlers/handlers/set-model.handler.js.map +1 -1
  165. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.d.ts +2 -1
  166. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.d.ts.map +1 -1
  167. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.js +3 -5
  168. package/dist/src/backend/services/chat-message-handlers/handlers/set-thinking-budget.handler.js.map +1 -1
  169. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.d.ts +2 -1
  170. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.d.ts.map +1 -1
  171. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.js +4 -2
  172. package/dist/src/backend/services/chat-message-handlers/handlers/start.handler.js.map +1 -1
  173. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.d.ts +2 -1
  174. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.d.ts.map +1 -1
  175. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.js +7 -2
  176. package/dist/src/backend/services/chat-message-handlers/handlers/stop.handler.js.map +1 -1
  177. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.d.ts +2 -1
  178. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.d.ts.map +1 -1
  179. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.js +1 -2
  180. package/dist/src/backend/services/chat-message-handlers/handlers/user-input.handler.js.map +1 -1
  181. package/dist/src/backend/services/chat-message-handlers/interactive-response.d.ts +1 -2
  182. package/dist/src/backend/services/chat-message-handlers/interactive-response.d.ts.map +1 -1
  183. package/dist/src/backend/services/chat-message-handlers/interactive-response.js +31 -23
  184. package/dist/src/backend/services/chat-message-handlers/interactive-response.js.map +1 -1
  185. package/dist/src/backend/services/chat-message-handlers/interactive-response.test.d.ts +2 -0
  186. package/dist/src/backend/services/chat-message-handlers/interactive-response.test.d.ts.map +1 -0
  187. package/dist/src/backend/services/chat-message-handlers/interactive-response.test.js +83 -0
  188. package/dist/src/backend/services/chat-message-handlers/interactive-response.test.js.map +1 -0
  189. package/dist/src/backend/services/chat-message-handlers/registry.d.ts +5 -1
  190. package/dist/src/backend/services/chat-message-handlers/registry.d.ts.map +1 -1
  191. package/dist/src/backend/services/chat-message-handlers/registry.js +0 -4
  192. package/dist/src/backend/services/chat-message-handlers/registry.js.map +1 -1
  193. package/dist/src/backend/services/chat-message-handlers/utils.d.ts +1 -2
  194. package/dist/src/backend/services/chat-message-handlers/utils.d.ts.map +1 -1
  195. package/dist/src/backend/services/chat-message-handlers/utils.js +1 -10
  196. package/dist/src/backend/services/chat-message-handlers/utils.js.map +1 -1
  197. package/dist/src/backend/services/chat-message-handlers.service.d.ts.map +1 -1
  198. package/dist/src/backend/services/chat-message-handlers.service.js +35 -17
  199. package/dist/src/backend/services/chat-message-handlers.service.js.map +1 -1
  200. package/dist/src/backend/services/data-backup.service.d.ts +2 -622
  201. package/dist/src/backend/services/data-backup.service.d.ts.map +1 -1
  202. package/dist/src/backend/services/data-backup.service.js +6 -162
  203. package/dist/src/backend/services/data-backup.service.js.map +1 -1
  204. package/dist/src/backend/services/data-backup.service.test.js +0 -18
  205. package/dist/src/backend/services/data-backup.service.test.js.map +1 -1
  206. package/dist/src/backend/services/file-lock.service.d.ts.map +1 -1
  207. package/dist/src/backend/services/file-lock.service.js +3 -5
  208. package/dist/src/backend/services/file-lock.service.js.map +1 -1
  209. package/dist/src/backend/services/file-lock.service.test.js +55 -0
  210. package/dist/src/backend/services/file-lock.service.test.js.map +1 -1
  211. package/dist/src/backend/services/fixer-session.service.d.ts.map +1 -1
  212. package/dist/src/backend/services/fixer-session.service.js +9 -0
  213. package/dist/src/backend/services/fixer-session.service.js.map +1 -1
  214. package/dist/src/backend/services/fixer-session.service.test.js +3 -0
  215. package/dist/src/backend/services/fixer-session.service.test.js.map +1 -1
  216. package/dist/src/backend/services/git-ops.service.d.ts.map +1 -1
  217. package/dist/src/backend/services/git-ops.service.js +1 -1
  218. package/dist/src/backend/services/git-ops.service.js.map +1 -1
  219. package/dist/src/backend/services/github-cli.service.d.ts.map +1 -1
  220. package/dist/src/backend/services/github-cli.service.js +118 -19
  221. package/dist/src/backend/services/github-cli.service.js.map +1 -1
  222. package/dist/src/backend/services/github-cli.service.test.js +126 -0
  223. package/dist/src/backend/services/github-cli.service.test.js.map +1 -1
  224. package/dist/src/backend/services/index.d.ts +1 -2
  225. package/dist/src/backend/services/index.d.ts.map +1 -1
  226. package/dist/src/backend/services/index.js +1 -2
  227. package/dist/src/backend/services/index.js.map +1 -1
  228. package/dist/src/backend/services/ratchet.service.d.ts +23 -69
  229. package/dist/src/backend/services/ratchet.service.d.ts.map +1 -1
  230. package/dist/src/backend/services/ratchet.service.js +414 -419
  231. package/dist/src/backend/services/ratchet.service.js.map +1 -1
  232. package/dist/src/backend/services/ratchet.service.test.js +319 -415
  233. package/dist/src/backend/services/ratchet.service.test.js.map +1 -1
  234. package/dist/src/backend/services/run-script-state-machine.service.d.ts +27 -0
  235. package/dist/src/backend/services/run-script-state-machine.service.d.ts.map +1 -0
  236. package/dist/src/backend/services/run-script-state-machine.service.js +149 -0
  237. package/dist/src/backend/services/run-script-state-machine.service.js.map +1 -0
  238. package/dist/src/backend/services/run-script-state-machine.service.test.d.ts +2 -0
  239. package/dist/src/backend/services/run-script-state-machine.service.test.d.ts.map +1 -0
  240. package/dist/src/backend/services/run-script-state-machine.service.test.js +453 -0
  241. package/dist/src/backend/services/run-script-state-machine.service.test.js.map +1 -0
  242. package/dist/src/backend/services/run-script.service.d.ts +0 -7
  243. package/dist/src/backend/services/run-script.service.d.ts.map +1 -1
  244. package/dist/src/backend/services/run-script.service.js +145 -76
  245. package/dist/src/backend/services/run-script.service.js.map +1 -1
  246. package/dist/src/backend/services/session-store.service.d.ts +73 -0
  247. package/dist/src/backend/services/session-store.service.d.ts.map +1 -0
  248. package/dist/src/backend/services/session-store.service.js +732 -0
  249. package/dist/src/backend/services/session-store.service.js.map +1 -0
  250. package/dist/src/backend/services/session-store.service.test.d.ts +2 -0
  251. package/dist/src/backend/services/session-store.service.test.d.ts.map +1 -0
  252. package/dist/src/backend/services/session-store.service.test.js +738 -0
  253. package/dist/src/backend/services/session-store.service.test.js.map +1 -0
  254. package/dist/src/backend/services/session.prompt-builder.d.ts +2 -0
  255. package/dist/src/backend/services/session.prompt-builder.d.ts.map +1 -1
  256. package/dist/src/backend/services/session.prompt-builder.js +3 -1
  257. package/dist/src/backend/services/session.prompt-builder.js.map +1 -1
  258. package/dist/src/backend/services/session.prompt-builder.test.js +66 -1
  259. package/dist/src/backend/services/session.prompt-builder.test.js.map +1 -1
  260. package/dist/src/backend/services/session.repository.d.ts +3 -2
  261. package/dist/src/backend/services/session.repository.d.ts.map +1 -1
  262. package/dist/src/backend/services/session.repository.js +14 -0
  263. package/dist/src/backend/services/session.repository.js.map +1 -1
  264. package/dist/src/backend/services/session.repository.test.d.ts +2 -0
  265. package/dist/src/backend/services/session.repository.test.d.ts.map +1 -0
  266. package/dist/src/backend/services/session.repository.test.js +60 -0
  267. package/dist/src/backend/services/session.repository.test.js.map +1 -0
  268. package/dist/src/backend/services/session.service.d.ts.map +1 -1
  269. package/dist/src/backend/services/session.service.js +6 -1
  270. package/dist/src/backend/services/session.service.js.map +1 -1
  271. package/dist/src/backend/services/session.service.test.js +3 -1
  272. package/dist/src/backend/services/session.service.test.js.map +1 -1
  273. package/dist/src/backend/services/workspace-activity.service.d.ts.map +1 -1
  274. package/dist/src/backend/services/workspace-activity.service.js +2 -1
  275. package/dist/src/backend/services/workspace-activity.service.js.map +1 -1
  276. package/dist/src/backend/services/workspace-activity.service.test.d.ts +2 -0
  277. package/dist/src/backend/services/workspace-activity.service.test.d.ts.map +1 -0
  278. package/dist/src/backend/services/workspace-activity.service.test.js +32 -0
  279. package/dist/src/backend/services/workspace-activity.service.test.js.map +1 -0
  280. package/dist/src/backend/services/workspace-creation.service.d.ts.map +1 -1
  281. package/dist/src/backend/services/workspace-creation.service.js +6 -0
  282. package/dist/src/backend/services/workspace-creation.service.js.map +1 -1
  283. package/dist/src/backend/services/workspace-creation.service.test.js +2 -8
  284. package/dist/src/backend/services/workspace-creation.service.test.js.map +1 -1
  285. package/dist/src/backend/services/workspace-query.service.d.ts +4 -0
  286. package/dist/src/backend/services/workspace-query.service.d.ts.map +1 -1
  287. package/dist/src/backend/services/workspace-query.service.js +32 -0
  288. package/dist/src/backend/services/workspace-query.service.js.map +1 -1
  289. package/dist/src/backend/services/worktree-lifecycle.service.d.ts.map +1 -1
  290. package/dist/src/backend/services/worktree-lifecycle.service.js +141 -21
  291. package/dist/src/backend/services/worktree-lifecycle.service.js.map +1 -1
  292. package/dist/src/backend/services/worktree-lifecycle.service.test.js +48 -1
  293. package/dist/src/backend/services/worktree-lifecycle.service.test.js.map +1 -1
  294. package/dist/src/backend/trpc/admin.trpc.d.ts +22 -19
  295. package/dist/src/backend/trpc/admin.trpc.d.ts.map +1 -1
  296. package/dist/src/backend/trpc/admin.trpc.js +2 -1
  297. package/dist/src/backend/trpc/admin.trpc.js.map +1 -1
  298. package/dist/src/backend/trpc/index.d.ts +48 -41
  299. package/dist/src/backend/trpc/index.d.ts.map +1 -1
  300. package/dist/src/backend/trpc/project.trpc.d.ts +9 -0
  301. package/dist/src/backend/trpc/project.trpc.d.ts.map +1 -1
  302. package/dist/src/backend/trpc/project.trpc.js +12 -0
  303. package/dist/src/backend/trpc/project.trpc.js.map +1 -1
  304. package/dist/src/backend/trpc/project.trpc.test.d.ts +2 -0
  305. package/dist/src/backend/trpc/project.trpc.test.d.ts.map +1 -0
  306. package/dist/src/backend/trpc/project.trpc.test.js +73 -0
  307. package/dist/src/backend/trpc/project.trpc.test.js.map +1 -0
  308. package/dist/src/backend/trpc/session.trpc.d.ts +10 -3
  309. package/dist/src/backend/trpc/session.trpc.d.ts.map +1 -1
  310. package/dist/src/backend/trpc/session.trpc.js +12 -3
  311. package/dist/src/backend/trpc/session.trpc.js.map +1 -1
  312. package/dist/src/backend/trpc/user-settings.trpc.d.ts +0 -12
  313. package/dist/src/backend/trpc/user-settings.trpc.d.ts.map +1 -1
  314. package/dist/src/backend/trpc/user-settings.trpc.js +1 -13
  315. package/dist/src/backend/trpc/user-settings.trpc.js.map +1 -1
  316. package/dist/src/backend/trpc/workspace/init.trpc.d.ts +1 -0
  317. package/dist/src/backend/trpc/workspace/init.trpc.d.ts.map +1 -1
  318. package/dist/src/backend/trpc/workspace/run-script.trpc.d.ts +0 -7
  319. package/dist/src/backend/trpc/workspace/run-script.trpc.d.ts.map +1 -1
  320. package/dist/src/backend/trpc/workspace/workspace-helpers.d.ts +1 -0
  321. package/dist/src/backend/trpc/workspace/workspace-helpers.d.ts.map +1 -1
  322. package/dist/src/backend/trpc/workspace.trpc.d.ts +7 -7
  323. package/dist/src/backend/trpc/workspace.trpc.d.ts.map +1 -1
  324. package/dist/src/backend/trpc/workspace.trpc.js +12 -1
  325. package/dist/src/backend/trpc/workspace.trpc.js.map +1 -1
  326. package/dist/src/shared/ci-status.d.ts +9 -0
  327. package/dist/src/shared/ci-status.d.ts.map +1 -0
  328. package/dist/src/shared/ci-status.js +44 -0
  329. package/dist/src/shared/ci-status.js.map +1 -0
  330. package/dist/src/shared/claude/index.d.ts +1 -0
  331. package/dist/src/shared/claude/index.d.ts.map +1 -1
  332. package/dist/src/shared/claude/index.js +1 -0
  333. package/dist/src/shared/claude/index.js.map +1 -1
  334. package/dist/src/shared/claude/protocol.d.ts +203 -9
  335. package/dist/src/shared/claude/protocol.d.ts.map +1 -1
  336. package/dist/src/shared/claude/protocol.js +182 -0
  337. package/dist/src/shared/claude/protocol.js.map +1 -1
  338. package/dist/src/shared/pending-request-types.d.ts +1 -0
  339. package/dist/src/shared/pending-request-types.d.ts.map +1 -1
  340. package/dist/src/shared/pending-request-types.js +3 -0
  341. package/dist/src/shared/pending-request-types.js.map +1 -1
  342. package/dist/src/shared/schemas/export-data.schema.d.ts +870 -0
  343. package/dist/src/shared/schemas/export-data.schema.d.ts.map +1 -0
  344. package/dist/src/shared/schemas/export-data.schema.js +168 -0
  345. package/dist/src/shared/schemas/export-data.schema.js.map +1 -0
  346. package/dist/src/shared/schemas/persisted-stores.schema.d.ts +19 -0
  347. package/dist/src/shared/schemas/persisted-stores.schema.d.ts.map +1 -0
  348. package/dist/src/shared/schemas/persisted-stores.schema.js +17 -0
  349. package/dist/src/shared/schemas/persisted-stores.schema.js.map +1 -0
  350. package/dist/src/shared/session-runtime.d.ts +17 -0
  351. package/dist/src/shared/session-runtime.d.ts.map +1 -0
  352. package/dist/src/shared/session-runtime.js +9 -0
  353. package/dist/src/shared/session-runtime.js.map +1 -0
  354. package/dist/src/shared/websocket/chat-message.schema.d.ts +18 -15
  355. package/dist/src/shared/websocket/chat-message.schema.d.ts.map +1 -1
  356. package/dist/src/shared/websocket/chat-message.schema.js +4 -3
  357. package/dist/src/shared/websocket/chat-message.schema.js.map +1 -1
  358. package/dist/src/shared/websocket/index.d.ts +1 -1
  359. package/dist/src/shared/websocket/index.d.ts.map +1 -1
  360. package/dist/src/shared/websocket/index.js.map +1 -1
  361. package/dist/src/shared/workspace-sidebar-status.d.ts +20 -0
  362. package/dist/src/shared/workspace-sidebar-status.d.ts.map +1 -0
  363. package/dist/src/shared/workspace-sidebar-status.js +73 -0
  364. package/dist/src/shared/workspace-sidebar-status.js.map +1 -0
  365. package/dist/tsconfig.backend.tsbuildinfo +1 -1
  366. package/package.json +2 -2
  367. package/prisma/migrations/20260208000000_remove_unused_ratchet_settings/migration.sql +12 -0
  368. package/prisma/migrations/20260208153000_add_claude_project_path/migration.sql +2 -0
  369. package/prisma/schema.prisma +1 -4
  370. package/prompts/ratchet/dispatch.md +31 -0
  371. package/dist/client/assets/channel-DXVcwOT7.js +0 -1
  372. package/dist/client/assets/classDiagram-2ON5EDUG-DSXhUv2N.js +0 -1
  373. package/dist/client/assets/classDiagram-v2-WZHVMYZB-DSXhUv2N.js +0 -1
  374. package/dist/client/assets/clone-Cel7XaEC.js +0 -1
  375. package/dist/client/assets/index-C0E1P_GL.js +0 -399
  376. package/dist/client/assets/index-DI1BSYUN.css +0 -1
  377. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-waz9l7Vx.js +0 -1
  378. package/dist/src/backend/services/chat-message-handlers/handlers/get-history.handler.d.ts +0 -3
  379. package/dist/src/backend/services/chat-message-handlers/handlers/get-history.handler.d.ts.map +0 -1
  380. package/dist/src/backend/services/chat-message-handlers/handlers/get-history.handler.js +0 -16
  381. package/dist/src/backend/services/chat-message-handlers/handlers/get-history.handler.js.map +0 -1
  382. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.d.ts +0 -3
  383. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.d.ts.map +0 -1
  384. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.js +0 -13
  385. package/dist/src/backend/services/chat-message-handlers/handlers/get-queue.handler.js.map +0 -1
  386. package/dist/src/backend/services/chat-message-handlers/handlers/handlers.test.d.ts +0 -2
  387. package/dist/src/backend/services/chat-message-handlers/handlers/handlers.test.d.ts.map +0 -1
  388. package/dist/src/backend/services/chat-message-handlers/handlers/handlers.test.js +0 -512
  389. package/dist/src/backend/services/chat-message-handlers/handlers/handlers.test.js.map +0 -1
  390. package/dist/src/backend/services/chat-transport-adapter.service.d.ts +0 -10
  391. package/dist/src/backend/services/chat-transport-adapter.service.d.ts.map +0 -1
  392. package/dist/src/backend/services/chat-transport-adapter.service.js +0 -46
  393. package/dist/src/backend/services/chat-transport-adapter.service.js.map +0 -1
  394. package/dist/src/backend/services/chat-transport-adapter.service.test.d.ts +0 -2
  395. package/dist/src/backend/services/chat-transport-adapter.service.test.d.ts.map +0 -1
  396. package/dist/src/backend/services/chat-transport-adapter.service.test.js +0 -67
  397. package/dist/src/backend/services/chat-transport-adapter.service.test.js.map +0 -1
  398. package/dist/src/backend/services/event-compression.service.d.ts +0 -37
  399. package/dist/src/backend/services/event-compression.service.d.ts.map +0 -1
  400. package/dist/src/backend/services/event-compression.service.js +0 -235
  401. package/dist/src/backend/services/event-compression.service.js.map +0 -1
  402. package/dist/src/backend/services/event-compression.service.test.d.ts +0 -2
  403. package/dist/src/backend/services/event-compression.service.test.d.ts.map +0 -1
  404. package/dist/src/backend/services/event-compression.service.test.js +0 -336
  405. package/dist/src/backend/services/event-compression.service.test.js.map +0 -1
  406. package/dist/src/backend/services/message-event-store.d.ts +0 -12
  407. package/dist/src/backend/services/message-event-store.d.ts.map +0 -1
  408. package/dist/src/backend/services/message-event-store.js +0 -21
  409. package/dist/src/backend/services/message-event-store.js.map +0 -1
  410. package/dist/src/backend/services/message-event-store.test.d.ts +0 -2
  411. package/dist/src/backend/services/message-event-store.test.d.ts.map +0 -1
  412. package/dist/src/backend/services/message-event-store.test.js +0 -32
  413. package/dist/src/backend/services/message-event-store.test.js.map +0 -1
  414. package/dist/src/backend/services/message-queue.service.d.ts +0 -21
  415. package/dist/src/backend/services/message-queue.service.d.ts.map +0 -1
  416. package/dist/src/backend/services/message-queue.service.js +0 -105
  417. package/dist/src/backend/services/message-queue.service.js.map +0 -1
  418. package/dist/src/backend/services/message-queue.service.test.d.ts +0 -2
  419. package/dist/src/backend/services/message-queue.service.test.d.ts.map +0 -1
  420. package/dist/src/backend/services/message-queue.service.test.js +0 -278
  421. package/dist/src/backend/services/message-queue.service.test.js.map +0 -1
  422. package/dist/src/backend/services/message-state-machine.d.ts +0 -37
  423. package/dist/src/backend/services/message-state-machine.d.ts.map +0 -1
  424. package/dist/src/backend/services/message-state-machine.js +0 -262
  425. package/dist/src/backend/services/message-state-machine.js.map +0 -1
  426. package/dist/src/backend/services/message-state-machine.test.d.ts +0 -2
  427. package/dist/src/backend/services/message-state-machine.test.d.ts.map +0 -1
  428. package/dist/src/backend/services/message-state-machine.test.js +0 -67
  429. package/dist/src/backend/services/message-state-machine.test.js.map +0 -1
  430. package/dist/src/backend/services/message-state.service.d.ts +0 -79
  431. package/dist/src/backend/services/message-state.service.d.ts.map +0 -1
  432. package/dist/src/backend/services/message-state.service.js +0 -285
  433. package/dist/src/backend/services/message-state.service.js.map +0 -1
  434. package/dist/src/backend/services/message-state.service.test.d.ts +0 -2
  435. package/dist/src/backend/services/message-state.service.test.d.ts.map +0 -1
  436. package/dist/src/backend/services/message-state.service.test.js +0 -550
  437. package/dist/src/backend/services/message-state.service.test.js.map +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,26 @@ 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
+ isSessionWorking: vi.fn(),
34
+ stopClaudeSession: vi.fn(),
41
35
  },
42
36
  }));
43
- vi.mock('./message-state.service', () => ({
44
- messageStateService: {
45
- getRecentUserAndAgentMessages: vi.fn(),
37
+ vi.mock('./session-store.service', () => ({
38
+ sessionStoreService: {
46
39
  injectCommittedUserMessage: vi.fn(),
47
40
  },
48
41
  }));
49
- vi.mock('./config.service', () => ({
50
- configService: {
51
- getMaxSessionsPerWorkspace: vi.fn(() => 5),
52
- },
53
- }));
54
42
  vi.mock('./logger.service', () => ({
55
43
  createLogger: () => ({
56
44
  debug: vi.fn(),
@@ -60,525 +48,441 @@ vi.mock('./logger.service', () => ({
60
48
  }),
61
49
  }));
62
50
  import { claudeSessionAccessor } from '../resource_accessors/claude-session.accessor.js';
63
- import { userSettingsAccessor } from '../resource_accessors/user-settings.accessor.js';
64
51
  import { workspaceAccessor } from '../resource_accessors/workspace.accessor.js';
52
+ import { fixerSessionService } from './fixer-session.service.js';
53
+ import { githubCLIService } from './github-cli.service.js';
65
54
  import { ratchetService } from './ratchet.service.js';
66
55
  import { sessionService } from './session.service.js';
67
- describe('ratchet service', () => {
56
+ describe('ratchet service (state-change + idle dispatch)', () => {
68
57
  beforeEach(() => {
69
58
  vi.clearAllMocks();
70
59
  ratchetService.isShuttingDown = false;
60
+ vi.mocked(githubCLIService.getAuthenticatedUsername).mockResolvedValue(null);
61
+ vi.mocked(sessionService.isSessionWorking).mockReturnValue(false);
71
62
  });
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
- ratchetAutoFixReviews: true,
77
- ratchetAutoMerge: false,
78
- ratchetAllowedReviewers: null,
79
- });
63
+ it('checks workspaces and processes each', async () => {
80
64
  vi.mocked(workspaceAccessor.findWithPRsForRatchet).mockResolvedValue([
81
65
  {
82
66
  id: 'ws-1',
83
67
  prUrl: 'https://github.com/example/repo/pull/1',
84
68
  prNumber: 1,
69
+ prState: 'OPEN',
70
+ prCiStatus: CIStatus.UNKNOWN,
85
71
  ratchetEnabled: true,
86
72
  ratchetState: RatchetState.IDLE,
87
73
  ratchetActiveSessionId: null,
88
74
  ratchetLastCiRunId: null,
89
- ratchetLastNotifiedState: null,
90
75
  prReviewLastCheckedAt: null,
91
76
  },
92
77
  ]);
93
- const processWorkspaceSpy = vi
94
- .spyOn(ratchetService, 'processWorkspace')
95
- .mockResolvedValue({
78
+ vi.spyOn(ratchetService, 'processWorkspace').mockResolvedValue({
96
79
  workspaceId: 'ws-1',
97
80
  previousState: RatchetState.IDLE,
98
81
  newState: RatchetState.IDLE,
99
82
  action: { type: 'WAITING', reason: 'noop' },
100
83
  });
101
84
  const result = await ratchetService.checkAllWorkspaces();
102
- expect(processWorkspaceSpy).toHaveBeenCalledTimes(1);
103
85
  expect(result.checked).toBe(1);
104
86
  });
105
- it('does not execute ratchet actions when workspace ratcheting is off', async () => {
87
+ it('does not dispatch when workspace ratcheting is disabled', async () => {
106
88
  const workspace = {
107
- id: 'ws-2',
89
+ id: 'ws-disabled',
108
90
  prUrl: 'https://github.com/example/repo/pull/2',
109
91
  prNumber: 2,
92
+ prState: 'OPEN',
93
+ prCiStatus: CIStatus.UNKNOWN,
110
94
  ratchetEnabled: false,
111
95
  ratchetState: RatchetState.IDLE,
112
96
  ratchetActiveSessionId: null,
113
97
  ratchetLastCiRunId: null,
114
- ratchetLastNotifiedState: null,
115
98
  prReviewLastCheckedAt: null,
116
99
  };
117
- const prStateInfo = {
118
- ciStatus: 'FAILURE',
119
- mergeStateStatus: 'CLEAN',
100
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
101
+ ciStatus: CIStatus.FAILURE,
102
+ snapshotKey: '2026-01-02T00:00:00Z',
120
103
  hasChangesRequested: false,
121
- hasNewReviewComments: false,
122
- failedChecks: [],
123
- ciRunId: '1001',
124
- reviews: [],
125
- comments: [],
126
- reviewComments: [],
127
- newReviewComments: [],
128
- newPRComments: [],
104
+ latestReviewActivityAtMs: null,
105
+ statusCheckRollup: null,
129
106
  prState: 'OPEN',
130
107
  prNumber: 2,
131
- };
132
- vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue(prStateInfo);
133
- vi.spyOn(ratchetService, 'determineRatchetState').mockReturnValue(RatchetState.CI_FAILED);
134
- const executeRatchetActionSpy = vi.spyOn(ratchetService, 'executeRatchetAction');
135
- vi.mocked(workspaceAccessor.findById).mockResolvedValue({
136
- ratchetEnabled: false,
137
108
  });
138
109
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
139
- const result = await ratchetService.processWorkspace(workspace, {
140
- autoFixCi: true,
141
- autoFixReviews: true,
142
- autoMerge: false,
143
- allowedReviewers: [],
144
- });
145
- expect(executeRatchetActionSpy).not.toHaveBeenCalled();
110
+ const triggerSpy = vi.spyOn(ratchetService, 'triggerFixer');
111
+ const result = await ratchetService.processWorkspace(workspace);
112
+ expect(triggerSpy).not.toHaveBeenCalled();
146
113
  expect(result).toMatchObject({
147
114
  action: { type: 'DISABLED', reason: 'Workspace ratcheting disabled' },
148
- previousState: RatchetState.IDLE,
149
115
  newState: RatchetState.IDLE,
150
116
  });
151
- expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, expect.objectContaining({ ratchetState: RatchetState.IDLE }));
152
117
  });
153
- it('does not execute ratchet actions when ratcheting is disabled mid-check', async () => {
118
+ it('does not dispatch when workspace is not idle', async () => {
154
119
  const workspace = {
155
- id: 'ws-3',
120
+ id: 'ws-busy',
156
121
  prUrl: 'https://github.com/example/repo/pull/3',
157
122
  prNumber: 3,
123
+ prState: 'OPEN',
124
+ prCiStatus: CIStatus.UNKNOWN,
158
125
  ratchetEnabled: true,
159
126
  ratchetState: RatchetState.IDLE,
160
127
  ratchetActiveSessionId: null,
161
128
  ratchetLastCiRunId: null,
162
- ratchetLastNotifiedState: null,
163
- prReviewLastCheckedAt: null,
129
+ prReviewLastCheckedAt: new Date('2026-01-01T00:00:00Z'),
164
130
  };
165
- const prStateInfo = {
166
- ciStatus: 'FAILURE',
167
- mergeStateStatus: 'CLEAN',
131
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
132
+ ciStatus: CIStatus.SUCCESS,
133
+ snapshotKey: '2026-01-02T00:00:00Z',
168
134
  hasChangesRequested: false,
169
- hasNewReviewComments: false,
170
- failedChecks: [],
171
- ciRunId: '1002',
172
- reviews: [],
173
- comments: [],
174
- reviewComments: [],
175
- newReviewComments: [],
176
- newPRComments: [],
135
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
136
+ statusCheckRollup: null,
177
137
  prState: 'OPEN',
178
138
  prNumber: 3,
179
- };
180
- vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue(prStateInfo);
181
- vi.spyOn(ratchetService, 'determineRatchetState').mockReturnValue(RatchetState.CI_FAILED);
182
- const executeRatchetActionSpy = vi.spyOn(ratchetService, 'executeRatchetAction');
183
- vi.mocked(workspaceAccessor.findById).mockResolvedValue({
184
- ratchetEnabled: false,
185
139
  });
186
140
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
187
- const result = await ratchetService.processWorkspace(workspace, {
188
- autoFixCi: true,
189
- autoFixReviews: true,
190
- autoMerge: false,
191
- allowedReviewers: [],
192
- });
193
- expect(executeRatchetActionSpy).not.toHaveBeenCalled();
141
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([
142
+ {
143
+ id: 'chat-1',
144
+ workflow: 'default-followup',
145
+ status: 'RUNNING',
146
+ },
147
+ ]);
148
+ vi.mocked(sessionService.isSessionWorking).mockReturnValue(true);
149
+ const result = await ratchetService.processWorkspace(workspace);
194
150
  expect(result).toMatchObject({
195
- action: { type: 'DISABLED', reason: 'Workspace ratcheting disabled' },
196
- newState: RatchetState.IDLE,
197
- });
198
- expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, expect.objectContaining({ ratchetState: RatchetState.IDLE }));
199
- });
200
- });
201
- describe('Ratchet CI regression behavior', () => {
202
- beforeEach(() => {
203
- vi.clearAllMocks();
204
- });
205
- it('treats CI UNKNOWN as CI_RUNNING', () => {
206
- const state = ratchetService.determineRatchetState({
207
- ciStatus: CIStatus.UNKNOWN,
208
- mergeStateStatus: 'CLEAN',
209
- hasChangesRequested: false,
210
- hasNewReviewComments: false,
211
- failedChecks: [],
212
- ciRunId: null,
213
- reviews: [],
214
- comments: [],
215
- reviewComments: [],
216
- newReviewComments: [],
217
- newPRComments: [],
218
- prState: 'OPEN',
219
- prNumber: 12,
151
+ action: {
152
+ type: 'WAITING',
153
+ reason: 'Workspace is not idle (active non-ratchet chat session)',
154
+ },
220
155
  });
221
- expect(state).toBe(RatchetState.CI_RUNNING);
222
156
  });
223
- it('re-triggers fixer when active session is unreachable and CI fails', async () => {
157
+ it('dispatches when PR state changed since last ratchet', async () => {
224
158
  const workspace = {
225
- id: 'ws-unreachable',
226
- prUrl: 'https://github.com/example/repo/pull/55',
227
- prNumber: 55,
159
+ id: 'ws-change',
160
+ prUrl: 'https://github.com/example/repo/pull/4',
161
+ prNumber: 4,
162
+ prState: 'OPEN',
163
+ prCiStatus: CIStatus.UNKNOWN,
228
164
  ratchetEnabled: true,
229
- ratchetState: RatchetState.REVIEW_PENDING,
230
- ratchetActiveSessionId: 'session-stale',
231
- ratchetLastCiRunId: '2001',
232
- ratchetLastNotifiedState: RatchetState.CI_FAILED,
233
- prReviewLastCheckedAt: null,
165
+ ratchetState: RatchetState.IDLE,
166
+ ratchetActiveSessionId: null,
167
+ ratchetLastCiRunId: '2026-01-01T00:00:00Z',
168
+ prReviewLastCheckedAt: new Date('2026-01-01T00:00:00Z'),
234
169
  };
235
- vi.mocked(claudeSessionAccessor.findById).mockResolvedValue({
236
- id: 'session-stale',
237
- status: SessionStatus.RUNNING,
170
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
171
+ ciStatus: CIStatus.FAILURE,
172
+ snapshotKey: '2026-01-02T00:00:00Z',
173
+ hasChangesRequested: false,
174
+ latestReviewActivityAtMs: null,
175
+ statusCheckRollup: null,
176
+ prState: 'OPEN',
177
+ prNumber: 4,
238
178
  });
239
- vi.mocked(sessionService.getClient).mockReturnValue(undefined);
240
179
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
241
- const triggerFixerSpy = vi
242
- .spyOn(ratchetService, 'triggerFixer')
243
- .mockResolvedValue({
244
- type: 'TRIGGERED_FIXER',
245
- sessionId: 'new-fixer',
246
- fixerType: 'ci',
180
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
181
+ vi.mocked(fixerSessionService.acquireAndDispatch).mockResolvedValue({
182
+ status: 'started',
183
+ sessionId: 'ratchet-session',
247
184
  promptSent: true,
248
185
  });
249
- const result = await ratchetService.executeRatchetAction(workspace, RatchetState.CI_FAILED, {
186
+ const result = await ratchetService.processWorkspace(workspace);
187
+ expect(result).toMatchObject({
188
+ action: { type: 'TRIGGERED_FIXER' },
189
+ });
190
+ const finalUpdatePayload = vi.mocked(workspaceAccessor.update).mock.calls.at(-1)?.[1];
191
+ expect(finalUpdatePayload.ratchetLastCiRunId).toBe('2026-01-02T00:00:00Z');
192
+ expect(finalUpdatePayload).toHaveProperty('prReviewLastCheckedAt');
193
+ });
194
+ it('does dispatch when non-ratchet session is running but idle', async () => {
195
+ const workspace = {
196
+ id: 'ws-idle-session',
197
+ prUrl: 'https://github.com/example/repo/pull/44',
198
+ prNumber: 44,
199
+ prState: 'OPEN',
200
+ prCiStatus: CIStatus.UNKNOWN,
201
+ ratchetEnabled: true,
202
+ ratchetState: RatchetState.IDLE,
203
+ ratchetActiveSessionId: null,
204
+ ratchetLastCiRunId: '2026-01-01T00:00:00Z',
205
+ prReviewLastCheckedAt: new Date('2026-01-01T00:00:00Z'),
206
+ };
207
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
250
208
  ciStatus: CIStatus.FAILURE,
251
- mergeStateStatus: 'CLEAN',
209
+ snapshotKey: '2026-01-02T00:00:00Z',
252
210
  hasChangesRequested: false,
253
- hasNewReviewComments: false,
254
- failedChecks: [],
255
- ciRunId: '3001',
256
- reviews: [],
257
- comments: [],
258
- reviewComments: [],
259
- newReviewComments: [],
260
- newPRComments: [],
211
+ latestReviewActivityAtMs: null,
212
+ statusCheckRollup: null,
261
213
  prState: 'OPEN',
262
- prNumber: 55,
263
- }, {
264
- autoFixCi: true,
265
- autoFixReviews: true,
266
- autoMerge: false,
267
- allowedReviewers: [],
214
+ prNumber: 44,
268
215
  });
269
- expect(triggerFixerSpy).toHaveBeenCalledTimes(1);
270
- expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, {
271
- ratchetActiveSessionId: null,
272
- });
273
- expect(result).toEqual({
274
- type: 'TRIGGERED_FIXER',
275
- sessionId: 'new-fixer',
276
- fixerType: 'ci',
216
+ vi.mocked(workspaceAccessor.update).mockResolvedValue({});
217
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([
218
+ {
219
+ id: 'chat-idle-1',
220
+ workflow: 'default-followup',
221
+ status: 'RUNNING',
222
+ },
223
+ ]);
224
+ vi.mocked(sessionService.isSessionWorking).mockReturnValue(false);
225
+ vi.mocked(fixerSessionService.acquireAndDispatch).mockResolvedValue({
226
+ status: 'started',
227
+ sessionId: 'ratchet-session-idle-ok',
277
228
  promptSent: true,
278
229
  });
230
+ const result = await ratchetService.processWorkspace(workspace);
231
+ expect(result).toMatchObject({
232
+ action: { type: 'TRIGGERED_FIXER', sessionId: 'ratchet-session-idle-ok' },
233
+ });
279
234
  });
280
- it('skips CI fixer dispatch when ciRunId matches ratchetLastCiRunId', async () => {
235
+ it('does not dispatch when PR state unchanged since last dispatch', async () => {
281
236
  const workspace = {
282
- id: 'ws-stale-ci',
283
- prUrl: 'https://github.com/example/repo/pull/99',
284
- prNumber: 99,
237
+ id: 'ws-unchanged',
238
+ prUrl: 'https://github.com/example/repo/pull/5',
239
+ prNumber: 5,
240
+ prState: 'OPEN',
241
+ prCiStatus: CIStatus.UNKNOWN,
285
242
  ratchetEnabled: true,
286
- ratchetState: RatchetState.CI_FAILED,
243
+ ratchetState: RatchetState.READY,
287
244
  ratchetActiveSessionId: null,
288
- ratchetLastCiRunId: 'run-already-handled',
289
- ratchetLastNotifiedState: null,
290
- prReviewLastCheckedAt: null,
245
+ ratchetLastCiRunId: '2026-01-02T00:00:00Z',
246
+ prReviewLastCheckedAt: new Date('2026-01-02T00:00:00Z'),
291
247
  };
292
- vi.mocked(workspaceAccessor.update).mockResolvedValue({});
293
- const triggerFixerSpy = vi.spyOn(ratchetService, 'triggerFixer');
294
- const result = await ratchetService.executeRatchetAction(workspace, RatchetState.CI_FAILED, {
248
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
295
249
  ciStatus: CIStatus.FAILURE,
296
- mergeStateStatus: 'CLEAN',
250
+ snapshotKey: '2026-01-02T00:00:00Z',
297
251
  hasChangesRequested: false,
298
- hasNewReviewComments: false,
299
- failedChecks: [],
300
- ciRunId: 'run-already-handled',
301
- reviews: [],
302
- comments: [],
303
- reviewComments: [],
304
- newReviewComments: [],
305
- newPRComments: [],
252
+ latestReviewActivityAtMs: null,
253
+ statusCheckRollup: null,
306
254
  prState: 'OPEN',
307
- prNumber: 99,
308
- }, {
309
- autoFixCi: true,
310
- autoFixReviews: true,
311
- autoMerge: false,
312
- allowedReviewers: [],
255
+ prNumber: 5,
313
256
  });
314
- expect(triggerFixerSpy).not.toHaveBeenCalled();
315
- expect(result).toEqual({
316
- type: 'WAITING',
317
- reason: 'Already dispatched fixer for this CI run; waiting for new run',
257
+ vi.mocked(workspaceAccessor.update).mockResolvedValue({});
258
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
259
+ const result = await ratchetService.processWorkspace(workspace);
260
+ expect(result).toMatchObject({
261
+ action: {
262
+ type: 'WAITING',
263
+ reason: 'PR state unchanged since last ratchet dispatch',
264
+ },
318
265
  });
266
+ expect(claudeSessionAccessor.findByWorkspaceId).not.toHaveBeenCalled();
319
267
  });
320
- it('dispatches CI fixer when ciRunId differs from ratchetLastCiRunId', async () => {
268
+ it('does not dispatch repeatedly for unchanged CHANGES_REQUESTED state', async () => {
321
269
  const workspace = {
322
- id: 'ws-new-ci',
323
- prUrl: 'https://github.com/example/repo/pull/100',
324
- prNumber: 100,
270
+ id: 'ws-review-unchanged',
271
+ prUrl: 'https://github.com/example/repo/pull/55',
272
+ prNumber: 55,
273
+ prState: 'OPEN',
274
+ prCiStatus: CIStatus.UNKNOWN,
325
275
  ratchetEnabled: true,
326
- ratchetState: RatchetState.CI_FAILED,
276
+ ratchetState: RatchetState.REVIEW_PENDING,
327
277
  ratchetActiveSessionId: null,
328
- ratchetLastCiRunId: 'run-old',
329
- ratchetLastNotifiedState: null,
330
- prReviewLastCheckedAt: null,
278
+ ratchetLastCiRunId: 'ci:SUCCESS|changes-requested:1735776000000',
279
+ prReviewLastCheckedAt: new Date('2026-01-02T00:00:00Z'),
331
280
  };
281
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
282
+ ciStatus: CIStatus.SUCCESS,
283
+ snapshotKey: 'ci:SUCCESS|changes-requested:1735776000000',
284
+ hasChangesRequested: true,
285
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
286
+ statusCheckRollup: null,
287
+ prState: 'OPEN',
288
+ prNumber: 55,
289
+ });
332
290
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
333
- const triggerFixerSpy = vi
334
- .spyOn(ratchetService, 'triggerFixer')
335
- .mockResolvedValue({
336
- type: 'TRIGGERED_FIXER',
337
- sessionId: 'new-fixer',
338
- fixerType: 'ci',
339
- promptSent: true,
291
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
292
+ const triggerSpy = vi.spyOn(ratchetService, 'triggerFixer');
293
+ const result = await ratchetService.processWorkspace(workspace);
294
+ expect(triggerSpy).not.toHaveBeenCalled();
295
+ expect(result).toMatchObject({
296
+ action: {
297
+ type: 'WAITING',
298
+ reason: 'PR state unchanged since last ratchet dispatch',
299
+ },
340
300
  });
341
- await ratchetService.executeRatchetAction(workspace, RatchetState.CI_FAILED, {
342
- ciStatus: CIStatus.FAILURE,
343
- mergeStateStatus: 'CLEAN',
301
+ expect(claudeSessionAccessor.findByWorkspaceId).not.toHaveBeenCalled();
302
+ });
303
+ it('treats closed PR as IDLE and does not dispatch', () => {
304
+ const state = ratchetService.determineRatchetState({
305
+ ciStatus: CIStatus.SUCCESS,
306
+ snapshotKey: '2026-01-02T00:00:00Z',
344
307
  hasChangesRequested: false,
345
- hasNewReviewComments: false,
346
- failedChecks: [],
347
- ciRunId: 'run-new',
348
- reviews: [],
349
- comments: [],
350
- reviewComments: [],
351
- newReviewComments: [],
352
- newPRComments: [],
353
- prState: 'OPEN',
354
- prNumber: 100,
355
- }, {
356
- autoFixCi: true,
357
- autoFixReviews: true,
358
- autoMerge: false,
359
- allowedReviewers: [],
308
+ latestReviewActivityAtMs: null,
309
+ prState: 'CLOSED',
310
+ prNumber: 6,
360
311
  });
361
- expect(triggerFixerSpy).toHaveBeenCalledTimes(1);
312
+ expect(state).toBe(RatchetState.IDLE);
362
313
  });
363
- it('notifies on a new CI run even when state is already CI_FAILED', () => {
364
- const shouldNotify = ratchetService.shouldNotifyActiveFixer(RatchetState.CI_FAILED, RatchetState.CI_FAILED, '4002', '4001');
365
- expect(shouldNotify).toBe(true);
366
- });
367
- it('does not consume CI run id when ratcheting is disabled', async () => {
314
+ it('handles prompt delivery failure by clearing active session and stopping runner', async () => {
368
315
  const workspace = {
369
- id: 'ws-disabled',
370
- prUrl: 'https://github.com/example/repo/pull/77',
371
- prNumber: 77,
316
+ id: 'ws-prompt-fail',
317
+ prUrl: 'https://github.com/example/repo/pull/7',
318
+ prNumber: 7,
319
+ prState: 'OPEN',
320
+ prCiStatus: CIStatus.UNKNOWN,
372
321
  ratchetEnabled: true,
373
322
  ratchetState: RatchetState.IDLE,
374
323
  ratchetActiveSessionId: null,
375
324
  ratchetLastCiRunId: null,
376
- ratchetLastNotifiedState: null,
377
325
  prReviewLastCheckedAt: null,
378
326
  };
379
327
  vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
380
- ciStatus: CIStatus.FAILURE,
381
- mergeStateStatus: 'CLEAN',
382
- hasChangesRequested: false,
383
- hasNewReviewComments: false,
384
- failedChecks: [],
385
- ciRunId: '5001',
386
- reviews: [],
387
- comments: [],
388
- reviewComments: [],
389
- newReviewComments: [],
390
- newPRComments: [],
328
+ ciStatus: CIStatus.SUCCESS,
329
+ snapshotKey: '2026-01-02T00:00:00Z',
330
+ hasChangesRequested: true,
331
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
332
+ statusCheckRollup: null,
391
333
  prState: 'OPEN',
392
- prNumber: 77,
334
+ prNumber: 7,
393
335
  });
394
- vi.spyOn(ratchetService, 'determineRatchetState').mockReturnValue(RatchetState.CI_FAILED);
395
- vi.mocked(workspaceAccessor.findById).mockResolvedValue({ ratchetEnabled: false });
396
336
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
397
- await ratchetService.processWorkspace(workspace, {
398
- autoFixCi: true,
399
- autoFixReviews: true,
400
- autoMerge: false,
401
- allowedReviewers: [],
337
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
338
+ vi.mocked(fixerSessionService.acquireAndDispatch).mockResolvedValue({
339
+ status: 'started',
340
+ sessionId: 'ratchet-session',
341
+ promptSent: false,
402
342
  });
403
- const updateCalls = vi.mocked(workspaceAccessor.update).mock.calls;
404
- const finalUpdatePayload = updateCalls[updateCalls.length - 1]?.[1];
405
- expect(finalUpdatePayload).not.toHaveProperty('ratchetLastCiRunId');
343
+ vi.mocked(sessionService.isSessionRunning).mockReturnValue(true);
344
+ vi.mocked(sessionService.stopClaudeSession).mockResolvedValue();
345
+ const result = await ratchetService.processWorkspace(workspace);
346
+ expect(result).toMatchObject({
347
+ action: { type: 'ERROR', error: 'Failed to deliver initial ratchet prompt' },
348
+ });
349
+ expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, {
350
+ ratchetActiveSessionId: null,
351
+ });
352
+ expect(sessionService.stopClaudeSession).toHaveBeenCalledWith('ratchet-session');
406
353
  });
407
- it('returns run id "0" when parsed from details url', () => {
408
- const signature = ratchetService.extractFailedCiSignature([
409
- {
410
- name: 'unit-tests',
411
- conclusion: 'FAILURE',
412
- detailsUrl: 'https://github.com/acme/repo/actions/runs/0',
413
- },
414
- {
415
- name: 'lint',
416
- conclusion: 'FAILURE',
417
- detailsUrl: 'https://github.com/acme/repo/actions/runs/42',
354
+ it('does not dispatch on a clean PR with no new review activity', async () => {
355
+ const workspace = {
356
+ id: 'ws-clean',
357
+ prUrl: 'https://github.com/example/repo/pull/8',
358
+ prNumber: 8,
359
+ prState: 'OPEN',
360
+ prCiStatus: CIStatus.UNKNOWN,
361
+ ratchetEnabled: true,
362
+ ratchetState: RatchetState.READY,
363
+ ratchetActiveSessionId: null,
364
+ ratchetLastCiRunId: '2026-01-01T00:00:00Z',
365
+ prReviewLastCheckedAt: new Date('2026-01-02T00:00:00Z'),
366
+ };
367
+ vi.spyOn(ratchetService, 'fetchPRState').mockResolvedValue({
368
+ ciStatus: CIStatus.SUCCESS,
369
+ snapshotKey: '2026-01-03T00:00:00Z',
370
+ hasChangesRequested: false,
371
+ latestReviewActivityAtMs: new Date('2026-01-02T00:00:00Z').getTime(),
372
+ statusCheckRollup: null,
373
+ prState: 'OPEN',
374
+ prNumber: 8,
375
+ });
376
+ vi.mocked(workspaceAccessor.update).mockResolvedValue({});
377
+ vi.mocked(claudeSessionAccessor.findByWorkspaceId).mockResolvedValue([]);
378
+ const triggerSpy = vi.spyOn(ratchetService, 'triggerFixer');
379
+ const result = await ratchetService.processWorkspace(workspace);
380
+ expect(triggerSpy).not.toHaveBeenCalled();
381
+ expect(result).toMatchObject({
382
+ action: {
383
+ type: 'WAITING',
384
+ reason: 'PR is clean (green CI and no new review activity)',
418
385
  },
419
- ]);
420
- expect(signature).toBe('lint:42|unit-tests:0');
386
+ });
421
387
  });
422
- it('clears stale active session linkage when IDLE session has no client', async () => {
388
+ it('clears active ratchet session immediately when runtime is not running', async () => {
423
389
  const workspace = {
424
- id: 'ws-idle',
425
- prUrl: 'https://github.com/example/repo/pull/88',
426
- prNumber: 88,
390
+ id: 'ws-stale-active',
391
+ prUrl: 'https://github.com/example/repo/pull/9',
392
+ prNumber: 9,
393
+ prState: 'OPEN',
394
+ prCiStatus: CIStatus.UNKNOWN,
427
395
  ratchetEnabled: true,
428
396
  ratchetState: RatchetState.CI_FAILED,
429
- ratchetActiveSessionId: 'session-idle',
430
- ratchetLastCiRunId: 'sig-old',
431
- ratchetLastNotifiedState: RatchetState.CI_FAILED,
397
+ ratchetActiveSessionId: 'ratchet-session',
398
+ ratchetLastCiRunId: null,
432
399
  prReviewLastCheckedAt: null,
433
400
  };
434
401
  vi.mocked(claudeSessionAccessor.findById).mockResolvedValue({
435
- id: 'session-idle',
436
- status: SessionStatus.IDLE,
402
+ id: 'ratchet-session',
403
+ status: SessionStatus.RUNNING,
437
404
  });
438
- vi.mocked(sessionService.getClient).mockReturnValue(undefined);
405
+ vi.mocked(sessionService.isSessionRunning).mockReturnValue(false);
439
406
  vi.mocked(workspaceAccessor.update).mockResolvedValue({});
440
- const triggerFixerSpy = vi
441
- .spyOn(ratchetService, 'triggerFixer')
442
- .mockResolvedValue({
443
- type: 'TRIGGERED_FIXER',
444
- sessionId: 'session-idle',
445
- fixerType: 'ci',
446
- promptSent: true,
447
- });
448
- await ratchetService.executeRatchetAction(workspace, RatchetState.CI_FAILED, {
449
- ciStatus: CIStatus.FAILURE,
450
- mergeStateStatus: 'CLEAN',
451
- hasChangesRequested: false,
452
- hasNewReviewComments: false,
453
- failedChecks: [],
454
- ciRunId: 'sig-new',
455
- reviews: [],
456
- comments: [],
457
- reviewComments: [],
458
- newReviewComments: [],
459
- newPRComments: [],
460
- prState: 'OPEN',
461
- prNumber: 88,
462
- }, {
463
- autoFixCi: true,
464
- autoFixReviews: true,
465
- autoMerge: false,
466
- allowedReviewers: [],
467
- });
468
- expect(triggerFixerSpy).toHaveBeenCalledTimes(1);
407
+ const action = await ratchetService.getActiveRatchetSession(workspace);
408
+ expect(action).toBeNull();
469
409
  expect(workspaceAccessor.update).toHaveBeenCalledWith(workspace.id, {
470
410
  ratchetActiveSessionId: null,
471
411
  });
472
412
  });
473
- });
474
- describe('Ratchet Comment Detection', () => {
475
- const lastCheckedAt = new Date('2024-01-01T12:00:00Z').getTime();
476
- describe('New comment detection', () => {
477
- it('should detect comments created after lastCheckedAt', () => {
478
- const comment = {
479
- id: '1',
480
- author: { login: 'reviewer1' },
481
- body: 'New comment',
482
- createdAt: '2024-01-01T13:00:00Z',
483
- updatedAt: '2024-01-01T13:00:00Z',
484
- url: 'https://github.com/test/pr/1',
485
- };
486
- const createdTime = new Date(comment.createdAt).getTime();
487
- const updatedTime = new Date(comment.updatedAt).getTime();
488
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
489
- expect(isNewOrEdited).toBe(true);
490
- });
491
- it('should not detect comments created before lastCheckedAt', () => {
492
- const comment = {
493
- id: '1',
494
- author: { login: 'reviewer1' },
495
- body: 'Old comment',
496
- createdAt: '2024-01-01T11:00:00Z',
497
- updatedAt: '2024-01-01T11:00:00Z',
498
- url: 'https://github.com/test/pr/1',
499
- };
500
- const createdTime = new Date(comment.createdAt).getTime();
501
- const updatedTime = new Date(comment.updatedAt).getTime();
502
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
503
- expect(isNewOrEdited).toBe(false);
413
+ it('decides to trigger fixer when context is actionable', () => {
414
+ const decision = ratchetService.decideRatchetAction({
415
+ workspace: { ratchetEnabled: true },
416
+ prStateInfo: { prState: 'OPEN', ciStatus: CIStatus.FAILURE },
417
+ isCleanPrWithNoNewReviewActivity: false,
418
+ activeRatchetSession: null,
419
+ hasStateChangedSinceLastDispatch: true,
420
+ hasOtherActiveSession: false,
504
421
  });
422
+ expect(decision).toEqual({ type: 'TRIGGER_FIXER' });
505
423
  });
506
- describe('Edited comment detection', () => {
507
- it('should detect comments edited after lastCheckedAt', () => {
508
- const comment = {
509
- id: '1',
510
- author: { login: 'reviewer1' },
511
- body: 'Edited comment',
512
- createdAt: '2024-01-01T11:00:00Z',
513
- updatedAt: '2024-01-01T13:00:00Z',
514
- url: 'https://github.com/test/pr/1',
515
- };
516
- const createdTime = new Date(comment.createdAt).getTime();
517
- const updatedTime = new Date(comment.updatedAt).getTime();
518
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
519
- expect(isNewOrEdited).toBe(true);
520
- });
521
- it('should detect comments with updatedAt exactly equal to lastCheckedAt as not new', () => {
522
- const comment = {
523
- id: '1',
524
- author: { login: 'reviewer1' },
525
- body: 'Comment at boundary',
526
- createdAt: '2024-01-01T11:00:00Z',
527
- updatedAt: '2024-01-01T12:00:00Z',
528
- url: 'https://github.com/test/pr/1',
529
- };
530
- const createdTime = new Date(comment.createdAt).getTime();
531
- const updatedTime = new Date(comment.updatedAt).getTime();
532
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
533
- expect(isNewOrEdited).toBe(false);
424
+ it('waits when CI is not in terminal state (PENDING)', () => {
425
+ const decision = ratchetService.decideRatchetAction({
426
+ workspace: { ratchetEnabled: true },
427
+ prStateInfo: { prState: 'OPEN', ciStatus: CIStatus.PENDING },
428
+ isCleanPrWithNoNewReviewActivity: false,
429
+ activeRatchetSession: null,
430
+ hasStateChangedSinceLastDispatch: true,
431
+ hasOtherActiveSession: false,
534
432
  });
535
- it('should not detect old unedited comments', () => {
536
- const comment = {
537
- id: '1',
538
- author: { login: 'reviewer1' },
539
- body: 'Old unedited comment',
540
- createdAt: '2024-01-01T10:00:00Z',
541
- updatedAt: '2024-01-01T10:00:00Z',
542
- url: 'https://github.com/test/pr/1',
543
- };
544
- const createdTime = new Date(comment.createdAt).getTime();
545
- const updatedTime = new Date(comment.updatedAt).getTime();
546
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
547
- expect(isNewOrEdited).toBe(false);
433
+ expect(decision).toEqual({
434
+ type: 'RETURN_ACTION',
435
+ action: { type: 'WAITING', reason: 'Waiting for CI to complete (not in terminal state)' },
548
436
  });
549
437
  });
550
- describe('Reviewer filtering with edited comments', () => {
551
- it('should apply reviewer filter to edited comments', () => {
552
- const allowedReviewers = ['reviewer1', 'reviewer2'];
553
- const filterByReviewer = allowedReviewers.length > 0;
554
- const comments = [
555
- {
556
- id: '1',
557
- author: { login: 'reviewer1' },
558
- body: 'Allowed reviewer edit',
559
- createdAt: '2024-01-01T11:00:00Z',
560
- updatedAt: '2024-01-01T13:00:00Z',
561
- url: 'https://github.com/test/pr/1',
562
- },
563
- {
564
- id: '2',
565
- author: { login: 'reviewer3' },
566
- body: 'Disallowed reviewer edit',
567
- createdAt: '2024-01-01T11:00:00Z',
568
- updatedAt: '2024-01-01T13:00:00Z',
569
- url: 'https://github.com/test/pr/2',
570
- },
571
- ];
572
- const filteredComments = comments.filter((comment) => {
573
- const createdTime = new Date(comment.createdAt).getTime();
574
- const updatedTime = new Date(comment.updatedAt).getTime();
575
- const isNewOrEdited = createdTime > lastCheckedAt || updatedTime > lastCheckedAt;
576
- const isAllowedReviewer = !filterByReviewer || allowedReviewers.includes(comment.author.login);
577
- return isNewOrEdited && isAllowedReviewer;
578
- });
579
- expect(filteredComments).toHaveLength(1);
580
- expect(filteredComments[0].author.login).toBe('reviewer1');
438
+ it('waits when CI is not in terminal state (UNKNOWN)', () => {
439
+ const decision = ratchetService.decideRatchetAction({
440
+ workspace: { ratchetEnabled: true },
441
+ prStateInfo: { prState: 'OPEN', ciStatus: CIStatus.UNKNOWN },
442
+ isCleanPrWithNoNewReviewActivity: false,
443
+ activeRatchetSession: null,
444
+ hasStateChangedSinceLastDispatch: true,
445
+ hasOtherActiveSession: false,
581
446
  });
447
+ expect(decision).toEqual({
448
+ type: 'RETURN_ACTION',
449
+ action: { type: 'WAITING', reason: 'Waiting for CI to complete (not in terminal state)' },
450
+ });
451
+ });
452
+ it('prefers precomputed review-activity diagnostics from decision context', () => {
453
+ const workspace = {
454
+ id: 'ws-log-diag',
455
+ prUrl: 'https://github.com/example/repo/pull/10',
456
+ prNumber: 10,
457
+ prState: 'OPEN',
458
+ prCiStatus: CIStatus.UNKNOWN,
459
+ ratchetEnabled: true,
460
+ ratchetState: RatchetState.READY,
461
+ ratchetActiveSessionId: null,
462
+ ratchetLastCiRunId: 'prev-snapshot',
463
+ prReviewLastCheckedAt: null,
464
+ };
465
+ const prStateInfo = {
466
+ ciStatus: CIStatus.SUCCESS,
467
+ snapshotKey: 'next-snapshot',
468
+ hasChangesRequested: false,
469
+ latestReviewActivityAtMs: null,
470
+ statusCheckRollup: null,
471
+ prState: 'OPEN',
472
+ prNumber: 10,
473
+ };
474
+ const logContext = ratchetService.buildRatchetingLogContext(workspace, RatchetState.READY, RatchetState.READY, { type: 'WAITING', reason: 'noop' }, prStateInfo, 10, { hasNewReviewActivitySinceLastDispatch: true });
475
+ expect(logContext.reviewTimestampComparison.hasNewReviewActivitySinceLastDispatch).toBe(true);
476
+ });
477
+ it('ignores review activity authored by the authenticated user', () => {
478
+ const latestActivity = ratchetService.computeLatestReviewActivityAtMs({
479
+ reviews: [
480
+ { submittedAt: '2026-01-02T00:00:00Z', author: { login: 'ratchet-bot' } },
481
+ { submittedAt: '2026-01-01T00:00:00Z', author: { login: 'reviewer' } },
482
+ ],
483
+ comments: [{ updatedAt: '2026-01-02T01:00:00Z', author: { login: 'ratchet-bot' } }],
484
+ }, [{ updatedAt: '2026-01-01T02:00:00Z', author: { login: 'reviewer2' } }], 'ratchet-bot');
485
+ expect(latestActivity).toBe(new Date('2026-01-01T02:00:00Z').getTime());
582
486
  });
583
487
  });
584
488
  //# sourceMappingURL=ratchet.service.test.js.map