specrails-desktop 2.29.1 → 2.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. package/client/dist/assets/ActivityFeedPage-ClpD5CFj.js +1 -0
  2. package/client/dist/assets/AgentBrowserCapture-CSPs9Ofc.js +1 -0
  3. package/client/dist/assets/AgentModeCodePane-DwcyklA5.js +2 -0
  4. package/client/dist/assets/AgentModeJobsPane-C69bqlMa.js +1 -0
  5. package/client/dist/assets/AgentsPage-pRiRr9-W.js +86 -0
  6. package/client/dist/assets/AnalyticsPage-C14exKsC.js +1 -0
  7. package/client/dist/assets/BarChart-nBOMNfiy.js +33 -0
  8. package/client/dist/assets/CodePage-CK76JKm9.js +2 -0
  9. package/client/dist/assets/DesktopAnalyticsPage-Cnv0f3XQ.js +1 -0
  10. package/client/dist/assets/DocsDialog-CHUm_Ym0.js +11 -0
  11. package/client/dist/assets/DocsPage-COI-oDok.js +11 -0
  12. package/client/dist/assets/ExportDropdown-BrtfWzEG.js +1 -0
  13. package/client/dist/assets/IntegrationsPage-Bs00_tl4.js +1 -0
  14. package/client/dist/assets/InteractiveJobComposer-CypM7R1F.js +19 -0
  15. package/client/dist/assets/JobDetailModal-B_W0H9dw.js +1 -0
  16. package/client/dist/assets/JobDetailPage-CeeXOoKS.js +1 -0
  17. package/client/dist/assets/JobsPage-CwhJG9jw.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-C51t1WOM.js +7 -0
  19. package/client/dist/assets/LoopPreviewModal-Y2HgtIJu.js +1 -0
  20. package/client/dist/assets/LoopsPage-Bvn1Kw5V.js +1 -0
  21. package/client/dist/assets/MinimizedChatsContext-DiFvZDPW.js +1 -0
  22. package/client/dist/assets/PluginsPage-BGPRJpdf.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-D_GTyGLc.js +1 -0
  24. package/client/dist/assets/TemplatePreviewModal-szwBhd7h.js +1 -0
  25. package/client/dist/assets/TicketDetailModal-9UEyK974.js +14 -0
  26. package/client/dist/assets/Trans-B_vKZ-Hb.js +1 -0
  27. package/client/dist/assets/activity-DiyjsgQV.js +1 -0
  28. package/client/dist/assets/addon-image-BRmmRHgQ.js +1 -0
  29. package/client/dist/assets/addon-ligatures-qIZUPvRV.js +2 -0
  30. package/client/dist/assets/{addon-webgl-DMbGeI_u.js → addon-webgl-C2d7lriQ.js} +3 -3
  31. package/client/dist/assets/addspec-Rw8uVBC1.js +1 -0
  32. package/client/dist/assets/agent-4rmXoo_p.js +1 -0
  33. package/client/dist/assets/agent-BxZuNGih.js +1 -0
  34. package/client/dist/assets/agent-C3uXOYv_.js +1 -0
  35. package/client/dist/assets/agent-CA8Y4wbw.js +1 -0
  36. package/client/dist/assets/agent-CASKrhG12.js +1 -0
  37. package/client/dist/assets/agent-Cv-jud5n2.js +1 -0
  38. package/client/dist/assets/agent-DitUxYh2.js +1 -0
  39. package/client/dist/assets/agent-Doow4X7o.js +1 -0
  40. package/client/dist/assets/agents-DhG7mg6-.js +1 -0
  41. package/client/dist/assets/agentstudio-BIUe1Im2.js +1 -0
  42. package/client/dist/assets/aiedit-iIjz2_Qk.js +1 -0
  43. package/client/dist/assets/analytics-DZ8Aoq3I.js +1 -0
  44. package/client/dist/assets/api-B2M7CtDw.js +1 -0
  45. package/client/dist/assets/arrow-left-CpIR4j7G.js +1 -0
  46. package/client/dist/assets/arrow-up-right-9DoMn7fD.js +1 -0
  47. package/client/dist/assets/attachments-B53PG733.js +1 -0
  48. package/client/dist/assets/brain-DI9lHkKY.js +1 -0
  49. package/client/dist/assets/browser-CXUcnTMI.js +1 -0
  50. package/client/dist/assets/button-BUIIBQPT.js +1 -0
  51. package/client/dist/assets/chat-BGD7MXAy.js +1 -0
  52. package/client/dist/assets/chevron-right-CViMxucY.js +1 -0
  53. package/client/dist/assets/chunk-KS7C4IRE-Cx5sqopX.js +3 -0
  54. package/client/dist/assets/circle-x-CMYsN4ZH.js +1 -0
  55. package/client/dist/assets/code-BDXEM4ml.js +1 -0
  56. package/client/dist/assets/commands-DyUZQ1FI.js +1 -0
  57. package/client/dist/assets/common-CD1QHriA2.js +1 -0
  58. package/client/dist/assets/common-CwwPaRxx.js +1 -0
  59. package/client/dist/assets/common-DVQuhs-m.js +1 -0
  60. package/client/dist/assets/common-Dg2jaX7d.js +1 -0
  61. package/client/dist/assets/common-DuBwj6tK.js +1 -0
  62. package/client/dist/assets/common-MRj9MHcc.js +1 -0
  63. package/client/dist/assets/common-O4ZpnMLp.js +1 -0
  64. package/client/dist/assets/common-fyeF0v7C2.js +1 -0
  65. package/client/dist/assets/createLucideIcon-Bjbbp_KS.js +1 -0
  66. package/client/dist/assets/{css.worker-Wv5dxAWO.js → css.worker-yKcq13f2.js} +13 -13
  67. package/client/dist/assets/{cssMode-DBHbkBvI.js → cssMode-BM4z9OX0.js} +1 -1
  68. package/client/dist/assets/dashboard-B_NkSHYm.js +1 -0
  69. package/client/dist/assets/dashboard-ClBb_3pq.js +1 -0
  70. package/client/dist/assets/dashboard-CpCmJvMB.js +1 -0
  71. package/client/dist/assets/dashboard-Czohu35O2.js +1 -0
  72. package/client/dist/assets/dashboard-DcjbD0UK.js +1 -0
  73. package/client/dist/assets/dashboard-Dvx5F3we.js +1 -0
  74. package/client/dist/assets/dashboard-R1bOPjBh2.js +1 -0
  75. package/client/dist/assets/dashboard-owcDucDi.js +1 -0
  76. package/client/dist/assets/dialog-CzIekCw0.js +45 -0
  77. package/client/dist/assets/dist-Cez5CRkX.js +1 -0
  78. package/client/dist/assets/dist-bVqfYmby.js +1 -0
  79. package/client/dist/assets/{dist-js-CgpP6tKI.js → dist-js-CcBCbb32.js} +1 -1
  80. package/client/dist/assets/{dist-js-DHiN-A_E.js → dist-js-VwztRkGj.js} +1 -1
  81. package/client/dist/assets/download-CFHyyHcw.js +1 -0
  82. package/client/dist/assets/editor.api2-4ndOupAA.js +872 -0
  83. package/client/dist/assets/{editor.main-C9J2ikJK.js → editor.main-DxrmGfoq.js} +2 -2
  84. package/client/dist/assets/editor.worker-tytyn86m.js +26 -0
  85. package/client/dist/assets/explore-DgBACn39.js +1 -0
  86. package/client/dist/assets/external-link-BcU5EWZp.js +1 -0
  87. package/client/dist/assets/file-text-CFT-xXKN.js +1 -0
  88. package/client/dist/assets/formatDistanceToNow-ByHIauTQ.js +1 -0
  89. package/client/dist/assets/{freemarker2-J5iIIIK-.js → freemarker2-CELlTDJ2.js} +1 -1
  90. package/client/dist/assets/getTimezoneOffsetInMilliseconds-BxHAksaU.js +1 -0
  91. package/client/dist/assets/git-branch-BRM0F5GN.js +1 -0
  92. package/client/dist/assets/{handlebars-DSw1l_tw.js → handlebars-NEjHJ7o_.js} +1 -1
  93. package/client/dist/assets/{html-h2-wE1Lj.js → html-CA10zN5A.js} +1 -1
  94. package/client/dist/assets/{html.worker-CQP8QQsS.js → html.worker-DSJ9zmd7.js} +12 -12
  95. package/client/dist/assets/{htmlMode-Cc_ij7lW.js → htmlMode-89rPYiyq.js} +1 -1
  96. package/client/dist/assets/i18n-CgaRxpII.js +2 -0
  97. package/client/dist/assets/index-BflYQpWm.css +2 -0
  98. package/client/dist/assets/index-C1_-BTXy.js +72 -0
  99. package/client/dist/assets/integrations-mB-7zoyT.js +1 -0
  100. package/client/dist/assets/{javascript-C1q3xv6N.js → javascript-B1gDmwzS.js} +1 -1
  101. package/client/dist/assets/jira-CmKWERE0.js +1 -0
  102. package/client/dist/assets/jira-api-BgFIr8eD.js +1 -0
  103. package/client/dist/assets/job-time-BCL7wee9.js +1 -0
  104. package/client/dist/assets/jobs-B-sDHEay2.js +1 -0
  105. package/client/dist/assets/jobs-BCtGfgUA.js +1 -0
  106. package/client/dist/assets/jobs-BHR_EnDI.js +1 -0
  107. package/client/dist/assets/jobs-BMbZJjJJ.js +1 -0
  108. package/client/dist/assets/jobs-CVFD8rar.js +1 -0
  109. package/client/dist/assets/jobs-DoSaFzAa.js +1 -0
  110. package/client/dist/assets/jobs-DzSIR8V62.js +1 -0
  111. package/client/dist/assets/jobs-lk1_Dq1U.js +1 -0
  112. package/client/dist/assets/json.worker-Oa9UrkKE.js +58 -0
  113. package/client/dist/assets/{jsonMode-CeaCdEhr.js → jsonMode-xHLMfzhF.js} +1 -1
  114. package/client/dist/assets/jsx-runtime-zu2_FqZY.js +1 -0
  115. package/client/dist/assets/lib-C7Mhm-gT.js +7 -0
  116. package/client/dist/assets/lib-CGdyxIWg.js +29 -0
  117. package/client/dist/assets/{liquid-eFsVK0V-.js → liquid-BY6hCUXG.js} +1 -1
  118. package/client/dist/assets/loader-circle-DcygOQpC.js +1 -0
  119. package/client/dist/assets/loops-BzfXWEwO.js +1 -0
  120. package/client/dist/assets/{lspLanguageFeatures-DT3-bbhn.js → lspLanguageFeatures-ira2aEJh.js} +1 -1
  121. package/client/dist/assets/mcp-uDkXYu4L.js +1 -0
  122. package/client/dist/assets/{mdx-Fuwnh1cX.js → mdx-BNx0xr3N.js} +1 -1
  123. package/client/dist/assets/monaco.contribution-Bt92R3Vw.js +2 -0
  124. package/client/dist/assets/nav-myFaXRHD.js +1 -0
  125. package/client/dist/assets/pencil-C9RkrIPZ.js +1 -0
  126. package/client/dist/assets/play-DNlrawbS.js +1 -0
  127. package/client/dist/assets/plus-CsnjfkmX.js +1 -0
  128. package/client/dist/assets/preload-helper-Czpn1I53.js +1 -0
  129. package/client/dist/assets/{python-BgoEs6Cj.js → python-DLdbtmNw.js} +1 -1
  130. package/client/dist/assets/{razor-CM1sPMMq.js → razor-B9gdhAbR.js} +1 -1
  131. package/client/dist/assets/react-dom-DiWzBDP9.js +1 -0
  132. package/client/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
  133. package/client/dist/assets/save-28sIlCdr.js +1 -0
  134. package/client/dist/assets/search-Cq_gE3Ik.js +1 -0
  135. package/client/dist/assets/settings-B0ypw9uw.js +1 -0
  136. package/client/dist/assets/setup-CmK87LuS.js +1 -0
  137. package/client/dist/assets/sparkles-DrY8hb0X.js +1 -0
  138. package/client/dist/assets/specs-DII0C8gw.js +1 -0
  139. package/client/dist/assets/spending-C4p06nen.js +1 -0
  140. package/client/dist/assets/terminal-BFRZnMPP.js +1 -0
  141. package/client/dist/assets/tickets-B8BuAaj5.js +1 -0
  142. package/client/dist/assets/tickets-B_yh4qQk.js +1 -0
  143. package/client/dist/assets/tickets-Bs37scwY2.js +1 -0
  144. package/client/dist/assets/tickets-CFk2n2Or.js +1 -0
  145. package/client/dist/assets/tickets-DQp7JS2R2.js +1 -0
  146. package/client/dist/assets/tickets-D_yDquEw.js +1 -0
  147. package/client/dist/assets/tickets-DpmdE8tv2.js +1 -0
  148. package/client/dist/assets/tickets-pAUJkDTu.js +1 -0
  149. package/client/dist/assets/trash-2-BOU3WdZZ.js +1 -0
  150. package/client/dist/assets/triangle-alert-H9T4jAmh.js +1 -0
  151. package/client/dist/assets/{ts.worker-METxwbDZ.js → ts.worker-BtjBFNHX.js} +48 -33
  152. package/client/dist/assets/{tsMode-BmT8WogI.js → tsMode-DEjSThU9.js} +1 -1
  153. package/client/dist/assets/{typescript-CQrQPsoW.js → typescript-CRRnD2Ts.js} +1 -1
  154. package/client/dist/assets/upload-DYMjAQ6M.js +1 -0
  155. package/client/dist/assets/useBrowserCaptureSession-2OqX0vap.js +1 -0
  156. package/client/dist/assets/useDesktop-X8YB9xzv.js +1 -0
  157. package/client/dist/assets/useSharedWebSocket-DaPh7QBL.js +1 -0
  158. package/client/dist/assets/utils-BC4DHlz2.js +1 -0
  159. package/client/dist/assets/value-s-gzBeyF.js +1 -0
  160. package/client/dist/assets/{workers-U0UXyLUf.js → workers-uOo7WLaa.js} +1 -1
  161. package/client/dist/assets/x-CBMI9ejn.js +1 -0
  162. package/client/dist/assets/{xml-WRxJs8_I.js → xml-EDREH2X7.js} +1 -1
  163. package/client/dist/assets/{yaml-CjpSTjJU.js → yaml-BjaDVX1E.js} +1 -1
  164. package/client/dist/assets/zap-CHo3aCD1.js +1 -0
  165. package/client/dist/index.html +73 -32
  166. package/docs/guide/en/integrations/5-mcp-server.md +2 -2
  167. package/docs/internals/safe-pr-review-flow.md +190 -132
  168. package/docs/mcp.md +15 -5
  169. package/package.json +11 -7
  170. package/server/dist/active-pr-continuation.js +16 -1
  171. package/server/dist/agent-chat-manager.js +360 -181
  172. package/server/dist/agent-cwd-manager.js +52 -8
  173. package/server/dist/agent-generator.js +90 -48
  174. package/server/dist/agent-mcp-config.js +69 -36
  175. package/server/dist/agent-operator-prompt.js +3 -2
  176. package/server/dist/agent-refine-manager.js +47 -6
  177. package/server/dist/agent-tier.js +13 -11
  178. package/server/dist/artifact-registry.js +33 -2
  179. package/server/dist/auth.js +5 -4
  180. package/server/dist/browser-capture-manager.js +92 -29
  181. package/server/dist/chat-manager.js +302 -46
  182. package/server/dist/contract-refine-runner.js +19 -6
  183. package/server/dist/db.js +665 -8
  184. package/server/dist/desktop-router.js +1 -2
  185. package/server/dist/headroom-manager.js +1310 -121
  186. package/server/dist/headroom-routing.js +120 -2
  187. package/server/dist/index.js +48 -13
  188. package/server/dist/interactive-job-session.js +237 -82
  189. package/server/dist/jira/jira-adf.js +10 -0
  190. package/server/dist/jira/jira-db.js +15 -3
  191. package/server/dist/jira/jira-sync-manager.js +182 -13
  192. package/server/dist/job-listing.js +140 -0
  193. package/server/dist/job-spawn-idempotency.js +67 -0
  194. package/server/dist/loop-run-manager.js +508 -98
  195. package/server/dist/loop-runs-store.js +266 -13
  196. package/server/dist/mcp/agent-capability.js +71 -0
  197. package/server/dist/mcp/guide.js +5 -2
  198. package/server/dist/mcp/mcp-server.js +5 -10
  199. package/server/dist/mcp/mcp-token.js +5 -5
  200. package/server/dist/mcp/tools/jobs.js +20 -13
  201. package/server/dist/mcp/tools/types.js +20 -42
  202. package/server/dist/mobile/mobile-router.js +1 -1
  203. package/server/dist/pr-lifecycle.js +77 -0
  204. package/server/dist/pr-publisher.js +94 -27
  205. package/server/dist/process-admission.js +82 -0
  206. package/server/dist/project-registry.js +572 -174
  207. package/server/dist/project-router-git.js +13 -2
  208. package/server/dist/project-router-jobs.js +111 -55
  209. package/server/dist/project-router-tickets.js +32 -2
  210. package/server/dist/providers/claude-adapter.js +21 -13
  211. package/server/dist/queue-manager.js +2332 -623
  212. package/server/dist/rail-isolated-launch.js +1137 -290
  213. package/server/dist/rail-isolation.js +4 -2
  214. package/server/dist/rail-pr-decision.js +740 -266
  215. package/server/dist/rail-pr-delivery.js +2 -1
  216. package/server/dist/rail-pr-store.js +317 -18
  217. package/server/dist/rail-pr-ticket-effects.js +328 -0
  218. package/server/dist/rail-worktree-release.js +18 -2
  219. package/server/dist/rails-router.js +149 -119
  220. package/server/dist/rails-store.js +106 -3
  221. package/server/dist/ticket-store.js +23 -1
  222. package/server/dist/transient-children.js +13 -0
  223. package/server/dist/util/cli-prompt.js +42 -8
  224. package/server/dist/util/secure-token-file.js +84 -0
  225. package/server/dist/worktree-manager.js +6 -5
  226. package/client/dist/assets/ActivityFeedPage-D8RKRRoM.js +0 -1
  227. package/client/dist/assets/AgentBrowserCapture-BUHPHNSN.js +0 -1
  228. package/client/dist/assets/AgentModeCodePane-GZc8bNGT.js +0 -2
  229. package/client/dist/assets/AgentModeJobsPane-DWtQqj3k.js +0 -1
  230. package/client/dist/assets/AgentsPage-CKPtHkRP.js +0 -86
  231. package/client/dist/assets/AnalyticsPage-CZW8c77k.js +0 -1
  232. package/client/dist/assets/BarChart-DugEoe2q.js +0 -33
  233. package/client/dist/assets/CodePage-CQ-uKETG.js +0 -2
  234. package/client/dist/assets/DesktopAnalyticsPage-Bgo4SyCH.js +0 -1
  235. package/client/dist/assets/DocsDialog-DxpWw_La.js +0 -11
  236. package/client/dist/assets/DocsPage-CmcOkK19.js +0 -11
  237. package/client/dist/assets/ExportDropdown-DvTS2wc7.js +0 -1
  238. package/client/dist/assets/IntegrationsPage-B72WxJ3Z.js +0 -1
  239. package/client/dist/assets/InteractiveJobComposer-B5gh4Qoy.js +0 -19
  240. package/client/dist/assets/JobDetailModal-D-BeqsJ6.js +0 -1
  241. package/client/dist/assets/JobDetailPage-DwdozwLO.js +0 -1
  242. package/client/dist/assets/JobsPage-BDkinklt.js +0 -1
  243. package/client/dist/assets/LoopBuilderPage-CclJ3VZA.js +0 -7
  244. package/client/dist/assets/LoopPreviewModal-Dw4UgoLu.js +0 -1
  245. package/client/dist/assets/LoopsPage-D7ac4M24.js +0 -1
  246. package/client/dist/assets/PluginsPage-wdC-qa40.js +0 -2
  247. package/client/dist/assets/ProjectSettingsDialog-BlTlRtGI.js +0 -1
  248. package/client/dist/assets/TemplatePreviewModal-CeMx2Red.js +0 -1
  249. package/client/dist/assets/activity-Dbl71S-E.js +0 -1
  250. package/client/dist/assets/addon-image-CwoZj17F.js +0 -1
  251. package/client/dist/assets/addon-ligatures-CWDrED80.js +0 -2
  252. package/client/dist/assets/addspec-DrXqP8ip.js +0 -1
  253. package/client/dist/assets/agent-B8BXmlUQ2.js +0 -1
  254. package/client/dist/assets/agent-BEXYxDbs.js +0 -1
  255. package/client/dist/assets/agent-BYc3-6Ln2.js +0 -1
  256. package/client/dist/assets/agent-BbJorPdT.js +0 -1
  257. package/client/dist/assets/agent-CewFImgL.js +0 -1
  258. package/client/dist/assets/agent-DfhYoUmp.js +0 -1
  259. package/client/dist/assets/agent-TK_rrK4d.js +0 -1
  260. package/client/dist/assets/agent-bHjA9R_j2.js +0 -1
  261. package/client/dist/assets/agents-CGDn_3kB.js +0 -1
  262. package/client/dist/assets/agentstudio-Dxe_94PK.js +0 -1
  263. package/client/dist/assets/aiedit-DrfHQnlz.js +0 -1
  264. package/client/dist/assets/analytics-CxDYcBl9.js +0 -1
  265. package/client/dist/assets/attachments-DvxQbBTC.js +0 -1
  266. package/client/dist/assets/brain-C92rjjYP.js +0 -1
  267. package/client/dist/assets/browser-DM8G151q.js +0 -1
  268. package/client/dist/assets/chat-0dg5hOo_.js +0 -1
  269. package/client/dist/assets/chunk-BNv3lrIs.js +0 -1
  270. package/client/dist/assets/code-B62fClk-.js +0 -1
  271. package/client/dist/assets/commands-DCJ-R9E4.js +0 -1
  272. package/client/dist/assets/common-BM3LFLCE2.js +0 -1
  273. package/client/dist/assets/common-Bwafujnc2.js +0 -1
  274. package/client/dist/assets/common-C-vU-j-I.js +0 -1
  275. package/client/dist/assets/common-C5HKSbbz.js +0 -1
  276. package/client/dist/assets/common-DXrmj6WD.js +0 -1
  277. package/client/dist/assets/common-DhWTkocg.js +0 -1
  278. package/client/dist/assets/common-DiPTT7V_2.js +0 -1
  279. package/client/dist/assets/common-DmqBPwZG.js +0 -1
  280. package/client/dist/assets/dashboard-B5ang-ZS.js +0 -1
  281. package/client/dist/assets/dashboard-CHHcOWVL2.js +0 -1
  282. package/client/dist/assets/dashboard-DBD4wPGY2.js +0 -1
  283. package/client/dist/assets/dashboard-DBuLfWo9.js +0 -1
  284. package/client/dist/assets/dashboard-DSnztwWH.js +0 -1
  285. package/client/dist/assets/dashboard-D_aP_InU.js +0 -1
  286. package/client/dist/assets/dashboard-DmBcdl1B.js +0 -1
  287. package/client/dist/assets/dashboard-RIcUKY1Y2.js +0 -1
  288. package/client/dist/assets/editor.api2-C3B0tp2x.js +0 -872
  289. package/client/dist/assets/editor.worker-Bd9IXS8d.js +0 -26
  290. package/client/dist/assets/explore-_XyU5e7R.js +0 -1
  291. package/client/dist/assets/index-BVbgKXUu.css +0 -2
  292. package/client/dist/assets/index-DyY1QyIn.js +0 -158
  293. package/client/dist/assets/integrations-By80EBkf.js +0 -1
  294. package/client/dist/assets/jira-DeIjwCK3.js +0 -1
  295. package/client/dist/assets/jobs-1GV2-4zd2.js +0 -1
  296. package/client/dist/assets/jobs-26F9gGcE.js +0 -1
  297. package/client/dist/assets/jobs-BgQzB0Sx2.js +0 -1
  298. package/client/dist/assets/jobs-BqWui9_f.js +0 -1
  299. package/client/dist/assets/jobs-CE8qeIYj.js +0 -1
  300. package/client/dist/assets/jobs-DrYmsKy5.js +0 -1
  301. package/client/dist/assets/jobs-DzeuASRf.js +0 -1
  302. package/client/dist/assets/jobs-Wh7dn28q2.js +0 -1
  303. package/client/dist/assets/json.worker-DzV-CpCQ.js +0 -58
  304. package/client/dist/assets/lib-0PFDyMxJ.js +0 -7
  305. package/client/dist/assets/loops-CO6q75W8.js +0 -1
  306. package/client/dist/assets/mcp-HwBbQDk0.js +0 -1
  307. package/client/dist/assets/monaco.contribution-Desq7oq4.js +0 -2
  308. package/client/dist/assets/nav-BTg8s-cD.js +0 -1
  309. package/client/dist/assets/preload-helper-DSXbuxSR.js +0 -1
  310. package/client/dist/assets/settings-cJNynGIZ.js +0 -1
  311. package/client/dist/assets/setup-D2CmgAro.js +0 -1
  312. package/client/dist/assets/specs-PGIOzMtc.js +0 -1
  313. package/client/dist/assets/terminal-CFt56A5f.js +0 -1
  314. package/client/dist/assets/tickets-B7zT4IRf.js +0 -1
  315. package/client/dist/assets/tickets-BHv3ewvq.js +0 -1
  316. package/client/dist/assets/tickets-BoXom7l8.js +0 -1
  317. package/client/dist/assets/tickets-C3b8cHhd2.js +0 -1
  318. package/client/dist/assets/tickets-CFXnJsYF.js +0 -1
  319. package/client/dist/assets/tickets-CWYFvx1W2.js +0 -1
  320. package/client/dist/assets/tickets-DTHo9c3C.js +0 -1
  321. package/client/dist/assets/tickets-SuX_HkK4.js +0 -1
  322. package/client/dist/assets/upload-CmR4h9H1.js +0 -1
  323. package/client/dist/assets/value-JA3bf47v.js +0 -1
  324. /package/client/dist/assets/{abap-D0E4FfVW.js → abap-08VXUWAP.js} +0 -0
  325. /package/client/dist/assets/{activity--iN4Ad2R2.js → activity-B-rp4h_v.js} +0 -0
  326. /package/client/dist/assets/{activity-6hM_ZSg9.js → activity-C6kJpm9q2.js} +0 -0
  327. /package/client/dist/assets/{activity-CIOLxGvh.js → activity-CGx4kb_j.js} +0 -0
  328. /package/client/dist/assets/{activity-BZNfOdiS.js → activity-CnoX2bH7.js} +0 -0
  329. /package/client/dist/assets/{activity-BuVo5sPB.js → activity-DAyekdlt2.js} +0 -0
  330. /package/client/dist/assets/{activity-D2r2h3SE2.js → activity-DCxtpT7j.js} +0 -0
  331. /package/client/dist/assets/{activity-DtxrAUMo2.js → activity-JzwRaWPD.js} +0 -0
  332. /package/client/dist/assets/{addspec-BMtLZLW62.js → addspec-BXjvkh5q.js} +0 -0
  333. /package/client/dist/assets/{addspec-CRuvWn8d.js → addspec-C4bwrmpi.js} +0 -0
  334. /package/client/dist/assets/{addspec-D-JLDsUV.js → addspec-CArHXUDM.js} +0 -0
  335. /package/client/dist/assets/{addspec-mgPOt5q02.js → addspec-DTKJcXiR.js} +0 -0
  336. /package/client/dist/assets/{addspec-DfhDbg5c.js → addspec-DbNBbrha2.js} +0 -0
  337. /package/client/dist/assets/{addspec-CP0HRuxY.js → addspec-htHGZHOe2.js} +0 -0
  338. /package/client/dist/assets/{addspec-bsNDdqRm2.js → addspec-lr_LP6jk.js} +0 -0
  339. /package/client/dist/assets/{agents-78bTZGZh.js → agents-6ZOJXWVk2.js} +0 -0
  340. /package/client/dist/assets/{agents-DDtCJ5M02.js → agents-BZ0XQKjg.js} +0 -0
  341. /package/client/dist/assets/{agents-CWo-KyAB.js → agents-BqWklnHR.js} +0 -0
  342. /package/client/dist/assets/{agents-BUdEi3-N.js → agents-Bz-thHkB2.js} +0 -0
  343. /package/client/dist/assets/{agents-bvyRLczL.js → agents-CJclElx6.js} +0 -0
  344. /package/client/dist/assets/{agents-BpoUaoW92.js → agents-D_l9L0VA.js} +0 -0
  345. /package/client/dist/assets/{agents-BLhWCmDS2.js → agents-u_C0jPB5.js} +0 -0
  346. /package/client/dist/assets/{agentstudio-BMQxc_bz.js → agentstudio-BF19hbXj.js} +0 -0
  347. /package/client/dist/assets/{agentstudio-C_Jq8Tl4.js → agentstudio-BLGvxWBx2.js} +0 -0
  348. /package/client/dist/assets/{agentstudio-Dtq3dGYk.js → agentstudio-D9UKiWTj2.js} +0 -0
  349. /package/client/dist/assets/{agentstudio-CWgXG5W52.js → agentstudio-Drs37nIM.js} +0 -0
  350. /package/client/dist/assets/{agentstudio-sI27TpBu2.js → agentstudio-nEnKkcXM.js} +0 -0
  351. /package/client/dist/assets/{agentstudio-jKMTdT70.js → agentstudio-pyFcSBKy.js} +0 -0
  352. /package/client/dist/assets/{agentstudio-COZriimI2.js → agentstudio-zfTzQh3U.js} +0 -0
  353. /package/client/dist/assets/{aiedit-hknZlly82.js → aiedit-B0KUbpB6.js} +0 -0
  354. /package/client/dist/assets/{aiedit-eMN4eQPF.js → aiedit-BoG24nlg2.js} +0 -0
  355. /package/client/dist/assets/{aiedit-pc5JDUq1.js → aiedit-CjlSRQVP.js} +0 -0
  356. /package/client/dist/assets/{aiedit-B03pD_Na.js → aiedit-DCjA7jLJ.js} +0 -0
  357. /package/client/dist/assets/{aiedit-6CHdb_iv2.js → aiedit-DNXxq_Dr.js} +0 -0
  358. /package/client/dist/assets/{aiedit-igmw-ZoZ2.js → aiedit-DQ-0_pvE.js} +0 -0
  359. /package/client/dist/assets/{aiedit-BtRqRxML.js → aiedit-olnv3QSt2.js} +0 -0
  360. /package/client/dist/assets/{analytics-CCprAqk3.js → analytics-BC7mwR4s.js} +0 -0
  361. /package/client/dist/assets/{analytics-CR1TJqUi2.js → analytics-BiNg8edP.js} +0 -0
  362. /package/client/dist/assets/{analytics-Bj-SmKHa.js → analytics-C0s_T2GN.js} +0 -0
  363. /package/client/dist/assets/{analytics-C5dpiS0o.js → analytics-CdsAPAwZ2.js} +0 -0
  364. /package/client/dist/assets/{analytics-BWMUZ2Pq2.js → analytics-Cr1_q-lC.js} +0 -0
  365. /package/client/dist/assets/{analytics-DfNdXR0y2.js → analytics-IEsKZvpW2.js} +0 -0
  366. /package/client/dist/assets/{analytics-rmHuJv0V.js → analytics-SRPf84OT.js} +0 -0
  367. /package/client/dist/assets/{apex-D_X_8J4d.js → apex-BWPQTe0t.js} +0 -0
  368. /package/client/dist/assets/{atom-one-dark-BMDPFwRZ.js → atom-one-dark-qiGhVQxg.js} +0 -0
  369. /package/client/dist/assets/{attachments-BgBNSqQd.js → attachments-D0wXXt9T2.js} +0 -0
  370. /package/client/dist/assets/{attachments-C9GTTJsx2.js → attachments-DenIjuVI.js} +0 -0
  371. /package/client/dist/assets/{attachments-BjyMmrwE.js → attachments-DhO7LPfy.js} +0 -0
  372. /package/client/dist/assets/{attachments-DPCmkFZu2.js → attachments-DoH5w0x4.js} +0 -0
  373. /package/client/dist/assets/{attachments-DCZOL1RU.js → attachments-Q9yNtiEM.js} +0 -0
  374. /package/client/dist/assets/{attachments-rNn1ekRy2.js → attachments-V_WZAYXx2.js} +0 -0
  375. /package/client/dist/assets/{attachments-DL-bPy9E.js → attachments-eaeScpjM.js} +0 -0
  376. /package/client/dist/assets/{auth-DpZq5p1e.js → auth-D_5bQv5W.js} +0 -0
  377. /package/client/dist/assets/{azcli-PZLnZ3jH.js → azcli-Bc_sGQ0U.js} +0 -0
  378. /package/client/dist/assets/{bat-KlBOxpO4.js → bat-i0X4ZdIN.js} +0 -0
  379. /package/client/dist/assets/{bicep-CgG9oF3V.js → bicep-B5-_aFwp.js} +0 -0
  380. /package/client/dist/assets/{browser-BPkhILSJ.js → browser-C25DkQiO.js} +0 -0
  381. /package/client/dist/assets/{browser-BqLqJVVT.js → browser-CiJ7-L32.js} +0 -0
  382. /package/client/dist/assets/{browser-rWKj-RR_2.js → browser-CvgDySd9.js} +0 -0
  383. /package/client/dist/assets/{browser-Cvg-QHvd2.js → browser-CyR1Kp3m2.js} +0 -0
  384. /package/client/dist/assets/{browser-FRv8P02E.js → browser-DwZljcRh2.js} +0 -0
  385. /package/client/dist/assets/{browser-Cc0iDqUi.js → browser-DxP-alBK.js} +0 -0
  386. /package/client/dist/assets/{browser-BijVYG0Y2.js → browser-rRCwi7Y7.js} +0 -0
  387. /package/client/dist/assets/{cameligo-6R45j6Ph.js → cameligo-DMUM7wLl.js} +0 -0
  388. /package/client/dist/assets/{chat-Bom6bqK3.js → chat-2rIB8HhT.js} +0 -0
  389. /package/client/dist/assets/{chat-vylxqR0P2.js → chat-BR8oe53x2.js} +0 -0
  390. /package/client/dist/assets/{chat-Dnwg-NpQ.js → chat-Bua03tty.js} +0 -0
  391. /package/client/dist/assets/{chat-BgL3zv6x2.js → chat-CZpAQan1.js} +0 -0
  392. /package/client/dist/assets/{chat-DsbwJhus.js → chat-CdY5SGvS.js} +0 -0
  393. /package/client/dist/assets/{chat-Bh3NdgJs.js → chat-DZyu3ImY2.js} +0 -0
  394. /package/client/dist/assets/{chat-DdwZjT8K2.js → chat-D_I7nU5K.js} +0 -0
  395. /package/client/dist/assets/{clojure-CLAAMO7J.js → clojure-Cm7r79vr.js} +0 -0
  396. /package/client/dist/assets/{clsx-CNmW0Nip.js → clsx-CjueKrWZ.js} +0 -0
  397. /package/client/dist/assets/{code-UPVvmAKM2.js → code-76gS0IdL.js} +0 -0
  398. /package/client/dist/assets/{code-BRcDlze52.js → code-C1VwxlML.js} +0 -0
  399. /package/client/dist/assets/{code-CeQ0FZpC.js → code-CnMZyJpr.js} +0 -0
  400. /package/client/dist/assets/{code-DgeHz9CB.js → code-D20ef9Cf2.js} +0 -0
  401. /package/client/dist/assets/{code-DzC1rMk12.js → code-DdOXaWRY.js} +0 -0
  402. /package/client/dist/assets/{code-CwnBPPyA.js → code-Duohuf9l.js} +0 -0
  403. /package/client/dist/assets/{code-BdMy3Ffq.js → code-geHmknB92.js} +0 -0
  404. /package/client/dist/assets/{coffee-AruSygo-.js → coffee-Ba7i2nA0.js} +0 -0
  405. /package/client/dist/assets/{commands-DkU0qliq.js → commands-B9RZO4qS.js} +0 -0
  406. /package/client/dist/assets/{commands-u-4eEHCN.js → commands-BEwbAjtD2.js} +0 -0
  407. /package/client/dist/assets/{commands-JWMfGDmr.js → commands-Bbxu8kLU.js} +0 -0
  408. /package/client/dist/assets/{commands-C4Y3C-PF2.js → commands-CDU3qv68.js} +0 -0
  409. /package/client/dist/assets/{commands-xHDDGfcP2.js → commands-CzezgtQj.js} +0 -0
  410. /package/client/dist/assets/{commands-CSDs9q_2.js → commands-DTQBF_q52.js} +0 -0
  411. /package/client/dist/assets/{commands-RHOchN4n2.js → commands-hOtKuOXc.js} +0 -0
  412. /package/client/dist/assets/{cpp-DEE5r4Pw.js → cpp-C7h46wYY.js} +0 -0
  413. /package/client/dist/assets/{csharp-BVXJUvbK.js → csharp-BKxtCVv1.js} +0 -0
  414. /package/client/dist/assets/{csp-cXOF0JoV.js → csp-bTuwJoIa.js} +0 -0
  415. /package/client/dist/assets/{css-C_zQuAoy.js → css-DIMkf-bt.js} +0 -0
  416. /package/client/dist/assets/{cypher-t95m_Q-O.js → cypher-CVaqCwHa.js} +0 -0
  417. /package/client/dist/assets/{dart-B4r1Tg-e.js → dart-onAF5SnQ.js} +0 -0
  418. /package/client/dist/assets/{dockerfile-6mt7zHQw.js → dockerfile-DZFCIeNp.js} +0 -0
  419. /package/client/dist/assets/{ecl-DEY_Fz_Q.js → ecl-D05T4iGw.js} +0 -0
  420. /package/client/dist/assets/{elixir-R6-q1c5t.js → elixir-6RTg0lbw.js} +0 -0
  421. /package/client/dist/assets/{explore-B7fblXBb2.js → explore-BG99VGpL.js} +0 -0
  422. /package/client/dist/assets/{explore-CNBzrEPS.js → explore-C0TjprJW2.js} +0 -0
  423. /package/client/dist/assets/{explore-C0k8HrkN.js → explore-C2dgEo7q.js} +0 -0
  424. /package/client/dist/assets/{explore-BHtEpoNN2.js → explore-C8NDHbob.js} +0 -0
  425. /package/client/dist/assets/{explore-Dahc1lOx.js → explore-CWWgzQGR.js} +0 -0
  426. /package/client/dist/assets/{explore-D_mWBYCS2.js → explore-CcQXpdtS.js} +0 -0
  427. /package/client/dist/assets/{explore-UvW1jEMa.js → explore-DsXAgnmJ2.js} +0 -0
  428. /package/client/dist/assets/{flow9-CNQvHBOt.js → flow9-C5_-GSwl.js} +0 -0
  429. /package/client/dist/assets/{format-command-CuWuRvka.js → format-command-Dfa1o2d-.js} +0 -0
  430. /package/client/dist/assets/{fsharp-vPZkFXwv.js → fsharp-C8Ef5oNN.js} +0 -0
  431. /package/client/dist/assets/{go-KUzBMYJ_.js → go-C-y9NEjX.js} +0 -0
  432. /package/client/dist/assets/{graphql-C3W0rCsD.js → graphql-fmXr3nnJ.js} +0 -0
  433. /package/client/dist/assets/{hcl-BOOMFSNX.js → hcl-CpzslTdj.js} +0 -0
  434. /package/client/dist/assets/{ini-CFat1WJr.js → ini-sBoK_t0W.js} +0 -0
  435. /package/client/dist/assets/{integrations-TAcYNh0b.js → integrations-CH0zy1Mw2.js} +0 -0
  436. /package/client/dist/assets/{integrations-C1ThkTC52.js → integrations-CIAjhd7L.js} +0 -0
  437. /package/client/dist/assets/{integrations-2YcNyAl3.js → integrations-CZdwOe8A.js} +0 -0
  438. /package/client/dist/assets/{integrations-BXCvXUlG2.js → integrations-Ct7omMld.js} +0 -0
  439. /package/client/dist/assets/{integrations-Crk2rFhL2.js → integrations-D6MSxy3-.js} +0 -0
  440. /package/client/dist/assets/{integrations-Dn4JiGtW.js → integrations-DbZa-Zyc.js} +0 -0
  441. /package/client/dist/assets/{integrations-D9YrJ_Ep.js → integrations-b8pH-vfl2.js} +0 -0
  442. /package/client/dist/assets/{java-Dee-CNAM.js → java-BEtHBSE6.js} +0 -0
  443. /package/client/dist/assets/{jira-D5efgwp_.js → jira-BySfUuQZ.js} +0 -0
  444. /package/client/dist/assets/{jira-bDs7Im2C.js → jira-Byw2C0F8.js} +0 -0
  445. /package/client/dist/assets/{jira-BHAKtc4B.js → jira-CNeYi06w2.js} +0 -0
  446. /package/client/dist/assets/{jira-Be369RIR2.js → jira-D11gPhxz.js} +0 -0
  447. /package/client/dist/assets/{jira-CPpgA-5z2.js → jira-D38Gj7AT.js} +0 -0
  448. /package/client/dist/assets/{jira-uNH1gETL2.js → jira-gAvOyije.js} +0 -0
  449. /package/client/dist/assets/{jira-DS7ownDy.js → jira-pRaL7JQo2.js} +0 -0
  450. /package/client/dist/assets/{julia-CGNlLYuU.js → julia-Bri6UV-V.js} +0 -0
  451. /package/client/dist/assets/{kotlin-CY7lhknJ.js → kotlin-BOotOW0E.js} +0 -0
  452. /package/client/dist/assets/{less-D0KN7hfJ.js → less-B9JPFI3C.js} +0 -0
  453. /package/client/dist/assets/{lexon-CuRJufPs.js → lexon-CfSJPG6W.js} +0 -0
  454. /package/client/dist/assets/{loops-LIgO_-eM.js → loops-CPpgMapU.js} +0 -0
  455. /package/client/dist/assets/{loops-DJNhp17p2.js → loops-CWWbKuVL.js} +0 -0
  456. /package/client/dist/assets/{loops-D6D3XHjj.js → loops-Crc9It8o2.js} +0 -0
  457. /package/client/dist/assets/{loops-CvOWsi5r.js → loops-DH2hZcbK2.js} +0 -0
  458. /package/client/dist/assets/{loops-vhJdbcz92.js → loops-DS5TmvIU2.js} +0 -0
  459. /package/client/dist/assets/{loops-K5_HXfJa.js → loops-P2f0cN8-.js} +0 -0
  460. /package/client/dist/assets/{loops-BvqQ-bHR2.js → loops-fJV-9hH7.js} +0 -0
  461. /package/client/dist/assets/{lua-DHvH62Zf.js → lua-CsQS60Ue.js} +0 -0
  462. /package/client/dist/assets/{m3-tlthQ8Fo.js → m3-D-oSqn_W.js} +0 -0
  463. /package/client/dist/assets/{markdown-Dbx1LgUZ.js → markdown-Cimd5fb3.js} +0 -0
  464. /package/client/dist/assets/{mcp-Dcl-XxkR.js → mcp-Czd_iyp02.js} +0 -0
  465. /package/client/dist/assets/{mcp-XJNRfxVp2.js → mcp-D2pc5GGx.js} +0 -0
  466. /package/client/dist/assets/{mcp-sRmWE8WJ.js → mcp-D4ZjtIx32.js} +0 -0
  467. /package/client/dist/assets/{mcp-DtbxuH3V2.js → mcp-DIfBUv2D2.js} +0 -0
  468. /package/client/dist/assets/{mcp-H-gnQUdN.js → mcp-DsTwmjST.js} +0 -0
  469. /package/client/dist/assets/{mcp-N1nzp8oL.js → mcp-SMkn8boG.js} +0 -0
  470. /package/client/dist/assets/{mcp-CjVKXAhD2.js → mcp-fSfiVYAc.js} +0 -0
  471. /package/client/dist/assets/{mips-D_O3IUz_.js → mips-CIPQ_RoX.js} +0 -0
  472. /package/client/dist/assets/{msdax-C_ZmCnRt.js → msdax-DauUninz.js} +0 -0
  473. /package/client/dist/assets/{mysql-C-dCEYSX.js → mysql-SOo6toE5.js} +0 -0
  474. /package/client/dist/assets/{nav-BElxljIF2.js → nav-BRJBxaGt2.js} +0 -0
  475. /package/client/dist/assets/{nav-COu2E_-L.js → nav-CAGKv3_F.js} +0 -0
  476. /package/client/dist/assets/{nav-BXO7PJJG2.js → nav-CNskRJKQ2.js} +0 -0
  477. /package/client/dist/assets/{nav-CW9pxWfC2.js → nav-DY93irBL.js} +0 -0
  478. /package/client/dist/assets/{nav-CXHUA4uf.js → nav-DhjvYgqt.js} +0 -0
  479. /package/client/dist/assets/{nav-DdHF5VWJ.js → nav-Gb-r4wop.js} +0 -0
  480. /package/client/dist/assets/{nav-C8kOuGh8.js → nav-TvumAFD22.js} +0 -0
  481. /package/client/dist/assets/{objective-c-DZY9Aqxd.js → objective-c-FvmIjYaQ.js} +0 -0
  482. /package/client/dist/assets/{pascal-BVHLXoor.js → pascal-DrH0SRf2.js} +0 -0
  483. /package/client/dist/assets/{pascaligo-XdG_6u3k.js → pascaligo-D-ptJ9y-.js} +0 -0
  484. /package/client/dist/assets/{perl-b9QV0BcD.js → perl-oz_6vUea.js} +0 -0
  485. /package/client/dist/assets/{pgsql-Du5FDOgT.js → pgsql-DTj74zXo.js} +0 -0
  486. /package/client/dist/assets/{php-WJD0z2Z_.js → php-nr791fC2.js} +0 -0
  487. /package/client/dist/assets/{pla-CbciTf2w.js → pla-CopQ2nXW.js} +0 -0
  488. /package/client/dist/assets/{postiats-C1QxmvVq.js → postiats-43DmfD33.js} +0 -0
  489. /package/client/dist/assets/{powerquery-BEYDhPLZ.js → powerquery-D3hlyOfw.js} +0 -0
  490. /package/client/dist/assets/{powershell-CfaYw4XL.js → powershell-DmHpPYUd.js} +0 -0
  491. /package/client/dist/assets/{protobuf-pwZEDOYa.js → protobuf-C531GsRP.js} +0 -0
  492. /package/client/dist/assets/{pug-5ftqSPVe.js → pug-Z5eAx3Zn.js} +0 -0
  493. /package/client/dist/assets/{qsharp-uI6qZMq4.js → qsharp-DkqhCAOL.js} +0 -0
  494. /package/client/dist/assets/{r-BK54P1EG.js → r-BwWrilGY.js} +0 -0
  495. /package/client/dist/assets/{redis-DI0yd7kN.js → redis-ClamHrr6.js} +0 -0
  496. /package/client/dist/assets/{redshift-Dov26eyI.js → redshift-DT7zqm-g.js} +0 -0
  497. /package/client/dist/assets/{restructuredtext-QqJ_1dC-.js → restructuredtext-BYgofb2h.js} +0 -0
  498. /package/client/dist/assets/{ruby-5WjveeVb.js → ruby-DezsRK8O.js} +0 -0
  499. /package/client/dist/assets/{rust-BzVSfqFH.js → rust-DdL9SqIa.js} +0 -0
  500. /package/client/dist/assets/{sb-DMIUfT-3.js → sb-CcwsVR0C.js} +0 -0
  501. /package/client/dist/assets/{scala-CSKA7lqt.js → scala-DHpiXF5c.js} +0 -0
  502. /package/client/dist/assets/{scheme-CojOP2Qc.js → scheme-BeGwcela.js} +0 -0
  503. /package/client/dist/assets/{scss-DoBZGJG6.js → scss-gp-XZpBa.js} +0 -0
  504. /package/client/dist/assets/{settings-1w1O5Og82.js → settings-B9ilOSYO.js} +0 -0
  505. /package/client/dist/assets/{settings-Jc46vUhd.js → settings-BqlWHRHA.js} +0 -0
  506. /package/client/dist/assets/{settings-BSihkr0e.js → settings-D8dlqt4d.js} +0 -0
  507. /package/client/dist/assets/{settings-BiDOB3hV2.js → settings-DAZqjX2a2.js} +0 -0
  508. /package/client/dist/assets/{settings-DNL68KTE.js → settings-DRh39v1S2.js} +0 -0
  509. /package/client/dist/assets/{settings-BoubJhDP2.js → settings-Mj1OJbfd2.js} +0 -0
  510. /package/client/dist/assets/{settings-CB-EZ_W8.js → settings-lRReaSYm.js} +0 -0
  511. /package/client/dist/assets/{setup-D84y3wx-.js → setup-CGZjy2IF2.js} +0 -0
  512. /package/client/dist/assets/{setup-BuQNKztt2.js → setup-CXa0Sx16.js} +0 -0
  513. /package/client/dist/assets/{setup-BwMovM9z2.js → setup-D2ktKKMF2.js} +0 -0
  514. /package/client/dist/assets/{setup-DU0ULX8q2.js → setup-DG2H7ha5.js} +0 -0
  515. /package/client/dist/assets/{setup-BXpw07r-.js → setup-DK5bHDUq2.js} +0 -0
  516. /package/client/dist/assets/{setup-7J8TjG4O.js → setup-DrWlIQTZ.js} +0 -0
  517. /package/client/dist/assets/{setup-BiduSX9d.js → setup-rzHUVgAu.js} +0 -0
  518. /package/client/dist/assets/{shell-YOaE4udl.js → shell-CC2rA5mh.js} +0 -0
  519. /package/client/dist/assets/{solidity-l3jSyVLu.js → solidity-BEEn4gHE.js} +0 -0
  520. /package/client/dist/assets/{sophia-BhSGTaQJ.js → sophia-CRfGWb83.js} +0 -0
  521. /package/client/dist/assets/{sparql-qhk6QD9w.js → sparql-D_Lu-MrJ.js} +0 -0
  522. /package/client/dist/assets/{specs-CWtKcVDC.js → specs-BcyxaCwf2.js} +0 -0
  523. /package/client/dist/assets/{specs-DTplt2XM.js → specs-C1q9geAi.js} +0 -0
  524. /package/client/dist/assets/{specs-PozUNhqb.js → specs-CLZKysq-2.js} +0 -0
  525. /package/client/dist/assets/{specs-C9Sd7BhH2.js → specs-DZMS9o54.js} +0 -0
  526. /package/client/dist/assets/{specs-D2clrt2c2.js → specs-Dm6zUI-R.js} +0 -0
  527. /package/client/dist/assets/{specs-CBDvEYKv.js → specs-V3TvhKLX.js} +0 -0
  528. /package/client/dist/assets/{specs-CIPr9reA2.js → specs-dV3Mq5Mu2.js} +0 -0
  529. /package/client/dist/assets/{sql-DoO3y21-.js → sql-NEE52Syq.js} +0 -0
  530. /package/client/dist/assets/{st-ChZAY8oG.js → st-DbInun42.js} +0 -0
  531. /package/client/dist/assets/{swift-D290Tr7_.js → swift-Bxkupp3x.js} +0 -0
  532. /package/client/dist/assets/{systemverilog-DsxzgJ-o.js → systemverilog-Bz4Y3fRF.js} +0 -0
  533. /package/client/dist/assets/{tcl-BKDUHAAR.js → tcl-DISqw1ZD.js} +0 -0
  534. /package/client/dist/assets/{terminal-De2Brd23.js → terminal-BZiIu3rf.js} +0 -0
  535. /package/client/dist/assets/{terminal-cqfd3EDV.js → terminal-C2NAd7BW2.js} +0 -0
  536. /package/client/dist/assets/{terminal-WSAvHXoU.js → terminal-IahE4LW92.js} +0 -0
  537. /package/client/dist/assets/{terminal-Z7KsDM_n.js → terminal-QpXZU2Bn2.js} +0 -0
  538. /package/client/dist/assets/{terminal-xGbCZJHg.js → terminal-ScjyXNxQ.js} +0 -0
  539. /package/client/dist/assets/{terminal-BRm03iFA2.js → terminal-d7GadXbN.js} +0 -0
  540. /package/client/dist/assets/{terminal-DxQWCqSD2.js → terminal-tITa81iz.js} +0 -0
  541. /package/client/dist/assets/{twig-UpZQaM8G.js → twig-De2hgUGE.js} +0 -0
  542. /package/client/dist/assets/{typespec-DzaKvN8i.js → typespec-B8J7ngcE.js} +0 -0
  543. /package/client/dist/assets/{vb-CZpbb7tk.js → vb-DV3o63ZY.js} +0 -0
  544. /package/client/dist/assets/{wgsl-CNt3OIvl.js → wgsl-DpFanUEy.js} +0 -0
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PrContinuationIsolationError = void 0;
36
37
  exports.launchIsolatedRail = launchIsolatedRail;
