specrails-desktop 2.36.0 → 2.42.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 (510) hide show
  1. package/README.md +94 -247
  2. package/client/dist/assets/ActivityFeedPage-DoEMumQS.js +1 -0
  3. package/client/dist/assets/AgentBrowserCapture-DIh4K0gh.js +1 -0
  4. package/client/dist/assets/AgentModeAnalyticsPane-mbq4WEoO.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-CKh7zuA4.js +2 -0
  6. package/client/dist/assets/AgentModeJobsPane-BDokPiLJ.js +1 -0
  7. package/client/dist/assets/AgentsPage-BFVAZbBj.js +87 -0
  8. package/client/dist/assets/AnalyticsPage-3Y9LuWwz.js +1 -0
  9. package/client/dist/assets/CodePage-C9KyuV_C.js +3 -0
  10. package/client/dist/assets/DesktopAnalyticsPage-Bhi54bXQ.js +1 -0
  11. package/client/dist/assets/{DocsDialog-C76VdBJC.js → DocsDialog-Dj6mt4ga.js} +2 -2
  12. package/client/dist/assets/{DocsPage-ziMlj6B6.js → DocsPage-24yWglNI.js} +1 -1
  13. package/client/dist/assets/ExportDropdown-ophapHrT.js +1 -0
  14. package/client/dist/assets/InteractiveJobComposer-C1ucmxQh.js +19 -0
  15. package/client/dist/assets/JobDetailModal-DNWmgVHD.js +1 -0
  16. package/client/dist/assets/JobDetailPage-BcajZyuu.js +1 -0
  17. package/client/dist/assets/JobsPage-B9nTZw6e.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-Oc48iOFW.js +7 -0
  19. package/client/dist/assets/LoopPreviewModal-ae0Fr6SU.js +1 -0
  20. package/client/dist/assets/LoopsPage-CUEqzUBI.js +1 -0
  21. package/client/dist/assets/MinimizedChatsContext-Deg39peL.js +1 -0
  22. package/client/dist/assets/PluginsPage-C1xtARIx.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-JZzrXFBN.js +1 -0
  24. package/client/dist/assets/RepositoryDeliveries-CSDsiVUz.js +5 -0
  25. package/client/dist/assets/RepositoryScopeSelector-D-RCaknl.js +1 -0
  26. package/client/dist/assets/ReviewPacketPage-x5EfGTiL.js +1 -0
  27. package/client/dist/assets/TemplatePreviewModal-BSyuZxGq.js +1 -0
  28. package/client/dist/assets/TicketDetailModalContext-DqyF0so-.js +14 -0
  29. package/client/dist/assets/Trans-SnMX4MhW.js +1 -0
  30. package/client/dist/assets/agent-BdT0SB1D.js +1 -0
  31. package/client/dist/assets/agent-Bh9K7iEt.js +1 -0
  32. package/client/dist/assets/agent-C4ibdFk9.js +1 -0
  33. package/client/dist/assets/agent-CSFY22Zo.js +1 -0
  34. package/client/dist/assets/agent-DAnqxUn8.js +1 -0
  35. package/client/dist/assets/agent-DTQiu0Mi.js +1 -0
  36. package/client/dist/assets/agent-T54WuR_Y.js +1 -0
  37. package/client/dist/assets/agent-i75WDZOA.js +1 -0
  38. package/client/dist/assets/browser-05vRDg_y.js +1 -0
  39. package/client/dist/assets/browser-Bulb-cB1.js +1 -0
  40. package/client/dist/assets/browser-C7f-Jsg1.js +1 -0
  41. package/client/dist/assets/browser-CG0PE1j_.js +1 -0
  42. package/client/dist/assets/browser-CMzaqAjo.js +1 -0
  43. package/client/dist/assets/browser-DMajYdNX.js +1 -0
  44. package/client/dist/assets/browser-DxcOHrdP.js +1 -0
  45. package/client/dist/assets/browser-ObE7xVo3.js +1 -0
  46. package/client/dist/assets/builder-765NeYTp.js +1 -0
  47. package/client/dist/assets/builder-8MG3V6ea.js +1 -0
  48. package/client/dist/assets/builder-B-1UBs5K.js +1 -0
  49. package/client/dist/assets/builder-BTiGnPT0.js +1 -0
  50. package/client/dist/assets/builder-Cx9Rz-7u.js +1 -0
  51. package/client/dist/assets/builder-DKFMELBl.js +1 -0
  52. package/client/dist/assets/builder-OcmKrC0h.js +1 -0
  53. package/client/dist/assets/builder-P9qVBXFZ.js +1 -0
  54. package/client/dist/assets/{button-CJqFnUOL.js → button-DCBqEaZl.js} +1 -1
  55. package/client/dist/assets/{code-CkUuW4m9.js → code-B37GRg9C.js} +1 -1
  56. package/client/dist/assets/{code-B0O8-p8k.js → code-BBOEZbf9.js} +1 -1
  57. package/client/dist/assets/{code-B1VLZDhS.js → code-BRm-jcyx.js} +1 -1
  58. package/client/dist/assets/{code-Bpj4Ddml.js → code-CF7w62CR.js} +1 -1
  59. package/client/dist/assets/{code-BykBZ5-e.js → code-CLuvJt2r.js} +1 -1
  60. package/client/dist/assets/{code-B86IMAfz.js → code-D0RCm59f.js} +1 -1
  61. package/client/dist/assets/code-DaZ-ZwCT.js +1 -0
  62. package/client/dist/assets/{code-cAqaqMTx.js → code-iIHkGaCL.js} +1 -1
  63. package/client/dist/assets/commands-0XjAftla.js +1 -0
  64. package/client/dist/assets/commands-BAWSYHjQ.js +1 -0
  65. package/client/dist/assets/commands-BBwowgiE.js +1 -0
  66. package/client/dist/assets/commands-BacjIZLN.js +1 -0
  67. package/client/dist/assets/commands-Bs62IAZ3.js +1 -0
  68. package/client/dist/assets/commands-CLPdh-Hh.js +1 -0
  69. package/client/dist/assets/commands-DlbFzgrp.js +1 -0
  70. package/client/dist/assets/commands-LjPSsi5F.js +1 -0
  71. package/client/dist/assets/common-B5AZNYMH.js +1 -0
  72. package/client/dist/assets/common-BFZ0c_vN.js +1 -0
  73. package/client/dist/assets/common-BrlwEnL7.js +1 -0
  74. package/client/dist/assets/common-BttQx3z4.js +1 -0
  75. package/client/dist/assets/common-CM65k_-e.js +1 -0
  76. package/client/dist/assets/common-D4mvFFlu.js +1 -0
  77. package/client/dist/assets/common-TZ9Ny96S.js +1 -0
  78. package/client/dist/assets/common-nFpSF4GC.js +1 -0
  79. package/client/dist/assets/dashboard-B14uYXkG.js +1 -0
  80. package/client/dist/assets/dashboard-BK_mez-z.js +1 -0
  81. package/client/dist/assets/dashboard-BfBir_6o.js +1 -0
  82. package/client/dist/assets/dashboard-Bt0cCzeG.js +1 -0
  83. package/client/dist/assets/dashboard-BwkjxI6H.js +1 -0
  84. package/client/dist/assets/{dashboard-C8WOEiPu.js → dashboard-C3bDKf7s.js} +1 -1
  85. package/client/dist/assets/dashboard-CdvjGfog.js +1 -0
  86. package/client/dist/assets/dashboard-fRrP0Kv0.js +1 -0
  87. package/client/dist/assets/dialog-SRw01_QU.js +45 -0
  88. package/client/dist/assets/dist-js-CJ_2XX1Y.js +1 -0
  89. package/client/dist/assets/formatDistanceToNow-DSj5DprF.js +1 -0
  90. package/client/dist/assets/getTimezoneOffsetInMilliseconds-YhD8rIEs.js +1 -0
  91. package/client/dist/assets/index-DwAmL7m5.css +2 -0
  92. package/client/dist/assets/index-EzrkUb8B.js +76 -0
  93. package/client/dist/assets/{jira-api-B1-MUehg.js → jira-api-CSCqGr-Q.js} +1 -1
  94. package/client/dist/assets/{jobs-CN_zv4Gr.js → jobs-B8rrzfyZ.js} +1 -1
  95. package/client/dist/assets/{jobs-DUlWSYq2.js → jobs-BS6RTurf.js} +1 -1
  96. package/client/dist/assets/{jobs-Bpks6doU.js → jobs-BTT0Bsks.js} +1 -1
  97. package/client/dist/assets/{jobs-Bgq-PZkC.js → jobs-CezU69AE.js} +1 -1
  98. package/client/dist/assets/{jobs-SwYS2M-B.js → jobs-ClFJae0s.js} +1 -1
  99. package/client/dist/assets/{jobs-BAls3bWq.js → jobs-DJSFIFVy.js} +1 -1
  100. package/client/dist/assets/{jobs-DhxKgWFq.js → jobs-DhgGr8vm.js} +1 -1
  101. package/client/dist/assets/{jobs-B_TeSQxb.js → jobs-DzMjUcaN.js} +1 -1
  102. package/client/dist/assets/mcp-6rMoZfEO.js +1 -0
  103. package/client/dist/assets/mcp-BD8sL5mP.js +1 -0
  104. package/client/dist/assets/mcp-BkcA3I5s.js +1 -0
  105. package/client/dist/assets/mcp-CCpSelOh.js +1 -0
  106. package/client/dist/assets/mcp-CUAN6ORK.js +1 -0
  107. package/client/dist/assets/mcp-CqbRxRTE.js +1 -0
  108. package/client/dist/assets/mcp-DP2YfVhO.js +1 -0
  109. package/client/dist/assets/mcp-DUVQamDz.js +1 -0
  110. package/client/dist/assets/narration-B-ynWTpn.js +1 -0
  111. package/client/dist/assets/narration-Bj_laZwg.js +1 -0
  112. package/client/dist/assets/narration-BxHpmMv_.js +1 -0
  113. package/client/dist/assets/narration-C1Px0-0j.js +1 -0
  114. package/client/dist/assets/narration-CAXhGbzM.js +1 -0
  115. package/client/dist/assets/narration-DQ1l_Kq6.js +1 -0
  116. package/client/dist/assets/narration-XISXwqZd.js +1 -0
  117. package/client/dist/assets/narration-kwzfglQo.js +1 -0
  118. package/client/dist/assets/nav-1-Hh2kOz.js +1 -0
  119. package/client/dist/assets/nav-B34QkESm.js +1 -0
  120. package/client/dist/assets/nav-Bap_UXAq.js +1 -0
  121. package/client/dist/assets/{nav-pYuXq7sf.js → nav-BwxXIB24.js} +1 -1
  122. package/client/dist/assets/nav-Ch-zHBG5.js +1 -0
  123. package/client/dist/assets/nav-CygC_WN-.js +1 -0
  124. package/client/dist/assets/{nav-iwjCg5FR.js → nav-KnnLQtOQ.js} +1 -1
  125. package/client/dist/assets/nav-m00uxYUG.js +1 -0
  126. package/client/dist/assets/packet-BQ2tXfJI.js +1 -0
  127. package/client/dist/assets/packet-BdpOJ1Cy.js +1 -0
  128. package/client/dist/assets/packet-C3Ievb9R.js +1 -0
  129. package/client/dist/assets/packet-C4zJIG0Y.js +1 -0
  130. package/client/dist/assets/packet-Cx-zRuWn.js +1 -0
  131. package/client/dist/assets/packet-DUTGX52M.js +1 -0
  132. package/client/dist/assets/packet-DhsKtUPB.js +1 -0
  133. package/client/dist/assets/packet-z3LJ8Zyy.js +1 -0
  134. package/client/dist/assets/project-repositories-DYhTvwQ1.js +1 -0
  135. package/client/dist/assets/provider-capabilities-B-wOHfLP.js +1 -0
  136. package/client/dist/assets/settings-BBt2xtDQ.js +1 -0
  137. package/client/dist/assets/settings-BJB9RKJj.js +1 -0
  138. package/client/dist/assets/settings-CHwQR2rB.js +1 -0
  139. package/client/dist/assets/settings-CrMi3kSi.js +1 -0
  140. package/client/dist/assets/settings-DKyhDGD1.js +1 -0
  141. package/client/dist/assets/settings-Deupji9c.js +1 -0
  142. package/client/dist/assets/settings-DqRMmwAM.js +1 -0
  143. package/client/dist/assets/settings-J4rj6vo-.js +1 -0
  144. package/client/dist/assets/setup-2xGgu4Sk.js +1 -0
  145. package/client/dist/assets/setup-Ba11l2JA.js +1 -0
  146. package/client/dist/assets/setup-BzW4kMok.js +1 -0
  147. package/client/dist/assets/setup-COzwYhkJ.js +1 -0
  148. package/client/dist/assets/setup-CRVULhtF.js +1 -0
  149. package/client/dist/assets/setup-DgvlxwpE.js +1 -0
  150. package/client/dist/assets/setup-Dw_wYeEY.js +1 -0
  151. package/client/dist/assets/setup-ZPtEeJPW.js +1 -0
  152. package/client/dist/assets/spending-BqbdPm_4.js +1 -0
  153. package/client/dist/assets/square-Ci-PFcFe.js +1 -0
  154. package/client/dist/assets/{terminal-BFRZnMPP.js → terminal-BAzpHtpY.js} +1 -1
  155. package/client/dist/assets/{terminal-IahE4LW9.js → terminal-C7dHeR3x.js} +1 -1
  156. package/client/dist/assets/{terminal-tITa81iz.js → terminal-CMla5Qsz.js} +1 -1
  157. package/client/dist/assets/{terminal-BZiIu3rf.js → terminal-CdNXH-n9.js} +1 -1
  158. package/client/dist/assets/{terminal-ScjyXNxQ.js → terminal-Cw5bZsmV.js} +1 -1
  159. package/client/dist/assets/{terminal-C2NAd7BW.js → terminal-DFUP4eNv.js} +1 -1
  160. package/client/dist/assets/{terminal-d7GadXbN.js → terminal-DGqljeK_.js} +1 -1
  161. package/client/dist/assets/{terminal-QpXZU2Bn.js → terminal-cU70NodO.js} +1 -1
  162. package/client/dist/assets/useDesktop-RSMTyy7V.js +1 -0
  163. package/client/dist/assets/useSharedWebSocket-BOdS5_Iz.js +2 -0
  164. package/client/dist/index.html +34 -35
  165. package/docs/README.md +3 -0
  166. package/docs/agent-live-steering.md +57 -0
  167. package/docs/ci-cd.md +57 -0
  168. package/docs/code-explorer.md +48 -0
  169. package/docs/codex.md +12 -1
  170. package/docs/features/detachable-mission-windows.md +59 -0
  171. package/docs/guide/de/integrations/5-mcp-server.md +13 -9
  172. package/docs/guide/de/integrations/6-agent-chat.md +1 -0
  173. package/docs/guide/en/integrations/5-mcp-server.md +13 -9
  174. package/docs/guide/en/integrations/6-agent-chat.md +1 -0
  175. package/docs/guide/es/integrations/5-mcp-server.md +13 -9
  176. package/docs/guide/es/integrations/6-agent-chat.md +1 -0
  177. package/docs/guide/fr/integrations/5-mcp-server.md +13 -9
  178. package/docs/guide/fr/integrations/6-agent-chat.md +1 -0
  179. package/docs/guide/it/integrations/5-mcp-server.md +13 -9
  180. package/docs/guide/it/integrations/6-agent-chat.md +1 -0
  181. package/docs/guide/ja/integrations/5-mcp-server.md +13 -9
  182. package/docs/guide/ja/integrations/6-agent-chat.md +1 -0
  183. package/docs/guide/pt/integrations/5-mcp-server.md +13 -9
  184. package/docs/guide/pt/integrations/6-agent-chat.md +1 -0
  185. package/docs/guide/zh/integrations/5-mcp-server.md +13 -9
  186. package/docs/guide/zh/integrations/6-agent-chat.md +1 -0
  187. package/docs/internals/README.md +1 -0
  188. package/docs/internals/browser-capture-performance.md +55 -3
  189. package/docs/internals/browser-login-popups.md +45 -0
  190. package/docs/internals/browser-native-retina-audit.md +39 -0
  191. package/docs/internals/ci-cd-audit.md +39 -0
  192. package/docs/internals/core-runtime-updates.md +68 -0
  193. package/docs/internals/embedded-browser-native-webview-evaluation.md +2 -0
  194. package/docs/internals/implementation-reliability-audit.md +93 -0
  195. package/docs/internals/interactive-jobs.md +110 -6
  196. package/docs/internals/mcp-mission-audit.md +76 -0
  197. package/docs/internals/project-builder.md +404 -42
  198. package/docs/internals/safe-pr-review-flow.md +52 -10
  199. package/docs/internals/startup-recovery-audit.md +58 -0
  200. package/docs/mcp.md +74 -41
  201. package/docs/mission-processes.md +59 -0
  202. package/docs/multi-repo-projects.md +51 -0
  203. package/docs/platforms/windows-parity.md +70 -0
  204. package/docs/platforms/windows.md +13 -11
  205. package/docs/running-pipelines.md +14 -0
  206. package/mcp-bridge/dist/specrails-mcp.js +7186 -0
  207. package/package.json +9 -4
  208. package/server/dist/agent-chat-manager.js +537 -68
  209. package/server/dist/agent-chat-router.js +87 -6
  210. package/server/dist/agent-context-resolver.js +129 -25
  211. package/server/dist/agent-defaults.js +19 -11
  212. package/server/dist/agent-input-store.js +206 -0
  213. package/server/dist/agent-mcp-config.js +54 -15
  214. package/server/dist/agent-operator-prompt.js +255 -65
  215. package/server/dist/agent-spec-framing.js +202 -0
  216. package/server/dist/agent-steering.js +218 -0
  217. package/server/dist/agent-store.js +153 -0
  218. package/server/dist/api-not-found.js +8 -0
  219. package/server/dist/background-process-control.js +164 -0
  220. package/server/dist/background-process-service.js +61 -0
  221. package/server/dist/background-process-store.js +242 -0
  222. package/server/dist/background-windows-bootstrap.js +238 -0
  223. package/server/dist/blueprint-chat-manager.js +476 -62
  224. package/server/dist/blueprint-commit.js +21 -1
  225. package/server/dist/blueprint-draft-parser.js +122 -17
  226. package/server/dist/blueprint-generation.js +238 -0
  227. package/server/dist/blueprint-operator-prompt.js +91 -63
  228. package/server/dist/blueprint-router.js +55 -3
  229. package/server/dist/blueprint-spec-fixtures.js +85 -0
  230. package/server/dist/blueprint-spec-quality.js +57 -16
  231. package/server/dist/blueprint-store.js +92 -1
  232. package/server/dist/browser-capture-manager.js +86 -30
  233. package/server/dist/browser-playwright.js +168 -45
  234. package/server/dist/browser-viewport.js +26 -0
  235. package/server/dist/chat-manager.js +23 -15
  236. package/server/dist/chromium-archive.cjs +83 -0
  237. package/server/dist/chromium-resolver.js +72 -64
  238. package/server/dist/code-activity.js +145 -0
  239. package/server/dist/code-explorer-router.js +714 -114
  240. package/server/dist/core-compat.js +28 -1
  241. package/server/dist/core-execution.js +111 -0
  242. package/server/dist/core-package.js +4 -16
  243. package/server/dist/core-runtime.js +202 -0
  244. package/server/dist/core-update-manager.js +161 -60
  245. package/server/dist/core-update-state.js +29 -0
  246. package/server/dist/db.js +66 -35
  247. package/server/dist/delivery-evidence.js +142 -39
  248. package/server/dist/desktop-db.js +239 -43
  249. package/server/dist/desktop-router.js +109 -9
  250. package/server/dist/dev-ports.js +8 -3
  251. package/server/dist/file-provenance.js +34 -22
  252. package/server/dist/file-story-manager.js +62 -64
  253. package/server/dist/file-story.js +118 -35
  254. package/server/dist/file-summary-generator.js +46 -5
  255. package/server/dist/file-summary-manager.js +355 -106
  256. package/server/dist/framework-manager.js +98 -110
  257. package/server/dist/framework-reseed.js +76 -10
  258. package/server/dist/git-diagnostics.js +2 -2
  259. package/server/dist/headroom-manager.js +38 -3
  260. package/server/dist/host-control.js +52 -0
  261. package/server/dist/index.js +43 -2
  262. package/server/dist/interactive-job-session.js +149 -16
  263. package/server/dist/internal-api.js +48 -0
  264. package/server/dist/jira/jira-materializer.js +1 -0
  265. package/server/dist/json-tolerant.js +174 -0
  266. package/server/dist/legacy-migration.js +4 -2
  267. package/server/dist/loop-command-catalog.js +83 -21
  268. package/server/dist/loop-constants.js +6 -1
  269. package/server/dist/loop-decider.js +11 -1
  270. package/server/dist/loop-executors.js +194 -45
  271. package/server/dist/loop-factory.js +11 -4
  272. package/server/dist/loop-graph.js +90 -4
  273. package/server/dist/loop-run-manager.js +301 -69
  274. package/server/dist/loop-shell-invocation.js +21 -0
  275. package/server/dist/loop-step-idle.js +55 -0
  276. package/server/dist/loop-templates.js +31 -7
  277. package/server/dist/mcp/agent-capability.js +37 -4
  278. package/server/dist/mcp/guide.js +149 -36
  279. package/server/dist/mcp/mcp-admin-router.js +20 -4
  280. package/server/dist/mcp/mcp-server.js +89 -43
  281. package/server/dist/mcp/mcp-tiers.js +6 -5
  282. package/server/dist/mcp/mcp-token.js +3 -1
  283. package/server/dist/mcp/resources.js +5 -4
  284. package/server/dist/mcp/tools/catalog.js +4 -0
  285. package/server/dist/mcp/tools/code.js +125 -14
  286. package/server/dist/mcp/tools/context.js +153 -0
  287. package/server/dist/mcp/tools/git.js +15 -5
  288. package/server/dist/mcp/tools/jobs.js +84 -71
  289. package/server/dist/mcp/tools/loops.js +17 -6
  290. package/server/dist/mcp/tools/meta.js +74 -27
  291. package/server/dist/mcp/tools/mission.js +29 -0
  292. package/server/dist/mcp/tools/projects.js +22 -6
  293. package/server/dist/mcp/tools/rails.js +48 -11
  294. package/server/dist/mcp/tools/specs.js +86 -14
  295. package/server/dist/mcp/tools/support.js +1 -1
  296. package/server/dist/mcp/tools/types.js +117 -43
  297. package/server/dist/mcp/tools/watch.js +144 -51
  298. package/server/dist/milestone-chain-store.js +164 -0
  299. package/server/dist/milestone-chain.js +487 -0
  300. package/server/dist/milestone-progress.js +348 -0
  301. package/server/dist/mobile/mobile-devices.js +10 -7
  302. package/server/dist/mobile/mobile-gateway.js +2 -0
  303. package/server/dist/mobile/mobile-missions.js +214 -0
  304. package/server/dist/mobile/mobile-redact.js +17 -3
  305. package/server/dist/mobile/mobile-router.js +46 -4
  306. package/server/dist/mobile/mobile-ws.js +49 -4
  307. package/server/dist/multi-repo-bases.js +56 -0
  308. package/server/dist/multi-repo-checkout.js +101 -0
  309. package/server/dist/multi-repo-delivery.js +100 -0
  310. package/server/dist/multi-repo-execution-store.js +65 -0
  311. package/server/dist/multi-repo-execution.js +453 -0
  312. package/server/dist/offline-assemble.js +10 -8
  313. package/server/dist/openspec-runtime-plugin-commands.json +18 -0
  314. package/server/dist/openspec-runtime-plugin.js +120 -0
  315. package/server/dist/plugins/codex-spawn.js +49 -0
  316. package/server/dist/plugins/serena/manifest.js +2 -2
  317. package/server/dist/plugins/serena/templates/instructions.md +16 -0
  318. package/server/dist/pr-publisher.js +1 -1
  319. package/server/dist/profiles-router.js +3 -18
  320. package/server/dist/project-code-discovery.js +142 -0
  321. package/server/dist/project-git.js +19 -21
  322. package/server/dist/project-registry.js +272 -20
  323. package/server/dist/project-repositories.js +157 -0
  324. package/server/dist/project-repository-provenance.js +31 -0
  325. package/server/dist/project-router-background-processes.js +105 -0
  326. package/server/dist/project-router-git.js +75 -16
  327. package/server/dist/project-router-jobs.js +2 -126
  328. package/server/dist/project-router-loop-runs.js +72 -2
  329. package/server/dist/project-router-tickets.js +53 -8
  330. package/server/dist/project-router.js +239 -38
  331. package/server/dist/provider-detection.js +18 -6
  332. package/server/dist/provider-limit.js +64 -0
  333. package/server/dist/providers/claude-adapter.js +26 -1
  334. package/server/dist/providers/claude-background-tasks.js +18 -0
  335. package/server/dist/providers/claude-live-session.js +383 -0
  336. package/server/dist/providers/codex-adapter.js +19 -0
  337. package/server/dist/providers/codex-live-session.js +545 -0
  338. package/server/dist/providers/gemini-adapter.js +1 -0
  339. package/server/dist/providers/live-session-types.js +12 -0
  340. package/server/dist/providers/live-session.js +13 -0
  341. package/server/dist/providers/terminal-result.js +24 -0
  342. package/server/dist/queue-manager.js +2 -0
  343. package/server/dist/rail-isolated-launch.js +141 -16
  344. package/server/dist/rail-pr-decision.js +364 -99
  345. package/server/dist/rail-pr-store.js +24 -10
  346. package/server/dist/rail-worktree-release.js +34 -2
  347. package/server/dist/rail-worktrees-store.js +4 -4
  348. package/server/dist/rails-router.js +249 -55
  349. package/server/dist/repo-lock.js +92 -17
  350. package/server/dist/review-packet.js +23 -13
  351. package/server/dist/schemas/file-summary.v1.json +59 -59
  352. package/server/dist/schemas/profile.v1.json +137 -137
  353. package/server/dist/setup-manager.js +52 -8
  354. package/server/dist/shell-integration/bash-shim.bash +45 -0
  355. package/server/dist/shell-integration/fish-shim.fish +36 -0
  356. package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
  357. package/server/dist/shell-integration/zsh-shim.zsh +47 -0
  358. package/server/dist/smash-runner.js +1 -0
  359. package/server/dist/spawn-lifecycle.js +44 -27
  360. package/server/dist/spec-contract-prompt.js +143 -0
  361. package/server/dist/spec-draft-parser.js +6 -1
  362. package/server/dist/spec-models.js +1 -0
  363. package/server/dist/stuck-run-detector.js +1 -0
  364. package/server/dist/terminal-manager.js +21 -1
  365. package/server/dist/terminal-shell-integration.js +4 -1
  366. package/server/dist/transient-children.js +619 -230
  367. package/server/dist/util/cli-prompt.js +78 -5
  368. package/server/dist/util/sqlite-migrations.js +20 -0
  369. package/server/dist/util/win-spawn.js +18 -3
  370. package/server/dist/util/windows-provider-shell.js +29 -0
  371. package/server/dist/verification-sentinel.js +14 -0
  372. package/server/dist/vitest-setup.js +2 -0
  373. package/server/dist/windows-job-supervisor.js +133 -0
  374. package/server/dist/workspace-manager.js +9 -30
  375. package/server/dist/worktree-manager.js +17 -2
  376. package/server/dist/worktree-overlay.js +3 -1
  377. package/client/dist/assets/ActivityFeedPage-tAQcWs1R.js +0 -1
  378. package/client/dist/assets/AgentBrowserCapture-BX8BNae5.js +0 -1
  379. package/client/dist/assets/AgentModeAnalyticsPane-BVAqCL0K.js +0 -2
  380. package/client/dist/assets/AgentModeCodePane-9XfqQc9c.js +0 -2
  381. package/client/dist/assets/AgentModeJobsPane-BCjLsPhc.js +0 -1
  382. package/client/dist/assets/AgentsPage-C8J_42sm.js +0 -87
  383. package/client/dist/assets/AnalyticsPage-BtQuyfJm.js +0 -1
  384. package/client/dist/assets/CodePage-BZ2MwdCu.js +0 -2
  385. package/client/dist/assets/DesktopAnalyticsPage-Bh_29V4g.js +0 -1
  386. package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
  387. package/client/dist/assets/InteractiveJobComposer-BCZiSfzc.js +0 -19
  388. package/client/dist/assets/JobDetailModal-NAJmuYIT.js +0 -1
  389. package/client/dist/assets/JobDetailPage-fE0ZycUX.js +0 -1
  390. package/client/dist/assets/JobsPage-z5M_HVJG.js +0 -1
  391. package/client/dist/assets/LoopBuilderPage-j1FQS47c.js +0 -7
  392. package/client/dist/assets/LoopPreviewModal-DhDJjdCe.js +0 -1
  393. package/client/dist/assets/LoopsPage-Ll7zORZb.js +0 -1
  394. package/client/dist/assets/MinimizedChatsContext-Bd3OqR_S.js +0 -1
  395. package/client/dist/assets/PluginsPage-DN4kGvJt.js +0 -2
  396. package/client/dist/assets/ProjectSettingsDialog-OgsgxjzF.js +0 -1
  397. package/client/dist/assets/ReviewPacketPage-DFog4sXn.js +0 -1
  398. package/client/dist/assets/TemplatePreviewModal-L2XxqiOD.js +0 -1
  399. package/client/dist/assets/TicketDetailModal-CsF-AWZX.js +0 -14
  400. package/client/dist/assets/Trans-Bsz9_gyq.js +0 -1
  401. package/client/dist/assets/agent-BQgKv_2F.js +0 -1
  402. package/client/dist/assets/agent-C1NRDJqL.js +0 -1
  403. package/client/dist/assets/agent-CLNleiy6.js +0 -1
  404. package/client/dist/assets/agent-CmceArkQ.js +0 -1
  405. package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
  406. package/client/dist/assets/agent-DB1JeE64.js +0 -1
  407. package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
  408. package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
  409. package/client/dist/assets/auth-D_5bQv5W.js +0 -1
  410. package/client/dist/assets/ban-E4EaZe3a.js +0 -1
  411. package/client/dist/assets/bot-DzuuuC5R.js +0 -1
  412. package/client/dist/assets/browser-BgqHDMXP.js +0 -1
  413. package/client/dist/assets/browser-CsKJtrpv.js +0 -1
  414. package/client/dist/assets/browser-Cu58EpZU.js +0 -1
  415. package/client/dist/assets/browser-CvXOJm-j.js +0 -1
  416. package/client/dist/assets/browser-DOrFh1c7.js +0 -1
  417. package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
  418. package/client/dist/assets/browser-LPgxvJu2.js +0 -1
  419. package/client/dist/assets/browser-eL-3MIEI.js +0 -1
  420. package/client/dist/assets/builder-0IWtosUm.js +0 -1
  421. package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
  422. package/client/dist/assets/builder-BnqaaBkT.js +0 -1
  423. package/client/dist/assets/builder-BxxfGgAO.js +0 -1
  424. package/client/dist/assets/builder-C5IIgWsq.js +0 -1
  425. package/client/dist/assets/builder-CHuahItu.js +0 -1
  426. package/client/dist/assets/builder-CjWosXL_.js +0 -1
  427. package/client/dist/assets/builder-CpCIOoKj.js +0 -1
  428. package/client/dist/assets/code-CHPkOkDS.js +0 -1
  429. package/client/dist/assets/commands-6tYQih5m.js +0 -1
  430. package/client/dist/assets/commands-BK2QinXN.js +0 -1
  431. package/client/dist/assets/commands-C2Nca76h.js +0 -1
  432. package/client/dist/assets/commands-CKdLihNX.js +0 -1
  433. package/client/dist/assets/commands-CYIBTAlc.js +0 -1
  434. package/client/dist/assets/commands-CjUtYJIM.js +0 -1
  435. package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
  436. package/client/dist/assets/commands-D_ZHYQug.js +0 -1
  437. package/client/dist/assets/common-6MLbD-jM.js +0 -1
  438. package/client/dist/assets/common-BKGEu4rm.js +0 -1
  439. package/client/dist/assets/common-BNbnv8cE.js +0 -1
  440. package/client/dist/assets/common-BqGWtDbt.js +0 -1
  441. package/client/dist/assets/common-DBtTzeYm.js +0 -1
  442. package/client/dist/assets/common-DEW3Nr8n.js +0 -1
  443. package/client/dist/assets/common-DHsv9N5d.js +0 -1
  444. package/client/dist/assets/common-mkrEcW-Y.js +0 -1
  445. package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
  446. package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
  447. package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
  448. package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
  449. package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
  450. package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
  451. package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
  452. package/client/dist/assets/dialog-CdWCb02j.js +0 -45
  453. package/client/dist/assets/dist-bVqfYmby.js +0 -1
  454. package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
  455. package/client/dist/assets/formatDistanceToNow-DQCvMgUc.js +0 -1
  456. package/client/dist/assets/getTimezoneOffsetInMilliseconds-C16cxSe1.js +0 -1
  457. package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
  458. package/client/dist/assets/i18n-hnX2DPmo.js +0 -2
  459. package/client/dist/assets/index-DIhMEAVw.js +0 -66
  460. package/client/dist/assets/index-DwygcU7s.css +0 -2
  461. package/client/dist/assets/mcp-B8X8wBLu.js +0 -1
  462. package/client/dist/assets/mcp-C2AojZZN.js +0 -1
  463. package/client/dist/assets/mcp-D8BT78_L.js +0 -1
  464. package/client/dist/assets/mcp-Df5RWBNk.js +0 -1
  465. package/client/dist/assets/mcp-Dj7BgMQW.js +0 -1
  466. package/client/dist/assets/mcp-Q7iT7rcz.js +0 -1
  467. package/client/dist/assets/mcp-YlvNvJbW.js +0 -1
  468. package/client/dist/assets/mcp-mKFpTSRb.js +0 -1
  469. package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
  470. package/client/dist/assets/narration-BGuiEDj3.js +0 -1
  471. package/client/dist/assets/narration-BuSoqRmv.js +0 -1
  472. package/client/dist/assets/narration-CB9t29fy.js +0 -1
  473. package/client/dist/assets/narration-CU3x8FHD.js +0 -1
  474. package/client/dist/assets/narration-CWeXhfP5.js +0 -1
  475. package/client/dist/assets/narration-DRaDnhT5.js +0 -1
  476. package/client/dist/assets/narration-DtEbQgCw.js +0 -1
  477. package/client/dist/assets/nav-ApJVg3xu.js +0 -1
  478. package/client/dist/assets/nav-BKNky1Bg.js +0 -1
  479. package/client/dist/assets/nav-C6vWWKYx.js +0 -1
  480. package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
  481. package/client/dist/assets/nav-CsmFBIDf.js +0 -1
  482. package/client/dist/assets/nav-DjkmQJjd.js +0 -1
  483. package/client/dist/assets/packet-B1IxaL5f.js +0 -1
  484. package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
  485. package/client/dist/assets/packet-By8fqYxW.js +0 -1
  486. package/client/dist/assets/packet-CZdvA44E.js +0 -1
  487. package/client/dist/assets/packet-CrjHRQCP.js +0 -1
  488. package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
  489. package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
  490. package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
  491. package/client/dist/assets/play-DNlrawbS.js +0 -1
  492. package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
  493. package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
  494. package/client/dist/assets/settings-C1-7cXzC.js +0 -1
  495. package/client/dist/assets/settings-CAFaT38b.js +0 -1
  496. package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
  497. package/client/dist/assets/settings-DHh7_IF5.js +0 -1
  498. package/client/dist/assets/settings-DQXAmdcv.js +0 -1
  499. package/client/dist/assets/settings-guzlhpup.js +0 -1
  500. package/client/dist/assets/settings-sZfTPMEH.js +0 -1
  501. package/client/dist/assets/setup-3jWaoVUO.js +0 -1
  502. package/client/dist/assets/setup-BjgRneb2.js +0 -1
  503. package/client/dist/assets/setup-BzFIjv-f.js +0 -1
  504. package/client/dist/assets/setup-C4VfQsxE.js +0 -1
  505. package/client/dist/assets/setup-DfUkpDbN.js +0 -1
  506. package/client/dist/assets/setup-ECe90Pmu.js +0 -1
  507. package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
  508. package/client/dist/assets/setup-jwfLMiM2.js +0 -1
  509. package/client/dist/assets/spending-EC9xqio-.js +0 -1
  510. package/client/dist/assets/useDesktop-BQVt8lFd.js +0 -1