37
38
  exports.reconcileRailWorktrees = reconcileRailWorktrees;
38
39
  /**
@@ -41,9 +42,10 @@ exports.reconcileRailWorktrees = reconcileRailWorktrees;
41
42
  * ledger) into a running parallel rail. Gated OFF by default (rail-isolation's
42
43
  * opt-in flag), so this path is inert unless explicitly enabled.
43
44
  *
44
- * Flow: allocate one worktree+branch per ticket → fan out the loop runs IN those
45
- * worktrees (so concurrent AI CLIs never collide) → when ALL runs settle, run the
46
- * sequential validated merge-back on the base repo, then clean up worktrees.
45
+ * Flow: allocate one worktree+branch per independent delivery → fan out the loop
46
+ * runs IN those worktrees (so concurrent AI CLIs never collide) → when ALL runs
47
+ * settle, run the sequential validated merge-back on the base repo, then clean
48
+ * up worktrees. Tickets continuing one PR are grouped into its single checkout.
47
49
  *
48
50
  * The run spawns with cwd = the worktree (and repoDir = the worktree, so
49
51
  * writes/git land there). Because `git worktree add` materializes only TRACKED
@@ -59,6 +61,7 @@ exports.reconcileRailWorktrees = reconcileRailWorktrees;
59
61
  * via the executors' base env (see workspace-resolution.resolveLoopBaseEnv).
60
62
  */
61
63
  const path = __importStar(require("path"));
64
+ const fs = __importStar(require("fs"));
62
65
  const artifact_registry_1 = require("./artifact-registry");
63
66
  const ids_1 = require("./ids");
64
67
  const loop_constants_1 = require("./loop-constants");
@@ -82,7 +85,18 @@ const file_story_1 = require("./file-story");
82
85
  const providers_1 = require("./providers");
83
86
  const pr_publisher_1 = require("./pr-publisher");
84
87
  const active_pr_continuation_1 = require("./active-pr-continuation");
88
+ const pr_lifecycle_1 = require("./pr-lifecycle");
85
89
  const rail_worktree_release_1 = require("./rail-worktree-release");
90
+ /** A PR follow-up may only run on the verified PR branch in a dedicated
91
+ * worktree. Routers must surface this error instead of degrading to shared cwd. */
92
+ class PrContinuationIsolationError extends Error {
93
+ code = 'pr_continuation_isolation_required';
94
+ constructor(message) {
95
+ super(message);
96
+ this.name = 'PrContinuationIsolationError';
97
+ }
98
+ }
99
+ exports.PrContinuationIsolationError = PrContinuationIsolationError;
86
100
  /**
87
101
  * Ticket data for the conventional branch name (pr-naming): title/labels from
88
102
  * the ticket-spec reader, the Jira key resolved with the authoritative
@@ -125,11 +139,86 @@ function commitFailureSummary(result) {
125
139
  const dirty = result.dirty.length > 0 ? `; dirty=${result.dirty.slice(0, 8).join(', ')}` : '';
126
140
  return `${result.error ?? 'worktree still has uncommitted deliverable changes'}${dirty}`;
127
141
  }
142
+ const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
143
+ /** Prove that the linked checkout is on the expected PR branch and that its
144
+ * HEAD is exactly the commit named by refs/heads/<branch>. The handle's branch
145
+ * string alone is insufficient: createWorktree may reuse a stale mounted path. */
146
+ async function verifyContinuationWorktree(git, repoDir, handle, target) {
147
+ if (handle.branch !== target.branch) {
148
+ throw new PrContinuationIsolationError(`PR branch ${target.branch} resolved to mounted branch ${handle.branch}; free the stale worktree and retry`);
149
+ }
150
+ const checkedOut = await git.run(['rev-parse', '--abbrev-ref', 'HEAD'], handle.worktreePath);
151
+ const actualBranch = checkedOut.code === 0 ? checkedOut.stdout.trim() : '';
152
+ if (actualBranch !== target.branch) {
153
+ throw new PrContinuationIsolationError(`worktree ${handle.worktreePath} is on ${actualBranch || 'an unverifiable ref'}, expected PR branch ${target.branch}; fix the checkout and retry`);
154
+ }
155
+ const [head, branchRef] = await Promise.all([
156
+ git.run(['rev-parse', '--verify', 'HEAD'], handle.worktreePath),
157
+ git.run(['rev-parse', '--verify', `refs/heads/${target.branch}`], repoDir),
158
+ ]);
159
+ const headSha = head.code === 0 ? head.stdout.trim() : '';
160
+ const branchSha = branchRef.code === 0 ? branchRef.stdout.trim() : '';
161
+ if (!COMMIT_SHA_RE.test(headSha) || headSha !== branchSha) {
162
+ throw new PrContinuationIsolationError(`worktree HEAD does not match refs/heads/${target.branch}; reconcile the PR branch and retry`);
163
+ }
164
+ return headSha;
165
+ }
166
+ async function readHeadSha(git, cwd) {
167
+ try {
168
+ const result = await git.run(['rev-parse', '--verify', 'HEAD'], cwd);
169
+ const sha = result.code === 0 ? result.stdout.trim() : '';
170
+ return COMMIT_SHA_RE.test(sha) ? sha : null;
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ async function branchHasDelta(git, cwd, baseRef) {
177
+ try {
178
+ const result = await git.run(['diff', '--quiet', `${baseRef}...HEAD`, '--'], cwd);
179
+ if (result.code === 0)
180
+ return false;
181
+ if (result.code === 1)
182
+ return true;
183
+ return null;
184
+ }
185
+ catch {
186
+ return null;
187
+ }
188
+ }
189
+ function errorDetail(err) {
190
+ return err instanceof Error ? err.message : String(err);
191
+ }
192
+ function aggregateImplementationOutcome(results) {
193
+ const succeeded = results.filter((result) => result.implementationOutcome === 'succeeded').length;
194
+ if (succeeded === results.length && results.length > 0)
195
+ return 'succeeded';
196
+ if (succeeded > 0)
197
+ return 'partially_succeeded';
198
+ return results.length > 0 ? 'failed' : 'unknown';
199
+ }
200
+ function branchRecords(results) {
201
+ return results.flatMap((result) => result.run.ticketIds.map((ticketId) => ({
202
+ ticketId,
203
+ branch: result.run.handle.branch,
204
+ // Legacy consumers interpret this as delivery eligibility, not engine truth.
205
+ succeeded: result.deliveryOutcome === 'ready',
206
+ runId: result.run.runId,
207
+ implementationOutcome: result.implementationOutcome,
208
+ deliveryOutcome: result.deliveryOutcome,
209
+ initialSha: result.initialSha,
210
+ finalSha: result.finalSha,
211
+ ...(result.changed === undefined ? {} : { changed: result.changed }),
212
+ failureCode: result.failureCode ?? null,
213
+ branchOwnership: result.run.branchOwnership,
214
+ })));
215
+ }
128
216
  /**
129
217
  * Launch the rail's tickets in isolated worktrees + schedule the merge-back.
130
- * Returns the loop run ids. Throws if worktree allocation fails (the caller then
131
- * falls back to the shared-cwd path) — but only after tearing down any partial
132
- * allocation, so a failure never leaves orphaned worktrees.
218
+ * Returns the loop run ids. Throws if worktree allocation fails, but only after
219
+ * tearing down owned partial allocation. A fresh launch may degrade to shared
220
+ * cwd in the router; a PR continuation raises PrContinuationIsolationError and
221
+ * must fail closed because shared cwd cannot prove which branch receives work.
133
222
  */
134
223
  async function launchIsolatedRail(input, io = {}) {
135
224
  const { ctx, railIndex, ticketIds, loopId, loopName, loopGraph, provider, model, effort } = input;
@@ -151,23 +240,21 @@ async function launchIsolatedRail(input, io = {}) {
151
240
  // every await and late-joining clients hydrate mid-build via GET /rails —
152
241
  // and re-broadcast the durable rail.pr_state snapshot on every mutation.
153
242
  let prDeliveryId = null;
154
- const broadcastPrState = () => {
155
- if (!prDeliveryId)
156
- return;
157
- const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, prDeliveryId);
158
- if (row)
159
- ctx.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(ctx.project.id, (0, rail_pr_store_1.toPrDeliverySnapshot)(row)));
160
- };
243
+ let supersededDelivery = null;
161
244
  // Origin-conversation card sync: when the launch came from an agent-chat
162
245
  // conversation, mirror the row's state into its inline decision card —
163
246
  // 'post' at launch (the user sees "working in an isolated worktree" from
164
247
  // second zero), 'update' on every later transition (settle, alloc-failure).
165
248
  // Null-safe: registry empty in tests / disabled builds; update falls back to
166
249
  // post when no card exists yet.
167
- const syncOriginCard = (verb) => {
168
- if (!prDeliveryId)
250
+ const broadcastDeliveryState = (deliveryId) => {
251
+ const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, deliveryId);
252
+ if (!row)
169
253
  return;
170
- const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, prDeliveryId);
254
+ ctx.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(ctx.project.id, (0, rail_pr_store_1.toPrDeliverySnapshot)(row)));
255
+ };
256
+ const syncDeliveryCard = (deliveryId, verb) => {
257
+ const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, deliveryId);
171
258
  if (!row?.origin_conversation_id)