@@ -35,7 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.PR_DECISION_ACTIONS = void 0;
37
37
  exports.isPrDecisionAction = isPrDecisionAction;
38
+ exports.actionAllowed = actionAllowed;
38
39
  exports.executePrDecision = executePrDecision;
40
+ exports.sweepMergedChainAncestors = sweepMergedChainAncestors;
39
41
  /**
40
42
  * Execute a user's ask-first PR decision (safe-pr-review-flow) against its
41
43
  * authoritative `rail_pr_deliveries` row: the actions both decision
@@ -52,9 +54,9 @@ exports.executePrDecision = executePrDecision;
52
54
  * the on_review tickets to todo.
53
55
  * poll-merge → `gh pr view` — MERGED promotes the tickets to done.
54
56
  * merge-local→ the REMOTE-LESS acceptance path: merge the delivered branches
55
- * into the integration branch directly in the user's checkout
56
- * (guarded: correct branch checked out + clean tree, abort on
57
- * conflict). Without it, a repo with no GitHub remote wedges
57
+ * into the integration branch after isolated assembly. Compatible
58
+ * local changes survive; another current branch stays untouched.
59
+ * Without it, a repo with no GitHub remote wedges
58
60
  * forever in retry(local-only)/discard — no way to ACCEPT work.
59
61
  *
60
62
  * Every mutation rides a lease-owned compare-and-set (a raced concurrent
@@ -82,9 +84,12 @@ const workspace_resolution_1 = require("./workspace-resolution");
82
84
  const agent_chat_registry_1 = require("./agent-chat-registry");
83
85
  const ids_1 = require("./ids");
84
86
  const repo_lock_1 = require("./repo-lock");
87
+ const integration_branch_1 = require("./integration-branch");
85
88
  const rail_pr_ticket_effects_1 = require("./rail-pr-ticket-effects");
86
89
  const pr_lifecycle_1 = require("./pr-lifecycle");
87
90
  const rail_pr_recovery_git_1 = require("./rail-pr-recovery-git");
91
+ const milestone_chain_store_1 = require("./milestone-chain-store");
92
+ const rail_pr_store_2 = require("./rail-pr-store");
88
93
  exports.PR_DECISION_ACTIONS = ['create-pr', 'publish', 'discard', 'dismiss', 'poll-merge', 'reopen', 'merge-local', 'acknowledge-no-changes', 'recover-and-retry'];
89
94
  function safetyArchiveRecorder(deps, row) {
90
95
  return (archive) => {
@@ -155,6 +160,12 @@ async function executePrDecision(deps, input) {
155
160
  const row = (0, rail_pr_store_1.getPrDelivery)(deps.db, input.prDeliveryId);
156
161
  if (!row)
157
162
  return { status: 404, body: { error: 'Unknown prDeliveryId' } };
163
+ if (row.parent_delivery_id && deps.repositoryChildOf !== row.parent_delivery_id)
164
+ return { status: 409, body: { error: 'repository_delivery_requires_parent', parentDeliveryId: row.parent_delivery_id } };
165
+ if (!row.parent_delivery_id && row.execution_manifest) {
166
+ const { executeRepositoryGroupDecision } = await Promise.resolve().then(() => __importStar(require('./multi-repo-delivery')));
167
+ return executeRepositoryGroupDecision(deps, input);
168
+ }
158
169
  // Compare-and-set pre-check: the caller decided against a snapshot — if the
159
170
  // row moved on (the other surface answered first), it must reconcile, not act.
160
171
  if (row.decision !== input.expectedDecision)
@@ -212,6 +223,8 @@ function casTransition(deps, row, next, patch = {}) {
212
223
  return staleDecision(current?.decision ?? row.decision);
213
224
  }
214
225
  function casTransitionWithTicketEffect(deps, row, next, patch, effect) {
226
+ if (deps.repositoryChildOf)
227
+ return casTransition(deps, row, next, patch);
215
228
  if (row.operation_token &&
216
229
  (0, rail_pr_ticket_effects_1.transitionClaimedDecisionWithTicketEffect)(deps.db, row.id, row.decision, next, row.operation_token, patch, effect))
217
230
  return null;
@@ -219,6 +232,8 @@ function casTransitionWithTicketEffect(deps, row, next, patch, effect) {
219
232
  return staleDecision(current?.decision ?? row.decision);
220
233
  }
221
234
  function applyTerminalTicketEffect(deps, row, terminalDecision, cleanupWarnings) {
235
+ if (deps.repositoryChildOf)
236
+ return;
222
237
  const result = (0, rail_pr_ticket_effects_1.applyRailPrTicketEffect)(deps, row.id);
223
238
  if (result.ok)
224
239
  return;
@@ -244,6 +259,8 @@ function finalizeTransition(deps, id) {
244
259
  deferredFinalizations.add(id);
245
260
  return snap;
246
261
  }
262
+ if (deps.repositoryChildOf)
263
+ return snap;
247
264
  deps.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(deps.project.id, snap));
248
265
  if (row.origin_conversation_id) {
249
266
  const agent = (deps.agentChat ?? agent_chat_registry_1.getAgentChatManager)();
@@ -1616,6 +1633,7 @@ async function runDiscard(deps, row, opts = { removeNeedsReview: true }) {
1616
1633
  });
1617
1634
  if (conflict)
1618
1635
  return conflict;
1636
+ pauseChainsOnDiscard(deps, row);
1619
1637
  if (!preserveExternalReview)
1620
1638
  applyTerminalTicketEffect(deps, row, 'discarded', cleanupWarnings);
1621
1639
  finalizeTransition(deps, row.id);
@@ -1720,6 +1738,7 @@ async function settleMergedPr(deps, row) {
1720
1738
  await releaseDeliveredRecoveryLineage(deps, row, row.delivery_sha);
1721
1739
  applyTerminalTicketEffect(deps, row, 'merged', cleanupWarnings);
1722
1740
  finalizeTransition(deps, row.id);
1741
+ await sweepMergedChainAncestors(deps, row);
1723
1742
  return {
1724
1743
  status: 200,
1725
1744
  body: {
@@ -1908,38 +1927,102 @@ async function runMergeLocal(deps, row) {
1908
1927
  return (0, repo_lock_1.withRepoLock)(deps.project.path, () => runMergeLocalLocked(deps, row));
1909
1928
  }
1910
1929
  async function runMergeLocalLocked(deps, row) {
1930
+ // A stacked milestone delivery targets the chain integration branch, not
1931
+ // the previous feature branch recorded as its PR base. Freeze that target
1932
+ // for the whole locked operation and every pre/post-advance check.
1933
+ const integrationTarget = mergeLocalTargetBranch(deps, row);
1911
1934
  deps.assertAdmission?.();
1935
+ if (!(0, integration_branch_1.isValidBranchName)(integrationTarget) || integrationTarget === 'HEAD' || integrationTarget.startsWith('refs/')) {
1936
+ return {
1937
+ status: 409,
1938
+ body: { error: 'merge_local_blocked', reason: 'unresolved_head', base: integrationTarget,
1939
+ detail: 'Configure a local integration branch before accepting this delivery.' },
1940
+ };
1941
+ }
1942
+ const head = await deps.git.run(['symbolic-ref', '--quiet', 'HEAD'], deps.project.path);
1943
+ if (head.code !== 0 && head.code !== 1) {
1944
+ return { status: 409, body: { error: 'merge_local_blocked', reason: 'unresolved_head', base: integrationTarget } };
1945
+ }
1946
+ if (head.stdout.trim() === `refs/heads/${integrationTarget}`)
1947
+ return runMergeLocalIntoCheckout(deps, row, deps.project.path, integrationTarget);
1948
+ // Claim the designated base in an ordinary linked checkout. Git refuses if
1949
+ // another worktree already holds it; never update-ref a checked-out branch
1950
+ // behind its owner's back or switch/stash the user's current work.
1951
+ const safeId = row.id.replace(/[^A-Za-z0-9-]/g, '').slice(0, 64) || 'delivery';
1952
+ const target = path.join(deps.assemblyRoot ?? batchWorktreeRoot(deps.project.slug), `local-base-${safeId}-${(0, ids_1.newId)()}`);
1953
+ const added = await deps.git.run(['worktree', 'add', target, integrationTarget], deps.project.path);
1954
+ if (added.code !== 0) {
1955
+ return {
1956
+ status: 409,
1957
+ body: {
1958
+ error: 'merge_local_blocked', reason: 'integration_branch_busy', base: integrationTarget,
1959
+ detail: added.stderr.trim() || added.stdout.trim() || 'The integration branch could not be opened safely.',
1960
+ },
1961
+ };
1962
+ }
1963
+ let result;
1964
+ try {
1965
+ result = await runMergeLocalIntoCheckout(deps, row, target, integrationTarget);
1966
+ return result;
1967
+ }
1968
+ finally {
1969
+ // Even failed assembly must release the temporary base checkout. Non-force
1970
+ // removal preserves files if an external process wrote there during merge.
1971
+ let warning = null;
1972
+ try {
1973
+ const removed = await deps.git.run(['worktree', 'remove', target], deps.project.path);
1974
+ if (removed.code !== 0)
1975
+ warning = `integration worktree ${target}: ${removed.stderr.trim() || removed.stdout.trim() || `exit ${removed.code}`}`;
1976
+ }
1977
+ catch (err) {
1978
+ warning = `integration worktree ${target}: ${err instanceof Error ? err.message : String(err)}`;
1979
+ }
1980
+ if (warning) {
1981
+ safetyArchiveRecorder(deps, row)(target);
1982
+ const current = (0, rail_pr_store_1.getPrDelivery)(deps.db, row.id);
1983
+ if (current?.operation_token === row.operation_token) {
1984
+ const warnings = [...(0, rail_pr_store_1.toPrDeliverySnapshot)(current).cleanupWarnings, warning];
1985
+ casTransition(deps, current, current.decision, { cleanupWarnings: warnings, statusCode: 'cleanup_incomplete' });
1986
+ finalizeTransition(deps, row.id);
1987
+ if (result)
1988
+ result.body.cleanupWarnings = warnings;
1989
+ }
1990
+ }
1991
+ }
1992
+ }
1993
+ async function runMergeLocalIntoCheckout(deps, row, targetRepo, integrationTarget) {
1912
1994
  const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
1913
1995
  const cleanupWarnings = [];
1914
- // Snapshot the checkout the user authorized. The same three invariants are
1915
- // revalidated immediately before the one final fast-forward.
1916
- const head = await deps.git.run(['rev-parse', '--abbrev-ref', 'HEAD'], deps.project.path);
1917
- const currentBranch = head.code === 0 ? head.stdout.trim() : '';
1918
- if (currentBranch !== row.base_branch) {
1996
+ // Snapshot branch/HEAD before assembly. Local edits are allowed: the final
1997
+ // non-forced fast-forward leaves unrelated work intact and Git atomically
1998
+ // refuses any overwrite, including ignored files.
1999
+ const head = await deps.git.run(['symbolic-ref', '--quiet', 'HEAD'], targetRepo);
2000
+ const currentBranch = head.code === 0 ? head.stdout.trim().replace(/^refs\/heads\//, '') : '';
2001
+ if (currentBranch !== integrationTarget) {
1919
2002
  return {
1920
2003
  status: 409,
1921
- body: { error: 'merge_local_blocked', reason: 'wrong_branch', current: currentBranch || null, base: row.base_branch },
2004
+ body: { error: 'merge_local_blocked', reason: 'wrong_branch', current: currentBranch || null, base: integrationTarget },
1922
2005
  };
1923
2006
  }
1924
- const status = await deps.git.run(['status', '--porcelain'], deps.project.path);
1925
- if (status.code !== 0 || status.stdout.trim() !== '') {
1926
- return { status: 409, body: { error: 'merge_local_blocked', reason: 'dirty', base: row.base_branch } };
2007
+ const status = await deps.git.run(['status', '--porcelain'], targetRepo);
2008
+ if (status.code !== 0) {
2009
+ return { status: 409, body: { error: 'merge_local_blocked', reason: 'status_unavailable', base: integrationTarget } };
1927
2010
  }
1928
- const startingHead = await deps.git.run(['rev-parse', '--verify', 'HEAD'], deps.project.path);
2011
+ const startingHead = await deps.git.run(['rev-parse', '--verify', 'HEAD'], targetRepo);
1929
2012
  const startingSha = startingHead.code === 0 ? startingHead.stdout.trim() : '';
1930
2013
  if (!/^[0-9a-f]{40,64}$/i.test(startingSha)) {
1931
- return { status: 409, body: { error: 'merge_local_blocked', reason: 'unresolved_head', base: row.base_branch } };
2014
+ return { status: 409, body: { error: 'merge_local_blocked', reason: 'unresolved_head', base: integrationTarget } };
1932
2015
  }
1933
2016
  // What to merge: immutable object ids only. The assembled head carries every
1934
2017
  // unit already; without one, merge each settled unit's exact final SHA.
1935
2018
  let toMerge;
1936
2019
  let deliveryHeadSha = null;
1937
- if (row.delivery_sha && await commitObjectExists(deps, row.delivery_sha)) {
2020
+ if (row.delivery_sha) {
2021
+ if (!COMMIT_SHA_RE.test(row.delivery_sha) || !(await commitObjectExists(deps, row.delivery_sha))) {
2022
+ return { status: 502, body: { error: 'merge_failed', detail: 'The verified delivery commit is unavailable; no branch fallback was merged.' } };
2023
+ }
1938
2024
  deliveryHeadSha = row.delivery_sha;
1939
2025
  }
1940
- else if (row.branch) {
1941
- deliveryHeadSha = await captureBranchSha(deps, row.branch);
1942
- }
1943
2026
  if (deliveryHeadSha) {
1944
2027
  toMerge = [deliveryHeadSha];
1945
2028
  }
@@ -1951,9 +2034,6 @@ async function runMergeLocalLocked(deps, row) {
1951
2034
  if (!(await commitObjectExists(deps, sourceSha)))
1952
2035
  sourceSha = null;
1953
2036
  }
1954
- else {
1955
- sourceSha = await captureBranchSha(deps, unit.branch);
1956
- }
1957
2037
  if (!sourceSha) {
1958
2038
  return {
1959
2039
  status: 502,
@@ -1975,15 +2055,21 @@ async function runMergeLocalLocked(deps, row) {
1975
2055
  const detail = (added.stderr.trim() || added.stdout.trim()).split('\n')[0] || `exit ${added.code}`;
1976
2056
  return { status: 502, body: { error: 'merge_failed', detail: `creating isolated assembly: ${detail}` } };
1977
2057
  }
2058
+ let assemblyRemovalAttempted = false;
1978
2059
  const removeAssembly = async () => {
2060
+ if (assemblyRemovalAttempted)
2061
+ return;
2062
+ assemblyRemovalAttempted = true;
1979
2063
  try {
1980
- const removed = await deps.git.run(['worktree', 'remove', '--force', assemblyPath], deps.project.path);
2064
+ const removed = await deps.git.run(['worktree', 'remove', assemblyPath], deps.project.path);
1981
2065
  if (removed.code !== 0) {
1982
2066
  cleanupWarnings.push(`assembly worktree ${assemblyPath}: ${(removed.stderr.trim() || removed.stdout.trim()).split('\n')[0] || `exit ${removed.code}`}`);
2067
+ safetyArchiveRecorder(deps, row)(assemblyPath);
1983
2068
  }
1984
2069
  }
1985
2070
  catch (err) {
1986
2071
  cleanupWarnings.push(`assembly worktree ${assemblyPath}: ${err instanceof Error ? err.message : String(err)}`);
2072
+ safetyArchiveRecorder(deps, row)(assemblyPath);
1987
2073
  }
1988
2074
  };
1989
2075
  const persistAssemblyFailure = (detail) => {
@@ -1995,97 +2081,136 @@ async function runMergeLocalLocked(deps, row) {
1995
2081
  finalizeTransition(deps, row.id);
1996
2082
  return null;
1997
2083
  };
1998
- for (const branch of toMerge) {
1999
- const r = await deps.git.run(['merge', '--no-ff', '--no-edit', branch], assemblyPath);
2000
- if (r.code !== 0) {
2001
- try {
2002
- const aborted = await deps.git.run(['merge', '--abort'], assemblyPath);
2003
- if (aborted.code !== 0)
2004
- cleanupWarnings.push(`assembly abort ${branch}: exit ${aborted.code}`);
2084
+ try {
2085
+ for (const branch of toMerge) {
2086
+ const r = await deps.git.run(['merge', '--no-ff', '--no-edit', branch], assemblyPath);
2087
+ if (r.code !== 0) {
2088
+ try {
2089
+ const aborted = await deps.git.run(['merge', '--abort'], assemblyPath);
2090
+ if (aborted.code !== 0)
2091
+ cleanupWarnings.push(`assembly abort ${branch}: exit ${aborted.code}`);
2092
+ }
2093
+ catch (err) {
2094
+ cleanupWarnings.push(`assembly abort ${branch}: ${err instanceof Error ? err.message : String(err)}`);
2095
+ }
2096
+ await removeAssembly();
2097
+ const detail = (r.stderr.trim() || r.stdout.trim()).split('\n')[0] || `exit ${r.code}`;
2098
+ const conflict = persistAssemblyFailure(`merging '${branch}': ${detail}`);
2099
+ if (conflict)
2100
+ return conflict;
2101
+ return { status: 502, body: { error: 'merge_failed', detail: `merging '${branch}': ${detail}` } };
2005
2102
  }
2006
- catch (err) {
2007
- cleanupWarnings.push(`assembly abort ${branch}: ${err instanceof Error ? err.message : String(err)}`);
2103
+ }
2104
+ const assembled = await deps.git.run(['rev-parse', '--verify', 'HEAD'], assemblyPath);
2105
+ const assembledSha = assembled.code === 0 ? assembled.stdout.trim() : '';
2106
+ if (!/^[0-9a-f]{40,64}$/i.test(assembledSha)) {
2107
+ await removeAssembly();
2108
+ const detail = 'isolated assembly produced no verifiable HEAD';
2109
+ const conflict = persistAssemblyFailure(detail);
2110
+ if (conflict)
2111
+ return conflict;
2112
+ return { status: 502, body: { error: 'merge_failed', detail } };
2113
+ }
2114
+ const [finalBranch, finalStatus, finalHead] = await Promise.all([
2115
+ deps.git.run(['symbolic-ref', '--quiet', 'HEAD'], targetRepo),
2116
+ deps.git.run(['status', '--porcelain'], targetRepo),
2117
+ deps.git.run(['rev-parse', '--verify', 'HEAD'], targetRepo),
2118
+ ]);
2119
+ const changedReason = finalBranch.code !== 0 || finalBranch.stdout.trim() !== `refs/heads/${integrationTarget}`
2120
+ ? 'wrong_branch'
2121
+ : finalStatus.code !== 0
2122
+ ? 'status_unavailable'
2123
+ : finalHead.code !== 0 || finalHead.stdout.trim() !== startingSha
2124
+ ? 'head_changed'
2125
+ : null;
2126
+ if (changedReason) {
2127
+ await removeAssembly();
2128
+ if (cleanupWarnings.length > 0) {
2129
+ const conflict = casTransition(deps, row, row.decision, { cleanupWarnings });
2130
+ if (conflict)
2131
+ return conflict;
2132
+ finalizeTransition(deps, row.id);
2008
2133
  }
2134
+ return { status: 409, body: { error: 'merge_local_blocked', reason: changedReason, base: integrationTarget } };
2135
+ }
2136
+ const advanced = await deps.git.run(['merge', '--ff-only', '--no-overwrite-ignore', assembledSha], targetRepo);
2137
+ if (advanced.code !== 0) {
2009
2138
  await removeAssembly();
2010
- const detail = (r.stderr.trim() || r.stdout.trim()).split('\n')[0] || `exit ${r.code}`;
2011
- const conflict = persistAssemblyFailure(`merging '${branch}': ${detail}`);
2139
+ if (/would be overwritten|local changes|untracked working tree/i.test(`${advanced.stderr}\n${advanced.stdout}`)) {
2140
+ return {
2141
+ status: 409,
2142
+ body: {
2143
+ error: 'merge_local_blocked', reason: 'dirty', base: integrationTarget,
2144
+ detail: (advanced.stderr.trim() || advanced.stdout.trim()).slice(0, 1200),
2145
+ },
2146
+ };
2147
+ }
2148
+ const detail = (advanced.stderr.trim() || advanced.stdout.trim()).split('\n')[0] || `exit ${advanced.code}`;
2149
+ const conflict = persistAssemblyFailure(`advancing '${integrationTarget}': ${detail}`);
2012
2150
  if (conflict)
2013
2151
  return conflict;
2014
- return { status: 502, body: { error: 'merge_failed', detail: `merging '${branch}': ${detail}` } };
2152
+ return { status: 502, body: { error: 'merge_failed', detail: `advancing '${integrationTarget}': ${detail}` } };
2015
2153
  }
2016
- }
2017
- const assembled = await deps.git.run(['rev-parse', '--verify', 'HEAD'], assemblyPath);
2018
- const assembledSha = assembled.code === 0 ? assembled.stdout.trim() : '';
2019
- if (!/^[0-9a-f]{40,64}$/i.test(assembledSha)) {
2020
- await removeAssembly();
2021
- const detail = 'isolated assembly produced no verifiable HEAD';
2022
- const conflict = persistAssemblyFailure(detail);
2023
- if (conflict)
2024
- return conflict;
2025
- return { status: 502, body: { error: 'merge_failed', detail } };
2026
- }
2027
- const [finalBranch, finalStatus, finalHead] = await Promise.all([
2028
- deps.git.run(['rev-parse', '--abbrev-ref', 'HEAD'], deps.project.path),
2029
- deps.git.run(['status', '--porcelain'], deps.project.path),
2030
- deps.git.run(['rev-parse', '--verify', 'HEAD'], deps.project.path),
2031
- ]);
2032
- const changedReason = finalBranch.code !== 0 || finalBranch.stdout.trim() !== row.base_branch
2033
- ? 'wrong_branch'
2034
- : finalStatus.code !== 0 || finalStatus.stdout.trim() !== ''
2035
- ? 'dirty'
2036
- : finalHead.code !== 0 || finalHead.stdout.trim() !== startingSha
2037
- ? 'head_changed'
2038
- : null;
2039
- if (changedReason) {
2040
- await removeAssembly();
2041
- if (cleanupWarnings.length > 0) {
2042
- const conflict = casTransition(deps, row, row.decision, { cleanupWarnings });
2154
+ const [acceptedBranch, acceptedHead, acceptedBase] = await Promise.all([
2155
+ deps.git.run(['symbolic-ref', '--quiet', 'HEAD'], targetRepo),
2156
+ deps.git.run(['rev-parse', '--verify', 'HEAD'], targetRepo),
2157
+ deps.git.run(['rev-parse', '--verify', `refs/heads/${integrationTarget}`], deps.project.path),
2158
+ ]);
2159
+ if (acceptedBranch.code !== 0 || acceptedBranch.stdout.trim() !== `refs/heads/${integrationTarget}` ||
2160
+ acceptedHead.code !== 0 || acceptedHead.stdout.trim().toLowerCase() !== assembledSha.toLowerCase() ||
2161
+ acceptedBase.code !== 0 || acceptedBase.stdout.trim().toLowerCase() !== assembledSha.toLowerCase()) {
2162
+ // An external checkout/ref edit can race the final preflight. A successful
2163
+ // Git exit is insufficient evidence that the authorized base was updated.
2164
+ // Preserve the assembled object and all source worktrees/branches for a
2165
+ // retry; never roll back the external edit or mark tickets done.
2166
+ assemblyRemovalAttempted = true;
2167
+ safetyArchiveRecorder(deps, row)(assemblyPath);
2168
+ const detail = `The integration checkout changed during the final advance. The assembled result is preserved at ${assemblyPath}.`;
2169
+ cleanupWarnings.push(detail);
2170
+ const conflict = casTransition(deps, row, row.decision, { cleanupWarnings, statusCode: 'delivery_failed', statusDetail: detail });
2043
2171
  if (conflict)
2044
2172
  return conflict;
2045
2173
  finalizeTransition(deps, row.id);
2174
+ return { status: 409, body: { error: 'merge_local_blocked', reason: 'head_changed', base: integrationTarget, detail } };
2046
2175
  }
2047
- return { status: 409, body: { error: 'merge_local_blocked', reason: changedReason, base: row.base_branch } };
2048
- }
2049
- const advanced = await deps.git.run(['merge', '--ff-only', assembledSha], deps.project.path);
2050
- if (advanced.code !== 0) {
2051
2176
  await removeAssembly();
2052
- const detail = (advanced.stderr.trim() || advanced.stdout.trim()).split('\n')[0] || `exit ${advanced.code}`;
2053
- const conflict = persistAssemblyFailure(`advancing '${row.base_branch}': ${detail}`);
2177
+ // Post-advance sweep: the work now lives on
2178
+ // the integration branch, so the launch's worktrees + branches are spent.
2179
+ cleanupWarnings.push(...await (0, rail_worktree_release_1.releaseRailWorktrees)({
2180
+ db: deps.db,
2181
+ git: deps.git,
2182
+ repoDir: deps.project.path,
2183
+ worktreeIds: releasableWorktreeIds(deps, snap),
2184
+ state: 'merged',
2185
+ expectedHeadByBranch: (0, rail_worktree_release_1.durableBranchHeads)(snap.branches),
2186
+ overlayEvidenceByBranch: (0, rail_worktree_release_1.durableOverlayCleanupEvidence)(snap.branches),
2187
+ settlementIgnoredByBranch: (0, rail_worktree_release_1.durableSettlementIgnoredPaths)(snap.branches),
2188
+ onSafetyArchive: safetyArchiveRecorder(deps, row),
2189
+ }));
2190
+ await deleteOwnedBranchesIfUnchanged(deps, row, snap, cleanupWarnings);
2191
+ const conflict = casTransitionWithTicketEffect(deps, row, 'merged', {
2192
+ deliveryOutcome: 'delivered',
2193
+ statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : 'merged',
2194
+ cleanupWarnings,
2195
+ }, {
2196
+ deliveryId: row.id,
2197
+ ticketIds: snap.ticketIds,
2198
+ targetStatus: 'done',
2199
+ jiraAction: 'merged',
2200
+ prUrl: null,
2201
+ });
2054
2202
  if (conflict)
2055
2203
  return conflict;
2056
- return { status: 502, body: { error: 'merge_failed', detail: `advancing '${row.base_branch}': ${detail}` } };
2204
+ applyTerminalTicketEffect(deps, row, 'merged', cleanupWarnings);
2205
+ finalizeTransition(deps, row.id);
2206
+ await sweepMergedChainAncestors(deps, row);
2207
+ return { status: 200, body: { ok: true, decision: 'merged', merged: true, local: true } };
2208
+ }
2209
+ finally {
2210
+ // Runner exceptions (spawn failures/timeouts) must release the temporary
2211
+ // assembly as well. A failed non-force removal leaves a recorded archive.
2212
+ await removeAssembly();
2057
2213
  }
2058
- await removeAssembly();
2059
- // Post-advance sweep: the work now lives on
2060
- // the integration branch, so the launch's worktrees + branches are spent.
2061
- cleanupWarnings.push(...await (0, rail_worktree_release_1.releaseRailWorktrees)({
2062
- db: deps.db,
2063
- git: deps.git,
2064
- repoDir: deps.project.path,
2065
- worktreeIds: releasableWorktreeIds(deps, snap),
2066
- state: 'merged',
2067
- expectedHeadByBranch: (0, rail_worktree_release_1.durableBranchHeads)(snap.branches),
2068
- overlayEvidenceByBranch: (0, rail_worktree_release_1.durableOverlayCleanupEvidence)(snap.branches),
2069
- settlementIgnoredByBranch: (0, rail_worktree_release_1.durableSettlementIgnoredPaths)(snap.branches),
2070
- onSafetyArchive: safetyArchiveRecorder(deps, row),
2071
- }));
2072
- await deleteOwnedBranchesIfUnchanged(deps, row, snap, cleanupWarnings);
2073
- const conflict = casTransitionWithTicketEffect(deps, row, 'merged', {
2074
- deliveryOutcome: 'delivered',
2075
- statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : 'merged',
2076
- cleanupWarnings,
2077
- }, {
2078
- deliveryId: row.id,
2079
- ticketIds: snap.ticketIds,
2080
- targetStatus: 'done',
2081
- jiraAction: 'merged',
2082
- prUrl: null,
2083
- });
2084
- if (conflict)
2085
- return conflict;
2086
- applyTerminalTicketEffect(deps, row, 'merged', cleanupWarnings);
2087
- finalizeTransition(deps, row.id);
2088
- return { status: 200, body: { ok: true, decision: 'merged', merged: true, local: true } };
2089
2214
  }
2090
2215
  function resolveTicketFile(deps) {
2091
2216
  if (deps.ticketFile)
@@ -2093,3 +2218,143 @@ function resolveTicketFile(deps) {
2093
2218
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: deps.project.slug, path: deps.project.path });
2094
2219
  return exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(deps.project.path);
2095
2220
  }
2221
+ // ─── Milestone chain hooks (premium-milestone-progress D4) ───────────────────
2222
+ /** Decisions a stacked sibling can sit at while its head is already merged. */
2223
+ const SWEEPABLE_DECISIONS = new Set(['on_review', 'pr_draft', 'pr_ready', 'pr_closed']);
2224
+ /**
2225
+ * Merging a STACKED delivery also lands every earlier chunk it was built on.
2226
+ * Walk the chain(s) this delivery belongs to and settle each still-undecided
2227
+ * sibling whose delivered head commit is PROVABLY an ancestor of the chain's
2228
+ * integration branch (`git merge-base --is-ancestor`) as `merged` — the same
2229
+ * CAS + ticket effect (`done`) + Jira hook a direct merge runs. Chain-local by
2230
+ * design; siblings whose head is not an ancestor are left untouched. Returns
2231
+ * the swept delivery ids. Never throws.
2232
+ */
2233
+ async function sweepMergedChainAncestors(deps, mergedRow) {
2234
+ const swept = [];
2235
+ if (deps.repositoryChildOf)
2236
+ return swept;
2237
+ let chains;
2238
+ try {
2239
+ chains = (0, milestone_chain_store_1.listChainsTouchingDelivery)(deps.db, mergedRow.id);
2240
+ }
2241
+ catch {
2242
+ return swept;
2243
+ }
2244
+ for (const chain of chains) {
2245
+ const integration = chain.integration_branch;
2246
+ if (!integration)
2247
+ continue;
2248
+ for (const entry of (0, milestone_chain_store_1.parseLaunched)(chain)) {
2249
+ if (!entry.deliveryId || entry.deliveryId === mergedRow.id || swept.includes(entry.deliveryId))
2250
+ continue;
2251
+ const other = (0, rail_pr_store_2.getPrDelivery)(deps.db, entry.deliveryId);
2252
+ if (!other || (0, rail_pr_store_2.isTerminalPrDecision)(other.decision) || !SWEEPABLE_DECISIONS.has(other.decision))
2253
+ continue;
2254
+ const snap = (0, rail_pr_store_2.toPrDeliverySnapshot)(other);
2255
+ // An assembled delivery SHA proves the whole chunk. Without that head,
2256
+ // every unit needs its own immutable proof; accepting just the first
2257
+ // ancestor could mark unmerged tickets done and delete their branches.
2258
+ const requiredShas = snap.deliverySha ? [snap.deliverySha] : snap.units.map((unit) => unit.finalSha);
2259
+ const validShas = requiredShas.filter((sha) => typeof sha === 'string' && COMMIT_SHA_RE.test(sha));
2260
+ if (requiredShas.length === 0 || validShas.length !== requiredShas.length)
2261
+ continue;
2262
+ let allAncestors = true;
2263
+ try {
2264
+ for (const sha of new Set(validShas)) {
2265
+ const r = await deps.git.run(['merge-base', '--is-ancestor', sha, integration], deps.project.path);
2266
+ if (r.code !== 0) {
2267
+ allAncestors = false;
2268
+ break;
2269
+ }
2270
+ }
2271
+ }
2272
+ catch {
2273
+ allAncestors = false;
2274
+ }
2275
+ if (!allAncestors)
2276
+ continue;
2277
+ const token = (0, ids_1.newId)();
2278
+ if (!(0, rail_pr_store_2.claimPrDeliveryOperation)(deps.db, other.id, other.decision, 'poll-merge', token))
2279
+ continue;
2280
+ let released = false;
2281
+ try {
2282
+ const claimed = { ...other, operation_token: token };
2283
+ const cleanupWarnings = await (0, rail_worktree_release_1.releaseRailWorktrees)({
2284
+ db: deps.db, git: deps.git, repoDir: deps.project.path,
2285
+ worktreeIds: releasableWorktreeIds(deps, snap), state: 'merged',
2286
+ expectedHeadByBranch: (0, rail_worktree_release_1.durableBranchHeads)(snap.branches),
2287
+ overlayEvidenceByBranch: (0, rail_worktree_release_1.durableOverlayCleanupEvidence)(snap.branches),
2288
+ settlementIgnoredByBranch: (0, rail_worktree_release_1.durableSettlementIgnoredPaths)(snap.branches),
2289
+ onSafetyArchive: safetyArchiveRecorder(deps, claimed),
2290
+ });
2291
+ await deleteOwnedBranchesIfUnchanged(deps, claimed, snap, cleanupWarnings);
2292
+ const conflict = casTransitionWithTicketEffect(deps, claimed, 'merged', {
2293
+ deliveryOutcome: 'delivered',
2294
+ statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : 'merged',
2295
+ statusDetail: `merged as part of ${mergedRow.rail_key}`,
2296
+ cleanupWarnings,
2297
+ }, {
2298
+ deliveryId: claimed.id,
2299
+ ticketIds: snap.ticketIds,
2300
+ targetStatus: 'done',
2301
+ jiraAction: 'merged',
2302
+ prUrl: claimed.pr_url,
2303
+ });
2304
+ if (conflict)
2305
+ continue;
2306
+ applyTerminalTicketEffect(deps, claimed, 'merged', cleanupWarnings);
2307
+ // Release BEFORE finalizing: a row still holding a lease defers its
2308
+ // broadcast to the lease owner's finally, which here would never come.
2309
+ (0, rail_pr_store_2.releasePrDeliveryOperation)(deps.db, other.id, token);
2310
+ released = true;
2311
+ finalizeTransition(deps, other.id);
2312
+ swept.push(other.id);
2313
+ }
2314
+ catch (err) {
2315
+ console.error(`[milestone-chain] ancestor sweep failed for ${other.id}:`, err);
2316
+ }
2317
+ finally {
2318
+ if (!released)
2319
+ (0, rail_pr_store_2.releasePrDeliveryOperation)(deps.db, other.id, token);
2320
+ }
2321
+ }
2322
+ }
2323
+ return swept;
2324
+ }
2325
+ /** A discarded delivery that a chain's later chunks build on pauses those
2326
+ * chains (`head_discarded`, head rewound to the previous chunk's branch). */
2327
+ function pauseChainsOnDiscard(deps, row) {
2328
+ if (deps.repositoryChildOf)
2329
+ return;
2330
+ try {
2331
+ const branchOf = (id) => {
2332
+ const other = (0, rail_pr_store_2.getPrDelivery)(deps.db, id);
2333
+ if (!other)
2334
+ return null;
2335
+ const s = (0, rail_pr_store_2.toPrDeliverySnapshot)(other);
2336
+ return s.branch ?? s.units.find((u) => u.succeeded && u.branch)?.branch ?? null;
2337
+ };
2338
+ const paused = (0, milestone_chain_store_1.pauseChainsForDiscardedHead)(deps.db, row.id, branchOf);
2339
+ for (const chain of paused) {
2340
+ deps.broadcast({ type: 'milestone.chain_changed', projectId: deps.project.id, chain: (0, milestone_chain_store_1.toChainSnapshot)(chain), timestamp: new Date().toISOString() });
2341
+ }
2342
+ }
2343
+ catch (err) {
2344
+ console.error('[milestone-chain] discard hook failed:', err);
2345
+ }
2346
+ }
2347
+ /** Local-integration target: a chain-launched (stacked) delivery integrates
2348
+ * into the chain's integration branch, never into its feature base. */
2349
+ function mergeLocalTargetBranch(deps, row) {
2350
+ if (deps.repositoryChildOf)
2351
+ return deps.repositoryIntegrationBranch ?? row.base_branch;
2352
+ try {
2353
+ for (const chain of (0, milestone_chain_store_1.listChainsTouchingDelivery)(deps.db, row.id)) {
2354
+ if (chain.integration_branch)
2355
+ return chain.integration_branch;
2356
+ }
2357
+ }
2358
+ catch { /* fall through */ }
2359
+ return row.base_branch;
2360
+ }