172
259
  return;
173
260
  const mgr = (0, agent_chat_registry_1.getAgentChatManager)();
@@ -179,11 +266,47 @@ async function launchIsolatedRail(input, io = {}) {
179
266
  else
180
267
  mgr.updatePrDecisionCard(row.origin_conversation_id, envelope);
181
268
  };
269
+ const emitPrDeliveryState = (deliveryId, verb) => {
270
+ broadcastDeliveryState(deliveryId);
271
+ syncDeliveryCard(deliveryId, verb);
272
+ };
273
+ const broadcastPrState = () => {
274
+ if (prDeliveryId)
275
+ broadcastDeliveryState(prDeliveryId);
276
+ };
277
+ const syncOriginCard = (verb) => {
278
+ if (prDeliveryId)
279
+ syncDeliveryCard(prDeliveryId, verb);
280
+ };
281
+ const closeFailedGeneration = (err) => {
282
+ if (!prDeliveryId || (0, rail_pr_store_1.getPrDelivery)(ctx.db, prDeliveryId)?.decision !== 'building')
283
+ return;
284
+ const closed = supersededDelivery
285
+ ? (0, rail_pr_store_1.failPrDeliveryAndRestoreSuperseded)(ctx.db, prDeliveryId, supersededDelivery)
286
+ : (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'discarded', {
287
+ implementationOutcome: 'unknown',
288
+ deliveryOutcome: 'blocked',
289
+ statusCode: 'delivery_failed',
290
+ statusDetail: errorDetail(err),
291
+ });
292
+ if (!closed)
293
+ return;
294
+ try {
295
+ emitPrDeliveryState(prDeliveryId, 'update');
296
+ }
297
+ catch { /* durable row is authoritative */ }
298
+ if (supersededDelivery) {
299
+ try {
300
+ emitPrDeliveryState(supersededDelivery.id, 'update');
301
+ }
302
+ catch { /* durable row is authoritative */ }
303
+ }
304
+ };
182
305
  // Units of isolation: `all` → ONE unit covering every ticket (one worktree, one
183
306
  // run — the batch pipeline handles the tickets internally); `per-ticket` → one
184
307
  // unit per ticket. The branch/ledger key is the unit's primary (first) ticket.
185
308
  const scope = input.scope ?? 'per-ticket';
186
- const units = scope === 'all'
309
+ let units = scope === 'all'
187
310
  ? [{ ticketId: ticketIds[0], ticketIds: [...ticketIds] }]
188
311
  : ticketIds.map((id) => ({ ticketId: id, ticketIds: [id] }));
189
312
  // Effective artifact root for the per-worktree overlay: a RELOCATED project's
@@ -231,187 +354,319 @@ async function launchIsolatedRail(input, io = {}) {
231
354
  let integration;
232
355
  let launchContinuation = null;
233
356
  const allocated = [];
234
- await (0, repo_lock_1.withRepoLock)(baseRepo, async () => {
235
- // Bring the repo's remote-tracking refs up to date BEFORE resolving the
236
- // integration branch or allocating any worktree — otherwise `git worktree
237
- // add -b <branch> <path> <bare-name>` resolves against whatever (possibly
238
- // stale) commit the user's LOCAL branch happens to be at. `fetchOrigin` only
239
- // ever touches `refs/remotes/origin/*`; it never mutates the checked-out
240
- // branch/working tree. De-duped per repo for a short TTL so a "Launch all"
241
- // batch (N independent launch requests for the same repo, serialized by
242
- // this same withRepoLock) performs one real fetch, not one per rail. A
243
- // failed fetch (no network / no remote / auth error) never blocks the
244
- // launch — resolveWorktreeBaseRef below degrades to the local ref.
245
- const fetchResult = await (0, integration_branch_1.fetchOrigin)(git, baseRepo);
246
- // Resolve the project's designated integration branch ONCE, and branch every
247
- // ticket's worktree off it (not the ambient HEAD). Empty setting → auto-resolve
248
- // (repo default → HEAD fallback). See server/integration-branch.ts.
249
- integration = await (0, integration_branch_1.resolveIntegrationBranch)(git, {
250
- repoDir: baseRepo,
251
- projectSetting: (0, db_1.getProjectSettings)(ctx.db).integrationBranch,
252
- });
253
- // Prefer the freshly-fetched remote-tracking ref (origin/<branch>) over the
254
- // bare local name for repo-default/project-setting sources — see
255
- // resolveWorktreeBaseRef for the exact fallback policy. `explicit` is left
256
- // completely untouched (rare, launch-time-chosen override).
257
- const worktreeBaseRef = await (0, integration_branch_1.resolveWorktreeBaseRef)(git, {
258
- repoDir: baseRepo, integration, fetchOk: fetchResult.ok,
259
- });
260
- if (worktreeBaseRef.warning) {
261
- console.warn(`[rail-isolated] ${worktreeBaseRef.warning} (repo ${baseRepo})`);
262
- try {
263
- ctx.broadcast({ type: 'rail.fetch_degraded', projectId: ctx.project.id, railIndex, warning: worktreeBaseRef.warning });
264
- }
265
- catch { /* non-fatal, mirrors notifyOverlayDegraded's broadcast guard below */ }
266
- }
267
- // Active-PR continuation: if a ticket is already parked on_review with a
268
- // matching OPEN GitHub PR (or a Jira-linked in_progress ticket explicitly
269
- // matches an open PR, covering unmapped Jira "Review" statuses), run the next
270
- // pass directly on that PR's head branch. Fresh tickets stay on the normal
271
- // integration-base path. Keep this deliberately single-target for now: one
272
- // rail delivery row can represent one PR URL, so multi-PR batches continue to
273
- // use the existing new-work flow instead of silently mixing PRs.
274
- const continuationTargets = await (0, active_pr_continuation_1.resolveActivePrContinuationTargets)({
275
- db: ctx.db,
276
- git,
277
- exec,
278
- repoDir: baseRepo,
279
- ticketIds: units.map((u) => u.ticketId),
280
- integrationBranch: integration.branch,
281
- fetchOk: fetchResult.ok,
282
- getTicketSpec: (ticketId) => {
357
+ try {
358
+ await (0, repo_lock_1.withRepoLock)(baseRepo, async () => {
359
+ // Bring the repo's remote-tracking refs up to date BEFORE resolving the
360
+ // integration branch or allocating any worktree — otherwise `git worktree
361
+ // add -b <branch> <path> <bare-name>` resolves against whatever (possibly
362
+ // stale) commit the user's LOCAL branch happens to be at. `fetchOrigin` only
363
+ // ever touches `refs/remotes/origin/*`; it never mutates the checked-out
364
+ // branch/working tree. De-duped per repo for a short TTL so a "Launch all"
365
+ // batch (N independent launch requests for the same repo, serialized by
366
+ // this same withRepoLock) performs one real fetch, not one per rail. A
367
+ // failed fetch (no network / no remote / auth error) never blocks the
368
+ // launch — resolveWorktreeBaseRef below degrades to the local ref.
369
+ const fetchResult = await (0, integration_branch_1.fetchOrigin)(git, baseRepo);
370
+ // Resolve the project's designated integration branch ONCE, and branch every
371
+ // ticket's worktree off it (not the ambient HEAD). Empty setting → auto-resolve
372
+ // (repo default → HEAD fallback). See server/integration-branch.ts.
373
+ integration = await (0, integration_branch_1.resolveIntegrationBranch)(git, {
374
+ repoDir: baseRepo,
375
+ projectSetting: (0, db_1.getProjectSettings)(ctx.db).integrationBranch,
376
+ });
377
+ // Prefer the freshly-fetched remote-tracking ref (origin/<branch>) over the
378
+ // bare local name for repo-default/project-setting sources — see
379
+ // resolveWorktreeBaseRef for the exact fallback policy. `explicit` is left
380
+ // completely untouched (rare, launch-time-chosen override).
381
+ const worktreeBaseRef = await (0, integration_branch_1.resolveWorktreeBaseRef)(git, {
382
+ repoDir: baseRepo, integration, fetchOk: fetchResult.ok,
383
+ });
384
+ if (worktreeBaseRef.warning) {
385
+ console.warn(`[rail-isolated] ${worktreeBaseRef.warning} (repo ${baseRepo})`);
283
386
  try {
284
- return ctx.getTicketSpec(ticketId);
387
+ ctx.broadcast({ type: 'rail.fetch_degraded', projectId: ctx.project.id, railIndex, warning: worktreeBaseRef.warning });
285
388
  }
286
- catch {
287
- return undefined;
288
- }
289
- },
290
- });
291
- const uniqueContinuationKeys = new Set(units
292
- .map((u) => continuationTargets.get(u.ticketId))
293
- .filter((t) => !!t)
294
- .map((t) => `${t.prUrl ?? ''}\n${t.branch}`));
295
- if (uniqueContinuationKeys.size === 1 && units.every((u) => continuationTargets.has(u.ticketId))) {
296
- launchContinuation = continuationTargets.get(units[0].ticketId) ?? null;
297
- }
298
- if (prMode) {
299
- prDeliveryId = (0, rail_pr_store_1.createPrDelivery)(ctx.db, {
300
- railIndex,
301
- loopId,
302
- railKey: `${railIndex}-${loopId}`,
303
- ticketIds: [...ticketIds],
304
- baseBranch: launchContinuation?.baseBranch ?? integration.branch,
305
- loopName,
306
- originSurface: input.originSurface ?? 'dashboard',
307
- originConversationId: input.originConversationId ?? null,
308
- }).id;
309
- input.onPrDeliveryCreated?.(prDeliveryId);
310
- if (launchContinuation) {
311
- (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'building', {
312
- branch: launchContinuation.branch,
313
- prUrl: launchContinuation.prUrl,
314
- prNumber: launchContinuation.prNumber,
315
- prState: 'pr-created',
316
- });
389
+ catch { /* non-fatal, mirrors notifyOverlayDegraded's broadcast guard below */ }
317
390
  }
318
- broadcastPrState();
319
- syncOriginCard('post');
320
- }
321
- // Conventional branch naming (pr-naming): `<type>/<ref>-<kebab-title>`, the
322
- // Jira key prevailing over the local id when linked. Collisions with foreign
323
- // branches suffix `-2`… (bounded); a branch a PRIOR rail run allocated for
324
- // the SAME ticket is ours to resume, so it is NOT treated as a collision.
325
- // The integration branch is never used; exhaustion falls back to the legacy
326
- // `sr/<slug>/ticket-<id>` name. Snapshot under the lock so concurrent
327
- // launches see each other's just-created branches.
328
- const takenBranches = await (0, worktree_manager_1.listLocalBranches)(git, baseRepo);
329
- const unitBranchName = (ticketId) => {
330
- const continuation = launchContinuation && continuationTargets.get(ticketId);
331
- if (continuation) {
332
- takenBranches.add(continuation.branch);
333
- return continuation.branch;
334
- }
335
- const preferred = (0, pr_naming_1.ticketBranchName)(unitNamingInput(ctx, ticketId));
336
- const branch = (0, pr_naming_1.resolveCollisionFreeName)(preferred, {
337
- taken: (name) => takenBranches.has(name) && !(0, rail_worktrees_store_1.railWorktreeBranchExistsForTicket)(ctx.db, ticketId, name),
338
- reserved: [integration.branch],
339
- }) ?? (0, worktree_manager_1.worktreeBranch)(slug, ticketId);
340
- takenBranches.add(branch);
341
- return branch;
342
- };
343
- try {
344
- for (const unit of units) {
345
- const continuationTarget = launchContinuation ? continuationTargets.get(unit.ticketId) ?? null : null;
346
- const handle = await create(git, {
391
+ // Active-PR continuation: if a ticket is already parked on_review with a
392
+ // matching OPEN GitHub PR (or a Jira-linked in_progress ticket explicitly
393
+ // matches an open PR, covering unmapped Jira "Review" statuses), run the next
394
+ // pass directly on that PR's head branch. Fresh tickets stay on the normal
395
+ // integration-base path. Keep this deliberately single-target for now: one
396
+ // rail delivery row can represent one PR URL, so multi-PR batches continue to
397
+ // use the existing new-work flow instead of silently mixing PRs.
398
+ const continuationTargets = prMode
399
+ ? await (0, active_pr_continuation_1.resolveActivePrContinuationTargets)({
400
+ db: ctx.db,
401
+ git,
402
+ exec,
347
403
  repoDir: baseRepo,
348
- worktreesRoot,
349
- slug,
350
- ticketId: unit.ticketId,
351
- baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
352
- branch: unitBranchName(unit.ticketId),
353
- refreshFromBaseRef: Boolean(continuationTarget?.baseRef),
354
- });
355
- // Per-run overlay: merge-link the framework surface the checkout didn't
356
- // bring into the worktree (idempotent; resume-safe via its manifest).
357
- let overlayExcludes = [];
358
- try {
359
- const res = overlay({
360
- worktreePath: handle.worktreePath,
361
- sourceRoot: overlaySourceRoot,
362
- providerDir: overlayProviderDir,
363
- instructionsFilename: overlayInstructions,
404
+ ticketIds: units.map((u) => u.ticketId),
405
+ integrationBranch: integration.branch,
406
+ fetchOk: fetchResult.ok,
407
+ getTicketSpec: (ticketId) => {
408
+ try {
409
+ return ctx.getTicketSpec(ticketId);
410
+ }
411
+ catch {
412
+ return undefined;
413
+ }
414
+ },
415
+ })
416
+ : new Map();
417
+ const uniqueContinuationKeys = new Set(units
418
+ .map((u) => continuationTargets.get(u.ticketId))
419
+ .filter((t) => !!t)
420
+ .map((t) => `${t.prUrl ?? ''}\n${t.branch}`));
421
+ if (uniqueContinuationKeys.size === 1 && units.every((u) => continuationTargets.has(u.ticketId))) {
422
+ launchContinuation = continuationTargets.get(units[0].ticketId) ?? null;
423
+ }
424
+ let staleRequiredPr = false;
425
+ if (input.requiredPrContinuation && (!launchContinuation ||
426
+ launchContinuation.branch !== input.requiredPrContinuation.branch ||
427
+ launchContinuation.prUrl !== input.requiredPrContinuation.prUrl ||
428
+ launchContinuation.prNumber !== input.requiredPrContinuation.prNumber)) {
429
+ const observed = await (0, pr_lifecycle_1.observePrLifecycle)(exec, baseRepo, input.requiredPrContinuation.prUrl);
430
+ if (observed.ok && (observed.state === 'CLOSED' || observed.state === 'MERGED')) {
431
+ // The router snapshot was stale, but this is not an isolation failure and
432
+ // must not trap the user in a 409 loop. Supersede that generation under
433
+ // the same expected-active CAS, then allocate a genuinely fresh branch.
434
+ staleRequiredPr = true;
435
+ launchContinuation = null;
436
+ }
437
+ else {
438
+ const reason = observed.ok
439
+ ? `the PR no longer matches its recorded head/base (${observed.state})`
440
+ : `GitHub lifecycle could not be confirmed (${observed.detail})`;
441
+ throw new PrContinuationIsolationError(`cannot safely continue PR branch ${input.requiredPrContinuation.branch}: ${reason}; retry after the PR is verifiably open`);
442
+ }
443
+ }
444
+ // A git branch can only be checked out by one worktree. When several rail
445
+ // tickets all continue the same PR, make them one atomic batch run in one
446
+ // checkout instead of attempting N worktrees for the same branch (the second
447
+ // `git worktree add` necessarily fails). `ticketIds` preserves the full rail
448
+ // scope for {{spec.ids}} / ticket commands and completion bookkeeping.
449
+ if (launchContinuation && units.length > 1) {
450
+ units = [{
451
+ ticketId: units[0].ticketId,
452
+ ticketIds: units.flatMap((unit) => unit.ticketIds),
453
+ }];
454
+ }
455
+ if (prMode) {
456
+ const generation = (0, rail_pr_store_1.createPrDeliveryGeneration)(ctx.db, {
457
+ railIndex,
458
+ loopId,
459
+ railKey: `${railIndex}-${loopId}`,
460
+ ticketIds: [...ticketIds],
461
+ baseBranch: launchContinuation?.baseBranch ?? integration.branch,
462
+ loopName,
463
+ originSurface: input.originSurface ?? 'dashboard',
464
+ originConversationId: input.originConversationId ?? null,
465
+ isContinuation: Boolean(launchContinuation),
466
+ }, input.requiredPrContinuation
467
+ ? { id: input.requiredPrContinuation.deliveryId, decision: input.requiredPrContinuation.decision }
468
+ : null);
469
+ prDeliveryId = generation.delivery.id;
470
+ supersededDelivery = generation.superseded;
471
+ input.onPrDeliveryCreated?.(prDeliveryId);
472
+ if (launchContinuation) {
473
+ (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'building', {
474
+ branch: launchContinuation.branch,
475
+ prUrl: launchContinuation.prUrl,
476
+ prNumber: launchContinuation.prNumber,
477
+ prState: 'pr-created',
478
+ isContinuation: true,
479
+ supersedesDeliveryId: supersededDelivery?.id ?? null,
480
+ });
481
+ }
482
+ else if (staleRequiredPr) {
483
+ // This replacement deliberately starts a new PR lineage: the stale PR
484
+ // remains historical and its head is never borrowed. Ownership was
485
+ // already persisted atomically by createPrDeliveryGeneration; this
486
+ // transition only adds the user-facing explanation.
487
+ (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'building', {
488
+ supersedesDeliveryId: supersededDelivery?.id ?? null,
489
+ statusDetail: 'The previous PR was no longer open; this implementation will use a fresh branch and PR.',
364
490
  });
365
- overlayExcludes = res.createdPaths;
366
- if (res.warnings.length > 0)
367
- notifyOverlayDegraded(unit.ticketId, res.warnings);
368
491
  }
369
- catch (err) {
370
- // applyWorktreeOverlay never throws; this guards injected test doubles
371
- // and future edits — the spawn proceeds regardless.
372
- notifyOverlayDegraded(unit.ticketId, [err instanceof Error ? err.message : String(err)]);
492
+ if (supersededDelivery) {
493
+ try {
494
+ emitPrDeliveryState(supersededDelivery.id, 'update');
495
+ }
496
+ catch { /* durable supersession is authoritative */ }
497
+ }
498
+ try {
499
+ broadcastPrState();
500
+ }
501
+ catch { /* hydration can recover */ }
502
+ try {
503
+ syncOriginCard('post');
373
504
  }
374
- // Code-Explorer provenance: freeze the worktree's pre-run state (HEAD sha
375
- // + the overlay's untracked set) so the settle diff attributes exactly
376
- // the run's own writes. Best-effort — a snapshot failure only loses
377
- // provenance, never the run.
378
- let provenanceSnapshot = null;
379
- if ((0, feature_flags_1.isCodeExplorerEnabled)()) {
505
+ catch { /* hydration can recover */ }
506
+ }
507
+ // Conventional branch naming (pr-naming): `<type>/<ref>-<kebab-title>`, the
508
+ // Jira key prevailing over the local id when linked. Collisions with foreign
509
+ // branches suffix `-2`… (bounded); a branch a PRIOR rail run allocated for
510
+ // the SAME ticket is ours to resume, so it is NOT treated as a collision.
511
+ // The integration branch is never used; exhaustion falls back to the legacy
512
+ // `sr/<slug>/ticket-<id>` name. Snapshot under the lock so concurrent
513
+ // launches see each other's just-created branches.
514
+ const takenBranches = await (0, worktree_manager_1.listLocalBranches)(git, baseRepo);
515
+ const preexistingBranches = new Set(takenBranches);
516
+ const unitBranchName = (ticketId) => {
517
+ const continuation = launchContinuation && continuationTargets.get(ticketId);
518
+ if (continuation) {
519
+ takenBranches.add(continuation.branch);
520
+ return { branch: continuation.branch, ownership: 'borrowed-pr' };
521
+ }
522
+ const preferred = (0, pr_naming_1.ticketBranchName)(unitNamingInput(ctx, ticketId));
523
+ const branch = (0, pr_naming_1.resolveCollisionFreeName)(preferred, {
524
+ taken: (name) => takenBranches.has(name) && !(0, rail_worktrees_store_1.railWorktreeBranchExistsForTicket)(ctx.db, ticketId, name),
525
+ reserved: [integration.branch],
526
+ }) ?? (0, worktree_manager_1.worktreeBranch)(slug, ticketId);
527
+ takenBranches.add(branch);
528
+ return { branch, ownership: preexistingBranches.has(branch) ? 'preexisting' : 'created' };
529
+ };
530
+ try {
531
+ for (const unit of units) {
532
+ const continuationTarget = launchContinuation ? continuationTargets.get(unit.ticketId) ?? null : null;
533
+ const branchPlan = unitBranchName(unit.ticketId);
534
+ let handle = null;
535
+ let branchOwnership = branchPlan.ownership;
536
+ let worktreeOwnership = 'created';
537
+ let initialSha = null;
380
538
  try {
381
- provenanceSnapshot = (io.snapshot ?? file_provenance_1.snapshotWorkingTree)(handle.worktreePath);
539
+ handle = await create(git, {
540
+ repoDir: baseRepo,
541
+ worktreesRoot,
542
+ slug,
543
+ ticketId: unit.ticketId,
544
+ baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
545
+ branch: branchPlan.branch,
546
+ refreshFromBaseRef: Boolean(continuationTarget?.baseRef),
547
+ });
548
+ worktreeOwnership = handle.worktreeCreated === false ? 'preexisting' : 'created';
549
+ if (!continuationTarget && (handle.branch !== branchPlan.branch || handle.branchCreated === false)) {
550
+ branchOwnership = 'preexisting';
551
+ }
552
+ if (continuationTarget) {
553
+ initialSha = await verifyContinuationWorktree(git, baseRepo, handle, continuationTarget);
554
+ }
555
+ else {
556
+ initialSha = await readHeadSha(git, handle.worktreePath);
557
+ }
558
+ // Per-run overlay: merge-link the framework surface the checkout didn't
559
+ // bring into the worktree (idempotent; resume-safe via its manifest).
560
+ let overlayExcludes = [];
561
+ try {
562
+ const res = overlay({
563
+ worktreePath: handle.worktreePath,
564
+ sourceRoot: overlaySourceRoot,
565
+ providerDir: overlayProviderDir,
566
+ instructionsFilename: overlayInstructions,
567
+ });
568
+ overlayExcludes = res.createdPaths;
569
+ if (res.warnings.length > 0)
570
+ notifyOverlayDegraded(unit.ticketId, res.warnings);
571
+ }
572
+ catch (err) {
573
+ // applyWorktreeOverlay never throws; this guards injected test doubles
574
+ // and future edits — the spawn proceeds regardless.
575
+ notifyOverlayDegraded(unit.ticketId, [err instanceof Error ? err.message : String(err)]);
576
+ }
577
+ // Code-Explorer provenance: freeze the worktree's pre-run state (HEAD sha
578
+ // + the overlay's untracked set) so the settle diff attributes exactly
579
+ // the run's own writes. Best-effort — a snapshot failure only loses
580
+ // provenance, never the run.
581
+ let provenanceSnapshot = null;
582
+ if ((0, feature_flags_1.isCodeExplorerEnabled)()) {
583
+ try {
584
+ provenanceSnapshot = (io.snapshot ?? file_provenance_1.snapshotWorkingTree)(handle.worktreePath);
585
+ }
586
+ catch (err) {
587
+ console.warn(`[rail-isolated] provenance snapshot failed: ${err.message}`);
588
+ }
589
+ }
590
+ const runId = (0, ids_1.newId)();
591
+ const ledgerId = (0, ids_1.newId)();
592
+ (0, rail_worktrees_store_1.createRailWorktree)(ctx.db, {
593
+ id: ledgerId, railIndex, ticketId: unit.ticketId, runId,
594
+ branch: handle.branch, worktreePath: handle.worktreePath,
595
+ });
596
+ allocated.push({
597
+ ticketId: unit.ticketId,
598
+ ticketIds: unit.ticketIds,
599
+ runId,
600
+ ledgerId,
601
+ handle,
602
+ overlayExcludes,
603
+ provenanceSnapshot,
604
+ continuationTarget,
605
+ baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
606
+ initialSha,
607
+ branchOwnership,
608
+ worktreeOwnership,
609
+ });
382
610
  }
383
611
  catch (err) {
384
- console.warn(`[rail-isolated] provenance snapshot failed: ${err.message}`);
612
+ // A handle that never reached `allocated` is still our responsibility,
613
+ // but only unmount/delete resources this call actually created.
614
+ if (handle && worktreeOwnership === 'created') {
615
+ await remove(git, {
616
+ repoDir: baseRepo,
617
+ worktreePath: handle.worktreePath,
618
+ branch: handle.branch,
619
+ deleteBranch: branchOwnership === 'created',
620
+ }).catch(() => { });
621
+ }
622
+ throw err;
385
623
  }
386
624
  }
387
- const runId = (0, ids_1.newId)();
388
- const ledgerId = (0, ids_1.newId)();
389
- (0, rail_worktrees_store_1.createRailWorktree)(ctx.db, {
390
- id: ledgerId, railIndex, ticketId: unit.ticketId, runId,
391
- branch: handle.branch, worktreePath: handle.worktreePath,
392
- });
393
- allocated.push({ ticketId: unit.ticketId, ticketIds: unit.ticketIds, runId, ledgerId, handle, overlayExcludes, provenanceSnapshot, continuationTarget });
394
625
  }
395
- }
396
- catch (err) {
397
- for (const a of allocated) {
398
- await remove(git, { repoDir: baseRepo, worktreePath: a.handle.worktreePath, branch: a.handle.branch }).catch(() => { });
399
- }
400
- // Close the just-inserted decision row (kept as 'discarded' for audit, never
401
- // deleted) so a wedged 'building' row can never block relaunching the slot.
402
- // Router-driven PR continuations may intentionally fall back to shared-cwd
403
- // on the same PR branch; in that case the router preserves and updates the
404
- // row as the live iteration card instead of showing a false discard.
405
- if (!input.preservePrDeliveryOnAllocationFailure && prDeliveryId && (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'discarded')) {
406
- broadcastPrState();
407
- syncOriginCard('update');
626
+ catch (err) {
627
+ for (const a of allocated) {
628
+ if (a.worktreeOwnership === 'created') {
629
+ await remove(git, {
630
+ repoDir: baseRepo,
631
+ worktreePath: a.handle.worktreePath,
632
+ branch: a.handle.branch,
633
+ deleteBranch: a.branchOwnership === 'created',
634
+ }).catch(() => { });
635
+ }
636
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, 'failed');
637
+ }
638
+ // Close the failed generation. A continuation atomically restores the exact
639
+ // predecessor it superseded; a stale concurrent generation wins instead of
640
+ // letting this catch overwrite it.
641
+ closeFailedGeneration(err);
642
+ if (launchContinuation) {
643
+ if (err instanceof PrContinuationIsolationError)
644
+ throw err;
645
+ const detail = err instanceof Error ? err.message : String(err);
646
+ throw new PrContinuationIsolationError(`cannot allocate a verified worktree for PR branch ${launchContinuation.branch}: ${detail}; free the branch checkout and retry`);
647
+ }
648
+ throw err;
408
649
  }
409
- throw err;
650
+ }); // withRepoLock — allocation done; the fan-out below runs in parallel.
651
+ }
652
+ catch (err) {
653
+ closeFailedGeneration(err);
654
+ // Auto-detected GitHub continuations do not have a router-side delivery
655
+ // contract yet. Preserve the fail-closed signal for errors that happen
656
+ // after detection but before the allocation catch (for example DB setup).
657
+ // TypeScript does not observe assignments performed inside withRepoLock's
658
+ // async callback, so retain the explicit runtime type at this boundary.
659
+ const detectedContinuation = launchContinuation;
660
+ if (detectedContinuation && !(err instanceof PrContinuationIsolationError)) {
661
+ const detail = err instanceof Error ? err.message : String(err);
662
+ throw new PrContinuationIsolationError(`cannot prepare a verified worktree for PR branch ${detectedContinuation.branch}: ${detail}`);
410
663
  }
411
- }); // withRepoLock — allocation done; the fan-out below runs in parallel.
412
- // 2. Fan out: one loop run per ticket, spawning IN its worktree. In PR mode a
413
- // COMPLETED run's tickets park at on_review (the user decides done vs
414
- // discard via the PR flow); failure outcomes ignore the field.
664
+ throw err;
665
+ }
666
+ // 2. Fan out: one loop run per independent worktree. Same-PR tickets form one
667
+ // atomic batch run because git cannot mount one branch in multiple linked
668
+ // worktrees. In PR mode a COMPLETED run's tickets park at on_review (the
669
+ // user decides done vs discard via the PR flow); failures ignore the field.
415
670
  const runFinishedOpts = { ticketCompletionStatus: prMode ? 'on_review' : 'done' };
416
671
  // Code-Explorer provenance at settle: diff the worktree against its pre-run
417
672
  // snapshot and record file_provenance/story rows (keyed by runId), exactly
@@ -427,58 +682,198 @@ async function launchIsolatedRail(input, io = {}) {
427
682
  if (provenanceRecorded.has(a.runId))
428
683
  return;
429
684
  provenanceRecorded.add(a.runId);
430
- recordProvenance({
431
- db: ctx.db,
432
- projectId: ctx.project.id,
433
- runId: a.runId,
434
- ticketId: a.ticketId,
435
- repoDir: a.handle.worktreePath,
436
- snapshot: a.provenanceSnapshot,
437
- broadcast: (msg) => ctx.broadcast(msg),
438
- });
685
+ try {
686
+ recordProvenance({
687
+ db: ctx.db,
688
+ projectId: ctx.project.id,
689
+ runId: a.runId,
690
+ ticketId: a.ticketId,
691
+ repoDir: a.handle.worktreePath,
692
+ snapshot: a.provenanceSnapshot,
693
+ broadcast: (msg) => ctx.broadcast(msg),
694
+ });
695
+ }
696
+ catch (err) {
697
+ console.warn(`[rail-isolated] provenance recording failed for ${a.runId}: ${errorDetail(err)}`);
698
+ }
699
+ };
700
+ const markWorktree = (a, state) => {
701
+ try {
702
+ return (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, state)
703
+ ? null
704
+ : `worktree ledger ${a.ledgerId} no longer exists`;
705
+ }
706
+ catch (err) {
707
+ return errorDetail(err);
708
+ }
709
+ };
710
+ /** This function resolves for every allocated unit. Engine truth is captured
711
+ * before any delivery effect and is the only value sent to the deferred
712
+ * terminal callback. Delivery failures become blocked evidence instead. */
713
+ const settleAllocatedRun = async (a, enginePromise) => {
714
+ let actualOutcome = 'failed';
715
+ let engineFailure;
716
+ try {
717
+ const result = await enginePromise;
718
+ actualOutcome = result.outcome;
719
+ }
720
+ catch (err) {
721
+ engineFailure = errorDetail(err);
722
+ console.error(`[rail-isolated] loop run ${a.runId} rejected: ${engineFailure}`);
723
+ }
724
+ const implementationOutcome = actualOutcome === 'success' ? 'succeeded' : 'failed';
725
+ recordRunProvenance(a);
726
+ let callbackFailure;
727
+ try {
728
+ ctx.onLoopRunFinished(a.runId, actualOutcome, runFinishedOpts);
729
+ }
730
+ catch (err) {
731
+ callbackFailure = errorDetail(err);
732
+ console.error(`[rail-isolated] terminal callback failed for ${a.runId}: ${callbackFailure}`);
733
+ }
734
+ let commit;
735
+ try {
736
+ commit = await (0, worktree_manager_1.commitWorktreeAndVerify)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId, implementationOutcome === 'failed'), a.overlayExcludes);
737
+ }
738
+ catch (err) {
739
+ commit = { staged: false, committed: false, clean: false, dirty: [], error: errorDetail(err) };
740
+ }
741
+ if (!commit.clean) {
742
+ const detail = commitFailureSummary(commit);
743
+ console.error(`[rail-isolated] run ${a.runId} left an unsafe worktree: ${detail}`);
744
+ markWorktree(a, 'needs-review');
745
+ return {
746
+ run: a,
747
+ implementationOutcome,
748
+ deliveryOutcome: 'blocked',
749
+ initialSha: a.initialSha,
750
+ finalSha: await readHeadSha(git, a.handle.worktreePath),
751
+ failureCode: 'commit_failed',
752
+ failureDetail: detail,
753
+ safeToRelease: false,
754
+ };
755
+ }
756
+ let finalSha = null;
757
+ let verificationFailure;
758
+ if (a.continuationTarget) {
759
+ try {
760
+ finalSha = await verifyContinuationWorktree(git, baseRepo, a.handle, a.continuationTarget);
761
+ }
762
+ catch (err) {
763
+ verificationFailure = errorDetail(err);
764
+ }
765
+ }
766
+ else {
767
+ finalSha = await readHeadSha(git, a.handle.worktreePath);
768
+ }
769
+ const changed = commit.committed || Boolean(a.initialSha && finalSha && a.initialSha !== finalSha);
770
+ // A successful commit without a provable object is not retryable or safe to
771
+ // detach. A continuation additionally requires HEAD == refs/heads/<branch>.
772
+ if (verificationFailure || !finalSha || (!commit.committed && a.branchOwnership === 'created' && !a.initialSha)) {
773
+ const detail = verificationFailure
774
+ ?? (!finalSha
775
+ ? 'the final HEAD object could not be verified'
776
+ : 'the initial HEAD object was not captured, so a no-change result cannot be proven');
777
+ console.error(`[rail-isolated] run ${a.runId} finished on an unverified ref: ${detail}`);
778
+ markWorktree(a, 'needs-review');
779
+ return {
780
+ run: a,
781
+ implementationOutcome,
782
+ deliveryOutcome: 'blocked',
783
+ initialSha: a.initialSha,
784
+ finalSha,
785
+ changed,
786
+ failureCode: 'branch_verification_failed',
787
+ failureDetail: detail,
788
+ safeToRelease: false,
789
+ };
790
+ }
791
+ // Failed clean units remain `building` until aggregate cleanup removes the
792
+ // mount and atomically terminalizes the ledger as failed. Marking them
793
+ // terminal first would make idempotent cleanup skip a still-mounted path.
794
+ const ledgerFailure = implementationOutcome === 'succeeded'
795
+ ? (markWorktree(a, 'built') ?? undefined)
796
+ : undefined;
797
+ const settlementFailure = callbackFailure ?? ledgerFailure;
798
+ if (settlementFailure) {
799
+ markWorktree(a, 'needs-review');
800
+ return {
801
+ run: a,
802
+ implementationOutcome,
803
+ deliveryOutcome: 'blocked',
804
+ initialSha: a.initialSha,
805
+ finalSha,
806
+ changed,
807
+ failureCode: 'settlement_interrupted',
808
+ failureDetail: settlementFailure,
809
+ safeToRelease: false,
810
+ };
811
+ }
812
+ if (implementationOutcome === 'failed') {
813
+ return {
814
+ run: a,
815
+ implementationOutcome,
816
+ deliveryOutcome: 'not_started',
817
+ initialSha: a.initialSha,
818
+ finalSha,
819
+ changed,
820
+ ...(engineFailure ? { failureDetail: engineFailure } : {}),
821
+ safeToRelease: true,
822
+ };
823
+ }
824
+ // A resumed non-PR branch may already contain reviewable commits from an
825
+ // earlier attempt, even when this iteration itself did not add one.
826
+ let resumedBranchDelta = null;
827
+ if (!changed && a.continuationTarget === null && a.branchOwnership === 'preexisting') {
828
+ resumedBranchDelta = await branchHasDelta(git, a.handle.worktreePath, a.baseRef);
829
+ if (resumedBranchDelta === null) {
830
+ const detail = `cannot prove whether resumed branch ${a.handle.branch} has commits ahead of ${a.baseRef}`;
831
+ markWorktree(a, 'needs-review');
832
+ return {
833
+ run: a,
834
+ implementationOutcome,
835
+ deliveryOutcome: 'blocked',
836
+ initialSha: a.initialSha,
837
+ finalSha,
838
+ changed,
839
+ failureCode: 'branch_verification_failed',
840
+ failureDetail: detail,
841
+ safeToRelease: false,
842
+ };
843
+ }
844
+ }
845
+ const noChanges = !changed && Boolean(a.initialSha && finalSha && a.initialSha === finalSha && (a.continuationTarget !== null ||
846
+ a.branchOwnership === 'created' ||
847
+ resumedBranchDelta === false));
848
+ return {
849
+ run: a,
850
+ implementationOutcome,
851
+ deliveryOutcome: noChanges ? 'no_changes' : 'ready',
852
+ initialSha: a.initialSha,
853
+ finalSha,
854
+ changed,
855
+ safeToRelease: true,
856
+ };
439
857
  };
440
858
  const runPromises = [];
441
859
  for (const a of allocated) {
442
- ctx.railLoopRuns.set(a.runId, { railIndex, ticketIds: a.ticketIds });
860
+ ctx.railLoopRuns.set(a.runId, {
861
+ railIndex,
862
+ ticketIds: a.ticketIds,
863
+ requiresTerminalIntent: true,
864
+ });
443
865
  const spec = ctx.getTicketSpec(a.ticketId);
444
- const p = ctx.loopRunManager
445
- .run({
866
+ const enginePromise = ctx.loopRunManager.run({
446
867
  runId: a.runId, loopId, loopName, graph: loopGraph, projectId: ctx.project.id,
447
868
  cwd: a.handle.worktreePath, repoDir: a.handle.worktreePath,
448
869
  isolation: { branch: a.handle.branch, worktreePath: a.handle.worktreePath },
449
870
  railIndex, ticketId: a.ticketId,
450
871
  spec: spec ? { ...spec, ticketIds: a.ticketIds } : { ticketIds: a.ticketIds },
872
+ ticketCompletionStatus: runFinishedOpts.ticketCompletionStatus,
873
+ deferTerminalOutcome: true,
451
874
  constants, provider, model, effort,
452
- })
453
- .then(async (r) => {
454
- const succeeded = r.outcome === 'success';
455
- recordRunProvenance(a);
456
- // Commit the run's work to its branch so PR creation/merge-back can
457
- // trust the branch as the durable source of truth. A "successful" loop
458
- // with deliverable local changes still dirty is treated as failed
459
- // instead of surfacing a Create PR card that cannot ship the work.
460
- const commit = await (0, worktree_manager_1.commitWorktreeAndVerify)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId), a.overlayExcludes);
461
- const deliverable = succeeded && commit.clean;
462
- if (succeeded && !commit.clean) {
463
- console.error(`[rail-isolated] run ${a.runId} finished success but commit verification failed: ${commitFailureSummary(commit)}`);
464
- }
465
- ctx.onLoopRunFinished(r.runId, deliverable ? 'success' : 'failed', runFinishedOpts);
466
- (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, deliverable ? 'built' : 'failed');
467
- return { run: a, succeeded: deliverable };
468
- })
469
- .catch(async (err) => {
470
- console.error('[rail-isolated] loop run failed:', err);
471
- ctx.onLoopRunFinished(a.runId, 'failed', runFinishedOpts);
472
- recordRunProvenance(a);
473
- // Commit partial work too → durable in git → resumable on re-launch.
474
- const commit = await (0, worktree_manager_1.commitWorktreeAndVerify)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId, true), a.overlayExcludes);
475
- if (!commit.clean) {
476
- console.error(`[rail-isolated] failed run ${a.runId} left uncommitted deliverable changes: ${commitFailureSummary(commit)}`);
477
- }
478
- (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, 'failed');
479
- return { run: a, succeeded: false };
480
875
  });
481
- runPromises.push(p);
876
+ runPromises.push(settleAllocatedRun(a, enginePromise));
482
877
  try {
483
878
  ctx.jiraSyncManager.onRailLaunch(a.ticketIds, a.runId);
484
879
  }
@@ -500,11 +895,28 @@ async function launchIsolatedRail(input, io = {}) {
500
895
  // work isolated on its branches and mark the decision row on_review —
501
896
  // the draft PR is only created when the user clicks [Create PR] on the
502
897
  // pr-decision endpoint (ask-first, safe-pr-review-flow).
503
- void Promise.allSettled(runPromises).then(async (settled) => {
504
- const results = settled.flatMap((s) => (s.status === 'fulfilled' ? [s.value] : []));
505
- const branches = results.map((r) => ({
506
- ticketId: r.run.ticketId, branch: r.run.handle.branch, succeeded: r.succeeded,
507
- }));
898
+ const guardedRunPromises = runPromises.map((promise, index) => promise.catch(async (err) => {
899
+ const run = allocated[index];
900
+ const detail = errorDetail(err);
901
+ console.error(`[rail-isolated] unexpected settlement rejection for ${run.runId}: ${detail}`);
902
+ const durable = ctx.db.prepare('SELECT final_outcome FROM loop_runs WHERE id = ?').get(run.runId);
903
+ const implementationOutcome = durable?.final_outcome === 'success' ? 'succeeded' : 'failed';
904
+ markWorktree(run, 'needs-review');
905
+ return {
906
+ run,
907
+ implementationOutcome,
908
+ deliveryOutcome: 'blocked',
909
+ initialSha: run.initialSha,
910
+ finalSha: await readHeadSha(git, run.handle.worktreePath),
911
+ failureCode: 'settlement_interrupted',
912
+ failureDetail: detail,
913
+ safeToRelease: false,
914
+ };
915
+ }));
916
+ void Promise.all(guardedRunPromises).then(async (settledResults) => {
917
+ // Cardinality is invariant: one structured result per allocated unit. The
918
+ // guarded promises above synthesize a blocked record for any rejection.
919
+ let results = settledResults;
508
920
  if (prMode) {
509
921
  // Build-settle: persist the per-unit branch outcomes + this launch's
510
922
  // worktree ledger ids on the row (the deferred create-pr / discard actions
@@ -513,71 +925,301 @@ async function launchIsolatedRail(input, io = {}) {
513
925
  // keep the implementation card visible as a failed job state with run-log
514
926
  // chips instead of silently auto-discarding or leaving it at "building".
515
927
  const worktreeIds = allocated.map((a) => a.ledgerId);
516
- const anySucceeded = results.some((r) => r.succeeded);
517
- const settledContinuation = anySucceeded ? launchContinuation : null;
928
+ let deliverySha = null;
518
929
  let continuationPushFailed = false;
519
930
  let continuationPushFailureReason = null;
520
- if (settledContinuation) {
521
- const pushed = await (0, pr_publisher_1.pushBranch)(exec, {
522
- repoDir: baseRepo,
523
- branch: settledContinuation.branch,
524
- baseBranch: settledContinuation.baseBranch,
525
- });
526
- if (pushed.state === 'local-only') {
931
+ let continuationNeedsNewPr = false;
932
+ let continuationExactPushCompleted = false;
933
+ let continuationRemoteIsDraft = launchContinuation?.isDraft ?? null;
934
+ let continuationMergedWithSha = false;
935
+ let continuationClosedWithSha = false;
936
+ let continuationLifecycleBlocked = false;
937
+ const unchangedContinuation = launchContinuation
938
+ ? results.find((result) => result.run.continuationTarget && result.deliveryOutcome === 'no_changes')
939
+ : undefined;
940
+ if (unchangedContinuation?.finalSha)
941
+ deliverySha = unchangedContinuation.finalSha;
942
+ // Revalidate immediately before an existing-PR push. Settlement's SHA is
943
+ // immutable delivery evidence; a moved ref blocks instead of substituting
944
+ // a newer object behind the user's back.
945
+ if (launchContinuation) {
946
+ const candidateIndex = results.findIndex((result) => result.deliveryOutcome === 'ready' && result.run.continuationTarget !== null);
947
+ if (candidateIndex >= 0) {
948
+ const candidate = results[candidateIndex];
949
+ try {
950
+ const verified = await verifyContinuationWorktree(git, baseRepo, candidate.run.handle, candidate.run.continuationTarget);
951
+ if (!candidate.finalSha || verified !== candidate.finalSha) {
952
+ throw new PrContinuationIsolationError('PR branch moved after settlement; refusing to push an unverified object');
953
+ }
954
+ deliverySha = candidate.finalSha;
955
+ }
956
+ catch (err) {
957
+ const detail = errorDetail(err);
958
+ markWorktree(candidate.run, 'needs-review');
959
+ results = results.map((result, resultIndex) => resultIndex === candidateIndex
960
+ ? {
961
+ ...result,
962
+ deliveryOutcome: 'blocked',
963
+ failureCode: 'branch_verification_failed',
964
+ failureDetail: detail,
965
+ safeToRelease: false,
966
+ }
967
+ : result);
968
+ }
969
+ }
970
+ }
971
+ const implementationOutcome = aggregateImplementationOutcome(results);
972
+ let ready = results.filter((result) => result.deliveryOutcome === 'ready');
973
+ let blocked = results.filter((result) => result.deliveryOutcome === 'blocked');
974
+ let noChanges = results.filter((result) => result.deliveryOutcome === 'no_changes');
975
+ const continuationHadReadyWork = ready.length > 0;
976
+ if (launchContinuation && deliverySha && (continuationHadReadyWork || noChanges.length > 0)) {
977
+ const beforePush = await (0, pr_lifecycle_1.observePrLifecycle)(exec, baseRepo, launchContinuation.prUrl, deliverySha);
978
+ if (!beforePush.ok) {
527
979
  continuationPushFailed = true;
528
- continuationPushFailureReason = pushed.reason;
529
- console.warn(`[rail-isolated] existing PR follow-up push failed for ${settledContinuation.branch}: ${continuationPushFailureReason}`);
980
+ continuationPushFailureReason = `could not confirm the existing PR is open before push: ${beforePush.detail}`;
981
+ }
982
+ else if (!(0, pr_lifecycle_1.isExactOpenPr)(beforePush, launchContinuation.branch, launchContinuation.baseBranch)) {
983
+ if (beforePush.state === 'MERGED' && beforePush.includesExpectedSha === true) {
984
+ // Another actor delivered the exact object before our push. Keep the
985
+ // old PR attached so poll-merge can apply the terminal ticket effect.
986
+ continuationRemoteIsDraft = false;
987
+ continuationMergedWithSha = true;
988
+ }
989
+ else if (beforePush.state === 'CLOSED' && beforePush.includesExpectedSha === true && !continuationHadReadyWork) {
990
+ // An unchanged iteration may safely offer Reopen when the closed PR
991
+ // already contains its exact head. Changed work is never assumed to
992
+ // belong to a closed PR before we push it.
993
+ continuationClosedWithSha = true;
994
+ }
995
+ else {
996
+ continuationNeedsNewPr = true;
997
+ continuationPushFailureReason = beforePush.includesExpectedSha === null
998
+ ? `the previous PR is ${beforePush.state.toLowerCase()} and GitHub could not prove it included this implementation; create a new draft PR from the preserved commit`
999
+ : `the previous PR is ${beforePush.state.toLowerCase()} without this implementation; create a new draft PR from the preserved commit`;
1000
+ }
1001
+ }
1002
+ else if (!continuationHadReadyWork) {
1003
+ // No-change still needs lifecycle truth: the PR may have merged while
1004
+ // the run was active, and an advanced remote head must not be silently
1005
+ // dismissed as though it were the verified unchanged object.
1006
+ if (beforePush.headRefOid?.toLowerCase() !== deliverySha.toLowerCase()) {
1007
+ continuationLifecycleBlocked = true;
1008
+ continuationPushFailureReason = 'the open PR head no longer exposes the exact verified commit from this unchanged iteration';
1009
+ }
1010
+ else {
1011
+ continuationRemoteIsDraft = beforePush.isDraft;
1012
+ }
1013
+ }
1014
+ else {
1015
+ continuationRemoteIsDraft = beforePush.isDraft;
1016
+ try {
1017
+ const pushed = await (0, pr_publisher_1.pushBranch)(exec, {
1018
+ repoDir: baseRepo,
1019
+ branch: launchContinuation.branch,
1020
+ baseBranch: launchContinuation.baseBranch,
1021
+ sourceSha: deliverySha,
1022
+ });
1023
+ if (pushed.state === 'local-only') {
1024
+ continuationPushFailed = true;
1025
+ continuationPushFailureReason = pushed.reason;
1026
+ }
1027
+ else {
1028
+ continuationExactPushCompleted = true;
1029
+ const afterPush = await (0, pr_lifecycle_1.observePrLifecycle)(exec, baseRepo, launchContinuation.prUrl, deliverySha);
1030
+ if (!afterPush.ok) {
1031
+ continuationPushFailed = true;
1032
+ continuationPushFailureReason = `exact commit was pushed, but the PR lifecycle could not be confirmed: ${afterPush.detail}`;
1033
+ }
1034
+ else if ((0, pr_lifecycle_1.isExactOpenPr)(afterPush, launchContinuation.branch, launchContinuation.baseBranch)) {
1035
+ if (afterPush.headRefOid?.toLowerCase() === deliverySha.toLowerCase()) {
1036
+ continuationRemoteIsDraft = afterPush.isDraft;
1037
+ }
1038
+ else {
1039
+ // The object push returned success but another writer moved the
1040
+ // remote head before observation. Preserve local evidence and
1041
+ // block; creating a PR from the mutable branch would be unsafe.
1042
+ continuationLifecycleBlocked = true;
1043
+ continuationPushFailureReason = 'the PR head moved after the exact push; the verified commit is preserved but no longer proven as the remote head';
1044
+ }
1045
+ }
1046
+ else if (afterPush.state === 'MERGED' && afterPush.includesExpectedSha === true) {
1047
+ // The PR won the race but demonstrably included this exact SHA.
1048
+ continuationRemoteIsDraft = false;
1049
+ continuationMergedWithSha = true;
1050
+ }
1051
+ else if (afterPush.state === 'MERGED' || afterPush.state === 'CLOSED') {
1052
+ continuationNeedsNewPr = true;
1053
+ continuationPushFailureReason = afterPush.includesExpectedSha === null
1054
+ ? `the previous PR became ${afterPush.state.toLowerCase()} and GitHub could not prove it included this implementation; create a new draft PR from the preserved commit`
1055
+ : `the previous PR became ${afterPush.state.toLowerCase()} without this implementation; create a new draft PR from the preserved commit`;
1056
+ }
1057
+ else {
1058
+ continuationLifecycleBlocked = true;
1059
+ continuationPushFailureReason = 'the existing PR no longer matches its recorded head/base after the exact push';
1060
+ }
1061
+ }
1062
+ }
1063
+ catch (err) {
1064
+ continuationPushFailed = true;
1065
+ continuationPushFailureReason = errorDetail(err);
1066
+ }
1067
+ }
1068
+ if (continuationPushFailed) {
1069
+ console.warn(`[rail-isolated] existing PR follow-up push failed for ${launchContinuation.branch}: ${continuationPushFailureReason}`);
530
1070
  }
531
1071
  }
532
- const next = anySucceeded
533
- ? (settledContinuation
534
- ? (continuationPushFailed
535
- ? 'pr_failed'
536
- : (settledContinuation.isDraft === false ? 'pr_ready' : 'pr_draft'))
537
- : 'on_review')
538
- : 'implementation_failed';
539
- const patch = settledContinuation
540
- ? {
541
- branches,
542
- worktreeIds,
543
- branch: settledContinuation.branch,
544
- prUrl: settledContinuation.prUrl,
545
- prNumber: settledContinuation.prNumber,
546
- prState: continuationPushFailed ? 'local-only' : 'pr-created',
1072
+ if (continuationNeedsNewPr && noChanges.length > 0) {
1073
+ // The iteration itself made no new commit, but the old PR did not deliver
1074
+ // its verified head. That head is therefore real work for a fresh PR.
1075
+ const noChangeRuns = new Set(noChanges.map((result) => result.run.runId));
1076
+ results = results.map((result) => noChangeRuns.has(result.run.runId)
1077
+ ? { ...result, deliveryOutcome: 'ready' }
1078
+ : result);
1079
+ }
1080
+ if (continuationLifecycleBlocked) {
1081
+ const affectedRuns = new Set(results
1082
+ .filter((result) => result.run.continuationTarget !== null &&
1083
+ (result.deliveryOutcome === 'ready' || result.deliveryOutcome === 'no_changes'))
1084
+ .map((result) => result.run.runId));
1085
+ for (const result of results) {
1086
+ if (affectedRuns.has(result.run.runId))
1087
+ markWorktree(result.run, 'needs-review');
547
1088
  }
548
- : { branches, worktreeIds };
1089
+ results = results.map((result) => affectedRuns.has(result.run.runId)
1090
+ ? {
1091
+ ...result,
1092
+ deliveryOutcome: 'blocked',
1093
+ failureCode: 'branch_verification_failed',
1094
+ failureDetail: continuationPushFailureReason ?? 'PR lifecycle verification failed',
1095
+ safeToRelease: false,
1096
+ }
1097
+ : result);
1098
+ }
1099
+ ready = results.filter((result) => result.deliveryOutcome === 'ready');
1100
+ blocked = results.filter((result) => result.deliveryOutcome === 'blocked');
1101
+ noChanges = results.filter((result) => result.deliveryOutcome === 'no_changes');
1102
+ let deliveryOutcome;
1103
+ if (implementationOutcome === 'failed') {
1104
+ deliveryOutcome = blocked.length > 0 ? 'blocked' : 'not_started';
1105
+ }
1106
+ else if (continuationLifecycleBlocked) {
1107
+ deliveryOutcome = 'blocked';
1108
+ }
1109
+ else if (continuationMergedWithSha || continuationClosedWithSha) {
1110
+ deliveryOutcome = 'delivered';
1111
+ }
1112
+ else if (launchContinuation && ready.length > 0) {
1113
+ deliveryOutcome = continuationNeedsNewPr
1114
+ ? (implementationOutcome === 'partially_succeeded' ? 'partial' : 'ready')
1115
+ : continuationPushFailed ? 'retryable_failure' : 'delivered';
1116
+ }
1117
+ else if (ready.length > 0) {
1118
+ deliveryOutcome = implementationOutcome === 'partially_succeeded' || blocked.length > 0 ? 'partial' : 'ready';
1119
+ }
1120
+ else if (noChanges.length === results.length && implementationOutcome === 'succeeded') {
1121
+ deliveryOutcome = 'no_changes';
1122
+ }
1123
+ else if (implementationOutcome === 'partially_succeeded') {
1124
+ deliveryOutcome = 'partial';
1125
+ }
1126
+ else {
1127
+ deliveryOutcome = 'blocked';
1128
+ }
1129
+ const next = implementationOutcome === 'failed'
1130
+ ? 'implementation_failed'
1131
+ : continuationLifecycleBlocked
1132
+ ? 'pr_failed'
1133
+ : continuationMergedWithSha
1134
+ ? 'pr_ready'
1135
+ : continuationClosedWithSha
1136
+ ? 'pr_closed'
1137
+ : ready.length > 0
1138
+ ? (launchContinuation
1139
+ ? (continuationNeedsNewPr
1140
+ ? 'on_review'
1141
+ : continuationPushFailed
1142
+ ? 'pr_failed'
1143
+ : (continuationRemoteIsDraft === false ? 'pr_ready' : 'pr_draft'))
1144
+ : 'on_review')
1145
+ : deliveryOutcome === 'no_changes'
1146
+ ? 'no_changes'
1147
+ : 'pr_failed';
1148
+ const firstFailure = results.find((result) => result.failureCode);
1149
+ const statusCode = implementationOutcome === 'failed'
1150
+ ? 'implementation_failed'
1151
+ : continuationLifecycleBlocked
1152
+ ? 'branch_verification_failed'
1153
+ : continuationMergedWithSha
1154
+ ? 'pr_ready'
1155
+ : continuationClosedWithSha
1156
+ ? 'pr_closed'
1157
+ : continuationNeedsNewPr
1158
+ ? 'ready_for_review'
1159
+ : continuationPushFailed
1160
+ ? 'push_failed'
1161
+ : deliveryOutcome === 'no_changes'
1162
+ ? 'no_changes'
1163
+ : implementationOutcome === 'partially_succeeded'
1164
+ ? 'partial_success'
1165
+ : firstFailure?.failureCode
1166
+ ?? (launchContinuation && ready.length > 0 ? 'existing_pr_updated' : 'ready_for_review');
1167
+ const statusDetail = continuationPushFailureReason ?? firstFailure?.failureDetail ?? null;
1168
+ // Only proven-clean, durably referenced results are automatically
1169
+ // detached. Fresh reviewable worktrees stay mounted for the later PR
1170
+ // decision; dirty/unknown/ref-mismatched work is never passed to --force.
1171
+ const failedSafeIds = results
1172
+ .filter((result) => result.implementationOutcome === 'failed' && result.safeToRelease)
1173
+ .map((result) => result.run.ledgerId);
1174
+ const completedSafeIds = results
1175
+ .filter((result) => result.safeToRelease && (result.deliveryOutcome === 'no_changes' ||
1176
+ (launchContinuation !== null && result.deliveryOutcome === 'ready')))
1177
+ .map((result) => result.run.ledgerId);
1178
+ const patch = {
1179
+ branches: branchRecords(results),
1180
+ worktreeIds,
1181
+ implementationOutcome,
1182
+ deliveryOutcome,
1183
+ statusCode,
1184
+ statusDetail,
1185
+ deliverySha,
1186
+ ...(launchContinuation ? {
1187
+ branch: launchContinuation.branch,
1188
+ prUrl: continuationNeedsNewPr ? null : launchContinuation.prUrl,
1189
+ prNumber: continuationNeedsNewPr ? null : launchContinuation.prNumber,
1190
+ // A stale CLOSED/MERGED PR is detached from this generation. The exact
1191
+ // object remains locally or on its recorded branch, ready for Create PR.
1192
+ prState: continuationNeedsNewPr
1193
+ ? (continuationExactPushCompleted ? 'pushed' : 'local-only')
1194
+ : 'pr-created',
1195
+ // Once detached, this generation owns the lifecycle of the new PR,
1196
+ // while per-unit branchOwnership still protects the borrowed branch.
1197
+ isContinuation: !continuationNeedsNewPr,
1198
+ } : {}),
1199
+ };
549
1200
  if (prDeliveryId && (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', next, patch)) {
550
- if (settledContinuation && !continuationPushFailed) {
551
- await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: ctx.db, git, repoDir: baseRepo, worktreeIds });
1201
+ // Persist truthful settlement before cleanup. A crash can leave a
1202
+ // safely-recoverable mount, but can no longer lose no-change/partial/
1203
+ // exact-SHA evidence and regress the card back to `building`.
1204
+ const cleanupWarnings = [
1205
+ ...await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: ctx.db, git, repoDir: baseRepo, worktreeIds: failedSafeIds, state: 'failed', remove }),
1206
+ ...await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: ctx.db, git, repoDir: baseRepo, worktreeIds: completedSafeIds, remove }),
1207
+ ];
1208
+ if (cleanupWarnings.length > 0) {
1209
+ (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, next, next, { cleanupWarnings });
552
1210
  }
553
1211
  broadcastPrState();
554
1212
  // Completion driver: refresh the origin conversation's card in place —
555
1213
  // on_review asks the question, discarded informs the outcome.
556
1214
  syncOriginCard('update');
557
1215
  }
558
- if (!anySucceeded) {
559
- // FAILED-IMPLEMENTATION CLEANUP (0 succeeded): unmount every worktree NOW rather
560
- // than leaving it for a restart's reconcile sweep — a still-mounted
561
- // worktree POISONS the next run of the same ticket: the worktree path is
562
- // keyed by ticketId, so the next allocation silently reuses this
563
- // checkout and its stale branch. BRANCHES are kept: per-run settle
564
- // committed partial work to them and a re-launch resumes a kept branch
565
- // by name (createWorktree's resume path); user-discard remains the one
566
- // explicit branch-deleting action. Ledger rows are already terminal
567
- // ('failed') from per-run settle — ensured defensively here.
568
- for (const a of allocated) {
569
- await remove(git, {
570
- repoDir: baseRepo, worktreePath: a.handle.worktreePath, branch: a.handle.branch, deleteBranch: false,
571
- }).catch((err) => {
572
- console.warn(`[rail-isolated] failed to remove failed worktree ${a.handle.worktreePath}: ${err.message}`);
573
- });
574
- const wt = (0, rail_worktrees_store_1.getRailWorktree)(ctx.db, a.ledgerId);
575
- if (wt && !(0, rail_worktrees_store_1.isTerminalMergeState)(wt.merge_state))
576
- (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, 'failed');
577
- }
578
- }
579
1216
  return; // legacy merge-back never runs in PR mode
580
1217
  }
1218
+ const branches = results.flatMap((result) => result.run.ticketIds.map((ticketId) => ({
1219
+ ticketId,
1220
+ branch: result.run.handle.branch,
1221
+ succeeded: result.deliveryOutcome === 'ready',
1222
+ })));
581
1223
  try {
582
1224
  const outcomes = await (0, rail_merge_orchestrator_1.runMergeBack)({
583
1225
  git, executor: (0, loop_executors_1.createLoopExecutors)(), baseDir: baseRepo,
@@ -605,33 +1247,238 @@ async function launchIsolatedRail(input, io = {}) {
605
1247
  const r = results.find((x) => x.run.ticketId === o.ticketId);
606
1248
  if (!r)
607
1249
  continue;
1250
+ if (!r.safeToRelease) {
1251
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, r.run.ledgerId, 'needs-review');
1252
+ continue;
1253
+ }
608
1254
  // Merged → drop the branch; needs-review / skipped → keep the branch for the human.
609
1255
  await remove(git, {
610
1256
  repoDir: baseRepo, worktreePath: r.run.handle.worktreePath, branch: o.branch,
611
1257
  deleteBranch: o.state === 'merged',
1258
+ }).then(() => {
1259
+ if (r.implementationOutcome === 'failed')
1260
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, r.run.ledgerId, 'failed');
612
1261
  }).catch(() => { });
613
1262
  }
614
1263
  }
615
1264
  catch (err) {
616
1265
  console.error('[rail-isolated] merge-back failed:', err);
617
1266
  }
1267
+ }).catch(async (err) => {
1268
+ const detail = errorDetail(err);
1269
+ console.error(`[rail-isolated] aggregate settlement failed: ${detail}`);
1270
+ if (!prMode || !prDeliveryId || (0, rail_pr_store_1.getPrDelivery)(ctx.db, prDeliveryId)?.decision !== 'building')
1271
+ return;
1272
+ const retained = await Promise.all(guardedRunPromises);
1273
+ for (const result of retained)
1274
+ markWorktree(result.run, 'needs-review');
1275
+ if ((0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'pr_failed', {
1276
+ branches: branchRecords(retained.map((result) => ({
1277
+ ...result,
1278
+ deliveryOutcome: 'blocked',
1279
+ failureCode: result.failureCode ?? 'settlement_interrupted',
1280
+ failureDetail: result.failureDetail ?? detail,
1281
+ safeToRelease: false,
1282
+ }))),
1283
+ worktreeIds: allocated.map((run) => run.ledgerId),
1284
+ implementationOutcome: aggregateImplementationOutcome(retained),
1285
+ deliveryOutcome: 'blocked',
1286
+ statusCode: 'settlement_interrupted',
1287
+ statusDetail: detail,
1288
+ })) {
1289
+ broadcastPrState();
1290
+ syncOriginCard('update');
1291
+ }
618
1292
  });
619
1293
  return allocated.map((a) => a.runId);
620
1294
  }
1295
+ function recoveryOverlayExcludes(worktreePath) {
1296
+ try {
1297
+ const parsed = JSON.parse(fs.readFileSync(path.join(worktreePath, worktree_overlay_1.OVERLAY_MANIFEST), 'utf8'));
1298
+ const values = Array.isArray(parsed) ? parsed : parsed?.paths;
1299
+ if (!Array.isArray(values))
1300
+ return [];
1301
+ return values.filter((value) => {
1302
+ if (typeof value !== 'string' || value.length === 0 || path.isAbsolute(value))
1303
+ return false;
1304
+ if (value.includes('\\') || value.split('/').includes('..') || /[\0:*?\[\]]/.test(value))
1305
+ return false;
1306
+ if (value === worktree_overlay_1.OVERLAY_MANIFEST)
1307
+ return true;
1308
+ if (!/^\.(?:claude|codex|gemini)\/[^/]+(?:\/[^/]+)*$/.test(value))
1309
+ return false;
1310
+ try {
1311
+ // Manifest contents are worktree-writable and therefore not authority.
1312
+ // Ignore only links that still prove they are overlay scaffolding;
1313
+ // copied/modified files remain recoverable dirty data.
1314
+ return fs.lstatSync(path.join(worktreePath, value)).isSymbolicLink();
1315
+ }
1316
+ catch {
1317
+ return false;
1318
+ }
1319
+ });
1320
+ }
1321
+ catch {
1322
+ return [];
1323
+ }
1324
+ }
1325
+ async function inspectRecoveryWorktree(git, repoDir, row) {
1326
+ const excludes = [...worktree_manager_1.PR_NEVER_STAGE_PATHS, ...recoveryOverlayExcludes(row.worktree_path)];
1327
+ try {
1328
+ const [status, actualBranch, head, branchRef] = await Promise.all([
1329
+ git.run([
1330
+ 'status', '--porcelain', '--untracked-files=all', '--', '.',
1331
+ ...excludes.map((entry) => `:(exclude)${entry}`),
1332
+ ], row.worktree_path),
1333
+ git.run(['rev-parse', '--abbrev-ref', 'HEAD'], row.worktree_path),
1334
+ git.run(['rev-parse', '--verify', 'HEAD'], row.worktree_path),
1335
+ git.run(['rev-parse', '--verify', `refs/heads/${row.branch}`], repoDir),
1336
+ ]);
1337
+ const sha = head.code === 0 && COMMIT_SHA_RE.test(head.stdout.trim()) ? head.stdout.trim() : null;
1338
+ const refSha = branchRef.code === 0 ? branchRef.stdout.trim() : '';
1339
+ if (status.code !== 0)
1340
+ return { safe: false, sha, detail: 'git status could not verify worktree cleanliness' };
1341
+ if (status.stdout.trim().length > 0)
1342
+ return { safe: false, sha, detail: 'the interrupted worktree contains uncommitted changes' };
1343
+ if (actualBranch.code !== 0 || actualBranch.stdout.trim() !== row.branch) {
1344
+ return { safe: false, sha, detail: `worktree is not on expected branch ${row.branch}` };
1345
+ }
1346
+ if (!sha || refSha !== sha) {
1347
+ return { safe: false, sha, detail: `HEAD does not match refs/heads/${row.branch}` };
1348
+ }
1349
+ return { safe: true, sha };
1350
+ }
1351
+ catch (err) {
1352
+ return { safe: false, sha: null, detail: errorDetail(err) };
1353
+ }
1354
+ }
621
1355
  /**
622
- * Startup reconciliation: a crash mid-fan-out leaves worktrees on disk and ledger
623
- * rows stuck in a non-terminal state. Remove each orphan's worktree (best-effort,
624
- * keeping its branch for inspection) and mark the row `failed`. No-op (no git
625
- * calls) when there are no stuck rows — so it is free for projects that never used
626
- * isolation. Returns how many rows were reconciled.
1356
+ * Startup reconciliation is serialized with launch allocation. It proves each
1357
+ * orphan clean and durably referenced before removal; successful, dirty,
1358
+ * unknown, or ref-mismatched work remains mounted as needs-review. Building
1359
+ * delivery generations are then recovered from the durable engine outcomes.
627
1360
  */
628
1361
  async function reconcileRailWorktrees(db, repoDir, io = {}) {
629
1362
  const git = io.git ?? worktree_manager_1.defaultGitRunner;
630
1363
  const remove = io.remove ?? worktree_manager_1.removeWorktree;
631
- const stuck = (0, rail_worktrees_store_1.listNonTerminalRailWorktrees)(db);
632
- for (const row of stuck) {
633
- await remove(git, { repoDir, worktreePath: row.worktree_path, branch: row.branch, deleteBranch: false }).catch(() => { });
634
- (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'failed');
635
- }
636
- return stuck.length;
1364
+ return (0, repo_lock_1.withRepoLock)(repoDir, async () => {
1365
+ const stuck = (0, rail_worktrees_store_1.listNonTerminalRailWorktrees)(db);
1366
+ const autoReleaseWorktreeIds = new Set();
1367
+ const settledDeliveries = db.prepare(`
1368
+ SELECT decision, delivery_outcome, worktree_ids FROM rail_pr_deliveries
1369
+ WHERE decision <> 'building'
1370
+ AND delivery_outcome IN ('delivered', 'no_changes', 'retryable_failure')
1371
+ `).all();
1372
+ for (const delivery of settledDeliveries) {
1373
+ try {
1374
+ const ids = JSON.parse(delivery.worktree_ids);
1375
+ if (Array.isArray(ids)) {
1376
+ for (const id of ids)
1377
+ if (typeof id === 'string')
1378
+ autoReleaseWorktreeIds.add(id);
1379
+ }
1380
+ }
1381
+ catch { /* malformed history never authorizes cleanup */ }
1382
+ }
1383
+ const verifiedShaByRun = new Map();
1384
+ for (const row of stuck) {
1385
+ const run = row.run_id
1386
+ ? db.prepare('SELECT status, final_outcome FROM loop_runs WHERE id = ?').get(row.run_id)
1387
+ : undefined;
1388
+ const inspection = await inspectRecoveryWorktree(git, repoDir, row);
1389
+ if (run?.status === 'completed' && run.final_outcome === 'success') {
1390
+ if (inspection.safe && inspection.sha) {
1391
+ if (row.run_id)
1392
+ verifiedShaByRun.set(row.run_id, inspection.sha);
1393
+ if (autoReleaseWorktreeIds.has(row.id)) {
1394
+ try {
1395
+ await remove(git, {
1396
+ repoDir,
1397
+ worktreePath: row.worktree_path,
1398
+ branch: row.branch,
1399
+ deleteBranch: false,
1400
+ });
1401
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'released');
1402
+ }
1403
+ catch {
1404
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'built');
1405
+ }
1406
+ }
1407
+ else {
1408
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'built');
1409
+ }
1410
+ }
1411
+ else {
1412
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'needs-review');
1413
+ }
1414
+ continue;
1415
+ }
1416
+ if (run?.status === 'completed' && inspection.safe) {
1417
+ try {
1418
+ await remove(git, {
1419
+ repoDir,
1420
+ worktreePath: row.worktree_path,
1421
+ branch: row.branch,
1422
+ deleteBranch: false,
1423
+ });
1424
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'failed');
1425
+ }
1426
+ catch {
1427
+ // A failed --force leaves mount state uncertain; retain the ledger as
1428
+ // needs-review instead of claiming cleanup succeeded.
1429
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'needs-review');
1430
+ }
1431
+ continue;
1432
+ }
1433
+ // Missing/incomplete run evidence, dirty state, status failure, and ref
1434
+ // mismatch are all recoverable uncertainty. Never force-remove them.
1435
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'needs-review');
1436
+ }
1437
+ const recovered = (0, rail_pr_store_1.reconcileFailedBuildingPrDeliveries)(db, { startup: true });
1438
+ for (const delivery of recovered) {
1439
+ if (delivery.is_continuation !== 1 || !['succeeded', 'partially_succeeded'].includes(delivery.implementation_outcome))
1440
+ continue;
1441
+ let runIds = [];
1442
+ try {
1443
+ const parsed = JSON.parse(delivery.run_ids);
1444
+ if (Array.isArray(parsed))
1445
+ runIds = parsed.filter((value) => typeof value === 'string');
1446
+ }
1447
+ catch { /* malformed legacy row remains blocked without a retry SHA */ }
1448
+ for (const runId of runIds) {
1449
+ if (verifiedShaByRun.has(runId))
1450
+ continue;
1451
+ const released = db.prepare(`
1452
+ SELECT branch, merge_state FROM rail_worktrees
1453
+ WHERE run_id = ? AND merge_state = 'released'
1454
+ ORDER BY created_at DESC, rowid DESC LIMIT 1
1455
+ `).get(runId);
1456
+ if (!released)
1457
+ continue;
1458
+ try {
1459
+ const ref = await git.run(['rev-parse', '--verify', `refs/heads/${released.branch}`], repoDir);
1460
+ const sha = ref.code === 0 ? ref.stdout.trim() : '';
1461
+ if (COMMIT_SHA_RE.test(sha))
1462
+ verifiedShaByRun.set(runId, sha);
1463
+ }
1464
+ catch { /* exact retry remains blocked */ }
1465
+ }
1466
+ const shas = [...new Set(runIds.map((runId) => verifiedShaByRun.get(runId)).filter((sha) => !!sha))];
1467
+ if (shas.length !== 1)
1468
+ continue;
1469
+ (0, rail_pr_store_1.transitionDecision)(db, delivery.id, delivery.decision, delivery.decision, {
1470
+ deliveryOutcome: 'retryable_failure',
1471
+ statusCode: 'settlement_interrupted',
1472
+ statusDetail: 'Recovered a clean exact continuation commit; delivery can be retried safely.',
1473
+ deliverySha: shas[0],
1474
+ });
1475
+ }
1476
+ for (const delivery of recovered) {
1477
+ try {
1478
+ io.onDeliveryRecovered?.(delivery.id);
1479
+ }
1480
+ catch { /* durable state wins over advisory surfaces */ }
1481
+ }
1482
+ return stuck.length;
1483
+ });
637
1484
  }