specrails-desktop 2.26.1 → 2.27.0

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 (265) hide show
  1. package/client/dist/assets/{ActivityFeedPage-C1LftFSE.js → ActivityFeedPage-BlkNVXCv.js} +1 -1
  2. package/client/dist/assets/AgentBrowserCapture-76Ri2QR1.js +1 -0
  3. package/client/dist/assets/AgentIntegrationsModal-D9hVgaBR.js +1 -0
  4. package/client/dist/assets/{AgentModeCodePane-b6UmdjwB.js → AgentModeCodePane-BUR_QxqR.js} +2 -2
  5. package/client/dist/assets/AgentModeJobsPane-CnRXelns.js +1 -0
  6. package/client/dist/assets/AgentsPage-DEt9vpzh.js +86 -0
  7. package/client/dist/assets/{AnalyticsPage-DjFf1OxU.js → AnalyticsPage-C_eT3I3i.js} +1 -1
  8. package/client/dist/assets/{BarChart-nmDbbUiR.js → BarChart-DuzYxiJ6.js} +1 -1
  9. package/client/dist/assets/CodePage-72-FLBNw.js +2 -0
  10. package/client/dist/assets/{DesktopAnalyticsPage-ee0bpk-F.js → DesktopAnalyticsPage-BSeBkF2n.js} +1 -1
  11. package/client/dist/assets/{DocsDialog-CGUzB1zX.js → DocsDialog-CGzqDyN3.js} +2 -2
  12. package/client/dist/assets/DocsPage-BB3VGFM9.js +11 -0
  13. package/client/dist/assets/{ExportDropdown-Dzk2Lkxw.js → ExportDropdown-IehskH-7.js} +1 -1
  14. package/client/dist/assets/IntegrationsPage-X8bLyuDk.js +3 -0
  15. package/client/dist/assets/InteractiveJobComposer-D1HUALME.js +19 -0
  16. package/client/dist/assets/JobDetailModal-CMg6aKSO.js +1 -0
  17. package/client/dist/assets/JobDetailPage-Bg01rzeu.js +1 -0
  18. package/client/dist/assets/JobsPage-Cwkm9yYc.js +1 -0
  19. package/client/dist/assets/{LoopBuilderPage-5ucN9Hq9.js → LoopBuilderPage-4uPNN8Xn.js} +2 -2
  20. package/client/dist/assets/{LoopPreviewModal-wKAts-Wf.js → LoopPreviewModal-BNUQ9iL8.js} +1 -1
  21. package/client/dist/assets/LoopsPage-DLXESZxq.js +1 -0
  22. package/client/dist/assets/ProjectSettingsDialog-DP6hAug-.js +1 -0
  23. package/client/dist/assets/{TemplatePreviewModal-_Dy6ceXg.js → TemplatePreviewModal-C6mndL8D.js} +1 -1
  24. package/client/dist/assets/{agent-gFi-qvxO2.js → agent-B0Rg_eLw2.js} +1 -1
  25. package/client/dist/assets/{agent-BmH52J4j.js → agent-B8f79myL.js} +1 -1
  26. package/client/dist/assets/{agent-DolxzTol.js → agent-BCH7a3z-.js} +1 -1
  27. package/client/dist/assets/{agent-Cg49-TGZ.js → agent-BELAd4RF.js} +1 -1
  28. package/client/dist/assets/{agent-DwDR2Exf2.js → agent-BrCnHlvX2.js} +1 -1
  29. package/client/dist/assets/{agent-DgDtg-0Y2.js → agent-CrcIj6WE2.js} +1 -1
  30. package/client/dist/assets/{agent-_tfCgFUk.js → agent-CtwLUmjl.js} +1 -1
  31. package/client/dist/assets/{agent-Bxb9RRNW.js → agent-xG4qLg4R.js} +1 -1
  32. package/client/dist/assets/{brain-BJ2Gd98j.js → brain-Dia32foa.js} +1 -1
  33. package/client/dist/assets/dashboard-5C2FOZQX.js +1 -0
  34. package/client/dist/assets/dashboard-B6iQ-IL_.js +1 -0
  35. package/client/dist/assets/dashboard-BFfnCtq52.js +1 -0
  36. package/client/dist/assets/dashboard-BIC8KtRf2.js +1 -0
  37. package/client/dist/assets/dashboard-BawQCyrV.js +1 -0
  38. package/client/dist/assets/dashboard-CEgg9NAr.js +1 -0
  39. package/client/dist/assets/dashboard-CiOyDh9L.js +1 -0
  40. package/client/dist/assets/dashboard-D6x5OnNj2.js +1 -0
  41. package/client/dist/assets/{dist-js-D7smLA35.js → dist-js-DN7Pxudf.js} +1 -1
  42. package/client/dist/assets/{dist-js-CXAsO0Z8.js → dist-js-D_4k5prK.js} +1 -1
  43. package/client/dist/assets/{index-B3ejvKSt.js → index-BbVOi-dY.js} +57 -55
  44. package/client/dist/assets/{index-C695aPdV.css → index-o3Q2Lii5.css} +1 -1
  45. package/client/dist/assets/{lib-BIHmUkcn.js → lib-BxSZPd0W.js} +1 -1
  46. package/client/dist/assets/settings-BFv6UbKV.js +1 -0
  47. package/client/dist/assets/{settings-BHdPchJP.js → settings-B_v3zgJf.js} +1 -1
  48. package/client/dist/assets/{settings-BeCczBFS2.js → settings-BemR_e3F.js} +1 -1
  49. package/client/dist/assets/{settings-D54_cgl12.js → settings-Bp24JHlv2.js} +1 -1
  50. package/client/dist/assets/{settings-C_nY14Yz.js → settings-BvIwQ3A1.js} +1 -1
  51. package/client/dist/assets/{settings-BlLi719f.js → settings-Bx2ObkTR2.js} +1 -1
  52. package/client/dist/assets/{settings-CaZ26a0S.js → settings-C_T0-j9d.js} +1 -1
  53. package/client/dist/assets/{settings-cn0tbve62.js → settings-DhaFKpcw2.js} +1 -1
  54. package/client/dist/assets/{upload-xdQuJvXN.js → upload-4u055Eea.js} +1 -1
  55. package/client/dist/assets/{value-Bk2f9AUg.js → value-C3Qe3VXm.js} +1 -1
  56. package/client/dist/index.html +5 -5
  57. package/package.json +1 -1
  58. package/server/dist/agent-operator-prompt.js +10 -1
  59. package/server/dist/db.js +51 -2
  60. package/server/dist/dev-ports.js +35 -0
  61. package/server/dist/index.js +2 -6
  62. package/server/dist/mcp/tools/catalog.js +2 -0
  63. package/server/dist/mcp/tools/env.js +72 -0
  64. package/server/dist/mcp/tools/jobs.js +66 -1
  65. package/server/dist/mcp/tools/rails.js +2 -0
  66. package/server/dist/path-resolver.js +74 -15
  67. package/server/dist/pr-publisher.js +17 -15
  68. package/server/dist/project-env.js +30 -0
  69. package/server/dist/project-git.js +57 -0
  70. package/server/dist/project-registry.js +4 -2
  71. package/server/dist/project-router-jobs.js +76 -0
  72. package/server/dist/project-router-settings.js +10 -1
  73. package/server/dist/queue-manager.js +9 -1
  74. package/server/dist/rail-isolated-launch.js +27 -6
  75. package/server/dist/rail-pr-decision.js +96 -18
  76. package/server/dist/rail-pr-store.js +36 -0
  77. package/server/dist/rail-worktree-release.js +25 -0
  78. package/server/dist/rail-worktrees-store.js +2 -2
  79. package/server/dist/rails-router.js +95 -9
  80. package/server/dist/transient-children.js +126 -8
  81. package/server/dist/workspace-resolution.js +3 -3
  82. package/server/dist/worktree-env-discovery.js +172 -0
  83. package/client/dist/assets/AgentBrowserCapture-B5prW-V0.js +0 -1
  84. package/client/dist/assets/AgentModeJobsPane-DW3Wj5oi.js +0 -1
  85. package/client/dist/assets/AgentsPage-DBMnhdlq.js +0 -86
  86. package/client/dist/assets/CodePage-DHd46Sm7.js +0 -2
  87. package/client/dist/assets/DocsPage-96iws84I.js +0 -11
  88. package/client/dist/assets/IntegrationsPage-Bta2lAhq.js +0 -3
  89. package/client/dist/assets/InteractiveJobComposer-B9S_ZGJS.js +0 -19
  90. package/client/dist/assets/JobDetailModal-DhrwU2z0.js +0 -1
  91. package/client/dist/assets/JobDetailPage-BnhoXkql.js +0 -1
  92. package/client/dist/assets/JobsPage-BBT9fyhg.js +0 -1
  93. package/client/dist/assets/LoopsPage-COyxf6Pa.js +0 -1
  94. package/client/dist/assets/ProjectSettingsDialog-CcToBI-Z.js +0 -1
  95. package/client/dist/assets/dashboard-90r0QcUc2.js +0 -1
  96. package/client/dist/assets/dashboard-B0C6mJP-.js +0 -1
  97. package/client/dist/assets/dashboard-BOx0FR1L2.js +0 -1
  98. package/client/dist/assets/dashboard-BcjUOLJr.js +0 -1
  99. package/client/dist/assets/dashboard-C27kNpjn.js +0 -1
  100. package/client/dist/assets/dashboard-DJAj2EbH.js +0 -1
  101. package/client/dist/assets/dashboard-DNd1Gobs.js +0 -1
  102. package/client/dist/assets/dashboard-DZWrDAYm2.js +0 -1
  103. package/client/dist/assets/settings-D9bDDe2O.js +0 -1
  104. /package/client/dist/assets/{activity-BZNfOdiS.js → activity-BZZlEG3a.js} +0 -0
  105. /package/client/dist/assets/{activity--iN4Ad2R2.js → activity-BbWiDfn-2.js} +0 -0
  106. /package/client/dist/assets/{activity-CIOLxGvh.js → activity-D3Ic-HTS.js} +0 -0
  107. /package/client/dist/assets/{activity-DtxrAUMo2.js → activity-DFbvNqhE2.js} +0 -0
  108. /package/client/dist/assets/{activity-BuVo5sPB.js → activity-DtzRy92x.js} +0 -0
  109. /package/client/dist/assets/{activity-6hM_ZSg9.js → activity-DvECTYGt.js} +0 -0
  110. /package/client/dist/assets/{activity-D2r2h3SE2.js → activity-FleOlK0l2.js} +0 -0
  111. /package/client/dist/assets/{addspec-D-JLDsUV.js → addspec-BKrH2Ucz.js} +0 -0
  112. /package/client/dist/assets/{addspec-DfhDbg5c.js → addspec-B_LQMHW3.js} +0 -0
  113. /package/client/dist/assets/{addspec-bsNDdqRm2.js → addspec-Bw_YQwo62.js} +0 -0
  114. /package/client/dist/assets/{addspec-BMtLZLW62.js → addspec-CFxGvmJR2.js} +0 -0
  115. /package/client/dist/assets/{addspec-mgPOt5q02.js → addspec-CR8MB5Fr2.js} +0 -0
  116. /package/client/dist/assets/{addspec-CP0HRuxY.js → addspec-DaA-CbTt.js} +0 -0
  117. /package/client/dist/assets/{addspec-CRuvWn8d.js → addspec-NBbcTbLc.js} +0 -0
  118. /package/client/dist/assets/{agents-BpoUaoW92.js → agents-B9pFSJQI2.js} +0 -0
  119. /package/client/dist/assets/{agents-78bTZGZh.js → agents-BHgn--3j.js} +0 -0
  120. /package/client/dist/assets/{agents-BUdEi3-N.js → agents-B_qGaB0F.js} +0 -0
  121. /package/client/dist/assets/{agents-bvyRLczL.js → agents-CP8_ezYG.js} +0 -0
  122. /package/client/dist/assets/{agents-BLhWCmDS2.js → agents-CV-_xBvX2.js} +0 -0
  123. /package/client/dist/assets/{agents-CWo-KyAB.js → agents-HFCqQH0Z.js} +0 -0
  124. /package/client/dist/assets/{agents-DDtCJ5M02.js → agents-VsyFhGOS2.js} +0 -0
  125. /package/client/dist/assets/{agentstudio-BMQxc_bz.js → agentstudio-BEp7Sb46.js} +0 -0
  126. /package/client/dist/assets/{agentstudio-Dtq3dGYk.js → agentstudio-BOjG6tgJ.js} +0 -0
  127. /package/client/dist/assets/{agentstudio-C_Jq8Tl4.js → agentstudio-Br3WD7Xs.js} +0 -0
  128. /package/client/dist/assets/{agentstudio-sI27TpBu2.js → agentstudio-CuTt3ZGe2.js} +0 -0
  129. /package/client/dist/assets/{agentstudio-CWgXG5W52.js → agentstudio-D2A5peWV2.js} +0 -0
  130. /package/client/dist/assets/{agentstudio-COZriimI2.js → agentstudio-RfZavULt2.js} +0 -0
  131. /package/client/dist/assets/{agentstudio-jKMTdT70.js → agentstudio-RsxnZtUA.js} +0 -0
  132. /package/client/dist/assets/{aiedit-igmw-ZoZ2.js → aiedit-26p5A_HY2.js} +0 -0
  133. /package/client/dist/assets/{aiedit-6CHdb_iv2.js → aiedit-CV520D4W2.js} +0 -0
  134. /package/client/dist/assets/{aiedit-pc5JDUq1.js → aiedit-CcugGPbS.js} +0 -0
  135. /package/client/dist/assets/{aiedit-eMN4eQPF.js → aiedit-D0Syl-mC.js} +0 -0
  136. /package/client/dist/assets/{aiedit-hknZlly82.js → aiedit-DnBzjlVz2.js} +0 -0
  137. /package/client/dist/assets/{aiedit-B03pD_Na.js → aiedit-DrIjSojo.js} +0 -0
  138. /package/client/dist/assets/{aiedit-BtRqRxML.js → aiedit-a-Z-g5iC.js} +0 -0
  139. /package/client/dist/assets/{analytics-C5dpiS0o.js → analytics-4-eskbtX.js} +0 -0
  140. /package/client/dist/assets/{analytics-CR1TJqUi2.js → analytics-4HvXb0Vn2.js} +0 -0
  141. /package/client/dist/assets/{analytics-rmHuJv0V.js → analytics-Bd9rHGjV.js} +0 -0
  142. /package/client/dist/assets/{analytics-BWMUZ2Pq2.js → analytics-Bpe41fnX2.js} +0 -0
  143. /package/client/dist/assets/{analytics-Bj-SmKHa.js → analytics-CpSTNfui.js} +0 -0
  144. /package/client/dist/assets/{analytics-CCprAqk3.js → analytics-DEe5BMz8.js} +0 -0
  145. /package/client/dist/assets/{analytics-DfNdXR0y2.js → analytics-DcOITHla2.js} +0 -0
  146. /package/client/dist/assets/{attachments-BjyMmrwE.js → attachments-CA2f3qL3.js} +0 -0
  147. /package/client/dist/assets/{attachments-DCZOL1RU.js → attachments-CHDbhKeH.js} +0 -0
  148. /package/client/dist/assets/{attachments-rNn1ekRy2.js → attachments-CKAY5aYG2.js} +0 -0
  149. /package/client/dist/assets/{attachments-BgBNSqQd.js → attachments-CgOQ9K-6.js} +0 -0
  150. /package/client/dist/assets/{attachments-DPCmkFZu2.js → attachments-D2jwJXQ42.js} +0 -0
  151. /package/client/dist/assets/{attachments-C9GTTJsx2.js → attachments-DlEQu56g2.js} +0 -0
  152. /package/client/dist/assets/{attachments-DL-bPy9E.js → attachments-jVdKzAXn.js} +0 -0
  153. /package/client/dist/assets/{browser-Cvg-QHvd2.js → browser-BQFDxrRq.js} +0 -0
  154. /package/client/dist/assets/{browser-rWKj-RR_2.js → browser-BbJ_q_OT2.js} +0 -0
  155. /package/client/dist/assets/{browser-Cc0iDqUi.js → browser-C4VJ5CW12.js} +0 -0
  156. /package/client/dist/assets/{browser-BPkhILSJ.js → browser-Cl-FwPXs.js} +0 -0
  157. /package/client/dist/assets/{browser-FRv8P02E.js → browser-Do2jf8_N.js} +0 -0
  158. /package/client/dist/assets/{browser-BqLqJVVT.js → browser-JsiohNmW.js} +0 -0
  159. /package/client/dist/assets/{browser-BijVYG0Y2.js → browser-kA31eYhu2.js} +0 -0
  160. /package/client/dist/assets/{chat-DdwZjT8K2.js → chat-BMWD_Rx12.js} +0 -0
  161. /package/client/dist/assets/{chat-Bom6bqK3.js → chat-BalviUAD.js} +0 -0
  162. /package/client/dist/assets/{chat-DsbwJhus.js → chat-CJddjN3D2.js} +0 -0
  163. /package/client/dist/assets/{chat-Dnwg-NpQ.js → chat-D5xEtsf3.js} +0 -0
  164. /package/client/dist/assets/{chat-vylxqR0P2.js → chat-Ds8GvCnv.js} +0 -0
  165. /package/client/dist/assets/{chat-BgL3zv6x2.js → chat-SaG-ng1x2.js} +0 -0
  166. /package/client/dist/assets/{chat-Bh3NdgJs.js → chat-n43nZGvn.js} +0 -0
  167. /package/client/dist/assets/{code-DzC1rMk12.js → code-Bit_z2Xd2.js} +0 -0
  168. /package/client/dist/assets/{code-BRcDlze52.js → code-BotxM43N2.js} +0 -0
  169. /package/client/dist/assets/{code-UPVvmAKM2.js → code-C9U6M5-e2.js} +0 -0
  170. /package/client/dist/assets/{code-BdMy3Ffq.js → code-CIPmDEd0.js} +0 -0
  171. /package/client/dist/assets/{code-DgeHz9CB.js → code-DUe1Bouz.js} +0 -0
  172. /package/client/dist/assets/{code-CwnBPPyA.js → code-F_KjccGf.js} +0 -0
  173. /package/client/dist/assets/{code-CeQ0FZpC.js → code-HDK4P6Rx.js} +0 -0
  174. /package/client/dist/assets/{commands-DkU0qliq.js → commands-BFQZc9AG.js} +0 -0
  175. /package/client/dist/assets/{commands-u-4eEHCN.js → commands-BMkC5-ki.js} +0 -0
  176. /package/client/dist/assets/{commands-CSDs9q_2.js → commands-BllkJauO.js} +0 -0
  177. /package/client/dist/assets/{commands-xHDDGfcP2.js → commands-CWX9WmDX2.js} +0 -0
  178. /package/client/dist/assets/{commands-C4Y3C-PF2.js → commands-CzFSoIiq2.js} +0 -0
  179. /package/client/dist/assets/{commands-JWMfGDmr.js → commands-KBbhsnw8.js} +0 -0
  180. /package/client/dist/assets/{commands-RHOchN4n2.js → commands-cKTaLwwV2.js} +0 -0
  181. /package/client/dist/assets/{common-DmqBPwZG.js → common-BfQD173j.js} +0 -0
  182. /package/client/dist/assets/{common-DhWTkocg.js → common-CF8iGXrG.js} +0 -0
  183. /package/client/dist/assets/{common-Bwafujnc2.js → common-Cfmwcrl52.js} +0 -0
  184. /package/client/dist/assets/{common-C-vU-j-I.js → common-D2cimsul.js} +0 -0
  185. /package/client/dist/assets/{common-C5HKSbbz.js → common-DH7fjyL1.js} +0 -0
  186. /package/client/dist/assets/{common-BM3LFLCE2.js → common-DZbXlmq72.js} +0 -0
  187. /package/client/dist/assets/{common-DiPTT7V_2.js → common-DtXmzX2o2.js} +0 -0
  188. /package/client/dist/assets/{explore-UvW1jEMa.js → explore-BEpIs343.js} +0 -0
  189. /package/client/dist/assets/{explore-CNBzrEPS.js → explore-BO_OinBD.js} +0 -0
  190. /package/client/dist/assets/{explore-C0k8HrkN.js → explore-Bhy7WWXv.js} +0 -0
  191. /package/client/dist/assets/{explore-B7fblXBb2.js → explore-CAc8Qzu32.js} +0 -0
  192. /package/client/dist/assets/{explore-D_mWBYCS2.js → explore-DCZL9gRN2.js} +0 -0
  193. /package/client/dist/assets/{explore-Dahc1lOx.js → explore-DW_YTkQh.js} +0 -0
  194. /package/client/dist/assets/{explore-BHtEpoNN2.js → explore-ELbrSZyQ2.js} +0 -0
  195. /package/client/dist/assets/{format-command-CuWuRvka.js → format-command-Db1viey1.js} +0 -0
  196. /package/client/dist/assets/{integrations-BNuauK3o.js → integrations-BQNkrfr7.js} +0 -0
  197. /package/client/dist/assets/{integrations-Cl_wD5AI.js → integrations-BdN2f6bP.js} +0 -0
  198. /package/client/dist/assets/{integrations-CHy8tnpV.js → integrations-BrIUJmVf.js} +0 -0
  199. /package/client/dist/assets/{integrations-Cy8nZ_dv2.js → integrations-Cyp7cSYv2.js} +0 -0
  200. /package/client/dist/assets/{integrations-4KZnYECp.js → integrations-DDR3OHdq.js} +0 -0
  201. /package/client/dist/assets/{integrations-7W266oqF2.js → integrations-DVxi8b0j2.js} +0 -0
  202. /package/client/dist/assets/{integrations-zMqY4VU12.js → integrations-RuNEX3La2.js} +0 -0
  203. /package/client/dist/assets/{jira-uNH1gETL2.js → jira-B5wszJ1-2.js} +0 -0
  204. /package/client/dist/assets/{jira-BHAKtc4B.js → jira-C5UjxHE4.js} +0 -0
  205. /package/client/dist/assets/{jira-D5efgwp_.js → jira-CQ7mrZde.js} +0 -0
  206. /package/client/dist/assets/{jira-bDs7Im2C.js → jira-DIRkZk9U.js} +0 -0
  207. /package/client/dist/assets/{jira-CPpgA-5z2.js → jira-DTYu3hJi2.js} +0 -0
  208. /package/client/dist/assets/{jira-DS7ownDy.js → jira-DtZpokjJ.js} +0 -0
  209. /package/client/dist/assets/{jira-Be369RIR2.js → jira-ZdKb-50F2.js} +0 -0
  210. /package/client/dist/assets/{jobs-CE8qeIYj.js → jobs-BFIdeDTw.js} +0 -0
  211. /package/client/dist/assets/{jobs-BgQzB0Sx2.js → jobs-BJ-Gcao22.js} +0 -0
  212. /package/client/dist/assets/{jobs-1GV2-4zd2.js → jobs-DAGiPVIS2.js} +0 -0
  213. /package/client/dist/assets/{jobs-26F9gGcE.js → jobs-DAU8C_tZ.js} +0 -0
  214. /package/client/dist/assets/{jobs-BqWui9_f.js → jobs-Da0GaaSc.js} +0 -0
  215. /package/client/dist/assets/{jobs-Wh7dn28q2.js → jobs-P3unxtxo2.js} +0 -0
  216. /package/client/dist/assets/{jobs-DzeuASRf.js → jobs-p360zdf1.js} +0 -0
  217. /package/client/dist/assets/{loops-CvOWsi5r.js → loops-Bi4ywYGn.js} +0 -0
  218. /package/client/dist/assets/{loops-BvqQ-bHR2.js → loops-BjK8lmoZ2.js} +0 -0
  219. /package/client/dist/assets/{loops-K5_HXfJa.js → loops-D3wdUkZV.js} +0 -0
  220. /package/client/dist/assets/{loops-D6D3XHjj.js → loops-DDs4KPu_.js} +0 -0
  221. /package/client/dist/assets/{loops-LIgO_-eM.js → loops-DKzYFcq6.js} +0 -0
  222. /package/client/dist/assets/{loops-DJNhp17p2.js → loops-Du-bNVpc2.js} +0 -0
  223. /package/client/dist/assets/{loops-vhJdbcz92.js → loops-DvGbSfxw2.js} +0 -0
  224. /package/client/dist/assets/{mcp-XJNRfxVp2.js → mcp-BG-OCJca2.js} +0 -0
  225. /package/client/dist/assets/{mcp-DtbxuH3V2.js → mcp-CbJ04oFS2.js} +0 -0
  226. /package/client/dist/assets/{mcp-sRmWE8WJ.js → mcp-CrWdq5db.js} +0 -0
  227. /package/client/dist/assets/{mcp-H-gnQUdN.js → mcp-Dd844F5_.js} +0 -0
  228. /package/client/dist/assets/{mcp-Dcl-XxkR.js → mcp-DfmzoT5t.js} +0 -0
  229. /package/client/dist/assets/{mcp-CjVKXAhD2.js → mcp-DyULurYS2.js} +0 -0
  230. /package/client/dist/assets/{mcp-N1nzp8oL.js → mcp-zSJ2TsJj.js} +0 -0
  231. /package/client/dist/assets/{nav-EI_OhEAo.js → nav-BFCUtHUQ2.js} +0 -0
  232. /package/client/dist/assets/{nav-CGy4yx3C.js → nav-BouzorQ5.js} +0 -0
  233. /package/client/dist/assets/{nav-40HzpIFb.js → nav-Bpr-K0tD.js} +0 -0
  234. /package/client/dist/assets/{nav-B6PkGfTk2.js → nav-CpFrRDXd2.js} +0 -0
  235. /package/client/dist/assets/{nav-CXEDcRdv2.js → nav-Dkv4Vlr_2.js} +0 -0
  236. /package/client/dist/assets/{nav-8yPhBn00.js → nav-GG6azCbh.js} +0 -0
  237. /package/client/dist/assets/{nav-BHqMLrS72.js → nav-prHkMqcr.js} +0 -0
  238. /package/client/dist/assets/{setup-BuQNKztt2.js → setup-1vN48B-m2.js} +0 -0
  239. /package/client/dist/assets/{setup-BwMovM9z2.js → setup-BmJ0Kx16.js} +0 -0
  240. /package/client/dist/assets/{setup-BXpw07r-.js → setup-DQWsu2TR.js} +0 -0
  241. /package/client/dist/assets/{setup-D84y3wx-.js → setup-JeBwoXoM.js} +0 -0
  242. /package/client/dist/assets/{setup-DU0ULX8q2.js → setup-T_ApeQDV2.js} +0 -0
  243. /package/client/dist/assets/{setup-BiduSX9d.js → setup-mXnzww4f.js} +0 -0
  244. /package/client/dist/assets/{setup-7J8TjG4O.js → setup-q0wRHUp1.js} +0 -0
  245. /package/client/dist/assets/{specs-CWtKcVDC.js → specs-BGRKhM8t.js} +0 -0
  246. /package/client/dist/assets/{specs-CBDvEYKv.js → specs-C6J2zrLM.js} +0 -0
  247. /package/client/dist/assets/{specs-D2clrt2c2.js → specs-CT8exGOy2.js} +0 -0
  248. /package/client/dist/assets/{specs-C9Sd7BhH2.js → specs-Ce5y2iFc2.js} +0 -0
  249. /package/client/dist/assets/{specs-CIPr9reA2.js → specs-DZDoZsDq.js} +0 -0
  250. /package/client/dist/assets/{specs-PozUNhqb.js → specs-DbtIzV8a.js} +0 -0
  251. /package/client/dist/assets/{specs-DTplt2XM.js → specs-GkiL-snY.js} +0 -0
  252. /package/client/dist/assets/{terminal-xGbCZJHg.js → terminal-BNSg2lR7.js} +0 -0
  253. /package/client/dist/assets/{terminal-De2Brd23.js → terminal-BTpE7iki.js} +0 -0
  254. /package/client/dist/assets/{terminal-BRm03iFA2.js → terminal-Cvabyzm42.js} +0 -0
  255. /package/client/dist/assets/{terminal-DxQWCqSD2.js → terminal-D0z12_sJ2.js} +0 -0
  256. /package/client/dist/assets/{terminal-Z7KsDM_n.js → terminal-DDk_bTw0.js} +0 -0
  257. /package/client/dist/assets/{terminal-cqfd3EDV.js → terminal-DEyt0prF.js} +0 -0
  258. /package/client/dist/assets/{terminal-WSAvHXoU.js → terminal-a6Uq8B0p.js} +0 -0
  259. /package/client/dist/assets/{tickets-C3b8cHhd2.js → tickets-BIxU86j12.js} +0 -0
  260. /package/client/dist/assets/{tickets-SuX_HkK4.js → tickets-C2BhwzwR.js} +0 -0
  261. /package/client/dist/assets/{tickets-DTHo9c3C.js → tickets-D1IeX2EW.js} +0 -0
  262. /package/client/dist/assets/{tickets-BoXom7l8.js → tickets-DZut8Ryt.js} +0 -0
  263. /package/client/dist/assets/{tickets-CWYFvx1W2.js → tickets-LfK6LzKl2.js} +0 -0
  264. /package/client/dist/assets/{tickets-CFXnJsYF.js → tickets-dQfsc7Ph.js} +0 -0
  265. /package/client/dist/assets/{tickets-B7zT4IRf.js → tickets-sVun0I6v.js} +0 -0
@@ -82,6 +82,7 @@ const file_story_1 = require("./file-story");
82
82
  const providers_1 = require("./providers");
83
83
  const pr_publisher_1 = require("./pr-publisher");
84
84
  const active_pr_continuation_1 = require("./active-pr-continuation");
85
+ const rail_worktree_release_1 = require("./rail-worktree-release");
85
86
  /**
86
87
  * Ticket data for the conventional branch name (pr-naming): title/labels from
87
88
  * the ticket-spec reader, the Jira key resolved with the authoritative
@@ -496,16 +497,33 @@ async function launchIsolatedRail(input, io = {}) {
496
497
  // Build-settle: persist the per-unit branch outcomes + this launch's
497
498
  // worktree ledger ids on the row (the deferred create-pr / discard actions
498
499
  // reconstruct their inputs from these — nothing survives in memory), then
499
- // hand the decision to the user. 0 succeeded units → nothing deliverable
500
- // → auto-close (per-run settle already reverted the tickets).
500
+ // hand the decision to the user. 0 succeeded units → nothing deliverable;
501
+ // keep the implementation card visible as a failed job state with run-log
502
+ // chips instead of silently auto-discarding or leaving it at "building".
501
503
  const worktreeIds = allocated.map((a) => a.ledgerId);
502
504
  const anySucceeded = results.some((r) => r.succeeded);
503
505
  const settledContinuation = anySucceeded ? launchContinuation : null;
506
+ let continuationPushFailed = false;
507
+ let continuationPushFailureReason = null;
508
+ if (settledContinuation) {
509
+ const pushed = await (0, pr_publisher_1.pushBranch)(exec, {
510
+ repoDir: baseRepo,
511
+ branch: settledContinuation.branch,
512
+ baseBranch: settledContinuation.baseBranch,
513
+ });
514
+ if (pushed.state === 'local-only') {
515
+ continuationPushFailed = true;
516
+ continuationPushFailureReason = pushed.reason;
517
+ console.warn(`[rail-isolated] existing PR follow-up push failed for ${settledContinuation.branch}: ${continuationPushFailureReason}`);
518
+ }
519
+ }
504
520
  const next = anySucceeded
505
521
  ? (settledContinuation
506
- ? (settledContinuation.isDraft === false ? 'pr_ready' : 'pr_draft')
522
+ ? (continuationPushFailed
523
+ ? 'pr_failed'
524
+ : (settledContinuation.isDraft === false ? 'pr_ready' : 'pr_draft'))
507
525
  : 'on_review')
508
- : 'discarded';
526
+ : 'implementation_failed';
509
527
  const patch = settledContinuation
510
528
  ? {
511
529
  branches,
@@ -513,17 +531,20 @@ async function launchIsolatedRail(input, io = {}) {
513
531
  branch: settledContinuation.branch,
514
532
  prUrl: settledContinuation.prUrl,
515
533
  prNumber: settledContinuation.prNumber,
516
- prState: 'pr-created',
534
+ prState: continuationPushFailed ? 'local-only' : 'pr-created',
517
535
  }
518
536
  : { branches, worktreeIds };
519
537
  if (prDeliveryId && (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', next, patch)) {
538
+ if (settledContinuation && !continuationPushFailed) {
539
+ await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: ctx.db, git, repoDir: baseRepo, worktreeIds });
540
+ }
520
541
  broadcastPrState();
521
542
  // Completion driver: refresh the origin conversation's card in place —
522
543
  // on_review asks the question, discarded informs the outcome.
523
544
  syncOriginCard('update');
524
545
  }
525
546
  if (!anySucceeded) {
526
- // AUTO-DISCARD CLEANUP (0 succeeded): unmount every worktree NOW rather
547
+ // FAILED-IMPLEMENTATION CLEANUP (0 succeeded): unmount every worktree NOW rather
527
548
  // than leaving it for a restart's reconcile sweep — a still-mounted
528
549
  // worktree POISONS the next run of the same ticket: the worktree path is
529
550
  // keyed by ticketId, so the next allocation silently reuses this
@@ -67,7 +67,9 @@ exports.executePrDecision = executePrDecision;
67
67
  const path = __importStar(require("path"));
68
68
  const artifact_registry_1 = require("./artifact-registry");
69
69
  const worktree_manager_1 = require("./worktree-manager");
70
+ const pr_publisher_1 = require("./pr-publisher");
70
71
  const rail_pr_delivery_1 = require("./rail-pr-delivery");
72
+ const rail_worktree_release_1 = require("./rail-worktree-release");
71
73
  const pr_naming_1 = require("./pr-naming");
72
74
  const pr_body_1 = require("./pr-body");
73
75
  const jira_db_1 = require("./jira/jira-db");
@@ -105,7 +107,8 @@ function actionAllowed(action, row) {
105
107
  return row.decision === 'pr_draft' && row.pr_url !== null;
106
108
  case 'discard':
107
109
  return row.decision === 'on_review' || row.decision === 'pr_draft' ||
108
- row.decision === 'pr_ready' || row.decision === 'pr_failed';
110
+ row.decision === 'pr_ready' || row.decision === 'implementation_failed' ||
111
+ row.decision === 'pr_failed';
109
112
  case 'poll-merge':
110
113
  return (row.decision === 'pr_draft' || row.decision === 'pr_ready') && row.pr_url !== null;
111
114
  case 'merge-local':
@@ -171,6 +174,46 @@ function batchWorktreeRoot(slug) {
171
174
  // Mirrors rail-isolated-launch's worktreesRoot — derivable, never stored.
172
175
  return path.join((0, artifact_registry_1.resolveHome)(), '.specrails', 'projects', slug, 'worktrees');
173
176
  }
177
+ async function resolveExistingPrDecisionAfterPush(deps, row) {
178
+ if (!row.pr_url)
179
+ return 'pr_ready';
180
+ const r = await deps.exec.run('gh', ['pr', 'view', row.pr_url, '--json', 'isDraft,state'], deps.project.path);
181
+ if (r.code !== 0)
182
+ return 'pr_ready';
183
+ try {
184
+ const parsed = JSON.parse(r.stdout);
185
+ if (parsed.isDraft === true)
186
+ return 'pr_draft';
187
+ }
188
+ catch {
189
+ /* fall through to the conservative published/open state */
190
+ }
191
+ return 'pr_ready';
192
+ }
193
+ async function retryExistingPrFollowupPush(deps, row) {
194
+ if (!row.branch || !row.pr_url)
195
+ return { status: 502, body: { error: 'push_failed', detail: 'missing PR branch/url' } };
196
+ const pushed = await (0, pr_publisher_1.pushBranch)(deps.exec, {
197
+ repoDir: deps.project.path,
198
+ branch: row.branch,
199
+ baseBranch: row.base_branch,
200
+ });
201
+ if (pushed.state === 'local-only') {
202
+ const conflict = casTransition(deps, row, 'pr_failed', { prState: 'local-only' });
203
+ if (conflict)
204
+ return conflict;
205
+ finalizeTransition(deps, row.id);
206
+ return { status: 200, body: { ok: true, decision: 'pr_failed', prUrl: row.pr_url, detail: pushed.reason } };
207
+ }
208
+ const next = await resolveExistingPrDecisionAfterPush(deps, row);
209
+ const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
210
+ const conflict = casTransition(deps, row, next, { prState: 'pr-created' });
211
+ if (conflict)
212
+ return conflict;
213
+ await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds });
214
+ const after = finalizeTransition(deps, row.id);
215
+ return { status: 200, body: { ok: true, decision: next, prUrl: after?.prUrl ?? row.pr_url, prState: 'pr-created' } };
216
+ }
174
217
  /**
175
218
  * Load the PR's ticket data (title/labels/description) from the ticket store,
176
219
  * with the Jira key resolved PER TICKET: the authoritative `jira_links` row
@@ -240,6 +283,9 @@ async function findRecoverableBranch(deps, unit, baseBranch) {
240
283
  return null;
241
284
  }
242
285
  async function runCreatePr(deps, row) {
286
+ if (row.decision === 'pr_failed' && row.pr_url && row.branch) {
287
+ return retryExistingPrFollowupPush(deps, row);
288
+ }
243
289
  const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
244
290
  // Pre-flight: every succeeded unit branch must actually exist before we push
245
291
  // it — a missing ref can only degrade to local-only again, so a retry on a
@@ -363,6 +409,9 @@ async function runCreatePr(deps, row) {
363
409
  const conflict = casTransition(deps, row, next, patch);
364
410
  if (conflict)
365
411
  return conflict;
412
+ if (next === 'pr_draft' && patch.prUrl) {
413
+ await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds });
414
+ }
366
415
  const after = finalizeTransition(deps, row.id);
367
416
  // Tickets stay on_review — a draft PR is still awaiting the engineer's merge.
368
417
  return {
@@ -380,17 +429,25 @@ async function runPublish(deps, row) {
380
429
  const r = await deps.exec.run('gh', ['pr', 'ready', row.pr_url], deps.project.path);
381
430
  if (r.code !== 0)
382
431
  return ghFailed(r); // no transition — the draft is still a draft
432
+ const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
383
433
  const conflict = casTransition(deps, row, 'pr_ready');
384
434
  if (conflict)
385
435
  return conflict;
436
+ await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds });
386
437
  finalizeTransition(deps, row.id);
387
438
  return { status: 200, body: { ok: true, decision: 'pr_ready', prUrl: row.pr_url } };
388
439
  }
389
440
  async function runDiscard(deps, row) {
390
441
  const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
442
+ const implementationFailed = row.decision === 'implementation_failed';
391
443
  // 1. Close the PR (drops its head branch) when one exists. Best-effort — a
392
444
  // gh failure must never leave the discard half-done.
393
- if (row.pr_url) {
445
+ //
446
+ // Implementation-failed rows are different: the implement run produced no
447
+ // deliverable update. If the row belongs to an existing-PR continuation it may
448
+ // still carry that PR's url/branch from launch time; clearing the failed card
449
+ // must not close or delete the user's existing review branch.
450
+ if (row.pr_url && !implementationFailed) {
394
451
  try {
395
452
  const r = await deps.exec.run('gh', ['pr', 'close', row.pr_url, '--delete-branch'], deps.project.path);
396
453
  if (r.code !== 0) {
@@ -404,12 +461,13 @@ async function runDiscard(deps, row) {
404
461
  // 2. Remove the launch's worktrees (still on disk unless a restart already
405
462
  // swept them) and close their ledger rows — the reconcile-style removal,
406
463
  // branch deletion deferred to step 3 (a checked-out branch can't be -D'd).
407
- const branchSet = new Set(snap.branches.map((b) => b.branch));
464
+ const branchSet = new Set(implementationFailed ? [] : snap.branches.map((b) => b.branch));
408
465
  for (const wtId of snap.worktreeIds) {
409
466
  const wt = (0, rail_worktrees_store_1.getRailWorktree)(deps.db, wtId);
410
467
  if (!wt)
411
468
  continue;
412
- branchSet.add(wt.branch);
469
+ if (!implementationFailed)
470
+ branchSet.add(wt.branch);
413
471
  await (0, worktree_manager_1.removeWorktree)(deps.git, {
414
472
  repoDir: deps.project.path, worktreePath: wt.worktree_path, branch: wt.branch, deleteBranch: false,
415
473
  }).catch(() => { });
@@ -421,22 +479,24 @@ async function runDiscard(deps, row) {
421
479
  // best-effort from the same ticket data the create-pr path names it from
422
480
  // (the delivered head is already covered by row.branch). Missing branches
423
481
  // are tolerated; the integration branch is NEVER deleted.
424
- if (row.branch)
425
- branchSet.add(row.branch);
426
- const succeededUnits = snap.branches.filter((b) => b.succeeded);
427
- if (succeededUnits.length > 1) {
428
- try {
429
- branchSet.add((0, pr_naming_1.batchBranchNameFor)(loadPrTicketData(deps, succeededUnits.map((b) => b.ticketId))));
482
+ if (!implementationFailed) {
483
+ if (row.branch)
484
+ branchSet.add(row.branch);
485
+ const succeededUnits = snap.branches.filter((b) => b.succeeded);
486
+ if (succeededUnits.length > 1) {
487
+ try {
488
+ branchSet.add((0, pr_naming_1.batchBranchNameFor)(loadPrTicketData(deps, succeededUnits.map((b) => b.ticketId))));
489
+ }
490
+ catch {
491
+ /* best-effort defense-in-depth only */
492
+ }
430
493
  }
431
- catch {
432
- /* best-effort defense-in-depth only */
494
+ for (const branch of branchSet) {
495
+ if (!branch || branch === row.base_branch)
496
+ continue;
497
+ await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
433
498
  }
434
499
  }
435
- for (const branch of branchSet) {
436
- if (!branch || branch === row.base_branch)
437
- continue;
438
- await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
439
- }
440
500
  const conflict = casTransition(deps, row, 'discarded');
441
501
  if (conflict)
442
502
  return conflict;
@@ -467,11 +527,29 @@ async function runPollMerge(deps, row) {
467
527
  // Not merged (OPEN/CLOSED/unknown) → observation only, nothing changes.
468
528
  return { status: 200, body: { ok: true, decision: row.decision, merged: false } };
469
529
  }
530
+ const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
531
+ const branchSet = new Set(snap.branches.map((b) => b.branch));
532
+ if (row.branch)
533
+ branchSet.add(row.branch);
534
+ const succeededUnits = snap.branches.filter((b) => b.succeeded);
535
+ if (succeededUnits.length > 1) {
536
+ try {
537
+ branchSet.add((0, pr_naming_1.batchBranchNameFor)(loadPrTicketData(deps, succeededUnits.map((b) => b.ticketId))));
538
+ }
539
+ catch {
540
+ /* best-effort cleanup only */
541
+ }
542
+ }
543
+ await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds, state: 'merged' });
544
+ for (const branch of branchSet) {
545
+ if (!branch || branch === row.base_branch)
546
+ continue;
547
+ await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
548
+ }
470
549
  const conflict = casTransition(deps, row, 'merged');
471
550
  if (conflict)
472
551
  return conflict;
473
552
  finalizeTransition(deps, row.id);
474
- const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
475
553
  const changed = applyTicketTransition(deps, snap.ticketIds, 'done');
476
554
  try {
477
555
  deps.jiraSyncManager?.onRailMerged(changed, row.id, row.pr_url);
@@ -6,6 +6,7 @@ exports.createPrDelivery = createPrDelivery;
6
6
  exports.getPrDelivery = getPrDelivery;
7
7
  exports.getActivePrDeliveryByRail = getActivePrDeliveryByRail;
8
8
  exports.listActivePrDeliveries = listActivePrDeliveries;
9
+ exports.reconcileFailedBuildingPrDeliveries = reconcileFailedBuildingPrDeliveries;
9
10
  exports.transitionDecision = transitionDecision;
10
11
  exports.toPrDeliverySnapshot = toPrDeliverySnapshot;
11
12
  exports.toRailPrStateMessage = toRailPrStateMessage;
@@ -19,6 +20,7 @@ exports.ACTIVE_PR_DECISIONS = new Set([
19
20
  'on_review',
20
21
  'pr_draft',
21
22
  'pr_ready',
23
+ 'implementation_failed',
22
24
  'pr_failed',
23
25
  ]);
24
26
  function isTerminalPrDecision(s) {
@@ -60,6 +62,40 @@ function listActivePrDeliveries(db) {
60
62
  ORDER BY rail_index, created_at DESC, rowid DESC`)
61
63
  .all();
62
64
  }
65
+ /**
66
+ * Recover implementation cards stranded at `building` after the loop process
67
+ * already settled all associated run ids without a success. This covers server
68
+ * restarts and the settle gap where loop_runs/worktrees mark failure but the
69
+ * PR-delivery row never receives its final card state.
70
+ */
71
+ function reconcileFailedBuildingPrDeliveries(db) {
72
+ const rows = db
73
+ .prepare(`SELECT * FROM rail_pr_deliveries WHERE decision = 'building' ORDER BY created_at ASC, rowid ASC`)
74
+ .all();
75
+ const reconciled = [];
76
+ for (const row of rows) {
77
+ const runIds = parseJsonArray(row.run_ids ?? '[]');
78
+ if (runIds.length === 0)
79
+ continue;
80
+ const uniqueRunIds = [...new Set(runIds)];
81
+ const placeholders = uniqueRunIds.map(() => '?').join(',');
82
+ const runs = db
83
+ .prepare(`SELECT id, status, final_outcome FROM loop_runs WHERE id IN (${placeholders})`)
84
+ .all(...uniqueRunIds);
85
+ if (runs.length !== uniqueRunIds.length)
86
+ continue;
87
+ if (runs.some((run) => run.status !== 'completed'))
88
+ continue;
89
+ if (runs.some((run) => run.final_outcome === 'success'))
90
+ continue;
91
+ if (transitionDecision(db, row.id, 'building', 'implementation_failed')) {
92
+ const updated = getPrDelivery(db, row.id);
93
+ if (updated)
94
+ reconciled.push(updated);
95
+ }
96
+ }
97
+ return reconciled;
98
+ }
63
99
  // Column allow-list — patch keys are interpolated into the SET clause, so gate
64
100
  // them to prevent any future caller injecting SQL via an object key (values
65
101
  // are always parameterized). Mirrors loop-runs-store's COUNTER_COLUMNS.
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.releaseRailWorktrees = releaseRailWorktrees;
4
+ const rail_worktrees_store_1 = require("./rail-worktrees-store");
5
+ const worktree_manager_1 = require("./worktree-manager");
6
+ /** Remove Specrails-managed linked worktrees while keeping their local branches.
7
+ * PR deliveries use this once the work has a durable remote branch/PR: the
8
+ * branch remains available for local checkout, but the internal worktree stops
9
+ * blocking `git switch <branch>` in the user's main checkout. */
10
+ async function releaseRailWorktrees(input) {
11
+ const state = input.state ?? 'released';
12
+ for (const wtId of input.worktreeIds) {
13
+ const wt = (0, rail_worktrees_store_1.getRailWorktree)(input.db, wtId);
14
+ if (!wt)
15
+ continue;
16
+ await (0, worktree_manager_1.removeWorktree)(input.git, {
17
+ repoDir: input.repoDir,
18
+ worktreePath: wt.worktree_path,
19
+ branch: wt.branch,
20
+ deleteBranch: false,
21
+ }).catch(() => { });
22
+ if (!(0, rail_worktrees_store_1.isTerminalMergeState)(wt.merge_state))
23
+ (0, rail_worktrees_store_1.updateRailWorktreeState)(input.db, wt.id, state);
24
+ }
25
+ }
@@ -12,7 +12,7 @@ exports.deleteRailWorktree = deleteRailWorktree;
12
12
  exports.listRailWorktreesForTicket = listRailWorktreesForTicket;
13
13
  exports.railWorktreeBranchExistsForTicket = railWorktreeBranchExistsForTicket;
14
14
  /** States after which the worktree is done (eligible for cleanup/reconciliation). */
15
- exports.TERMINAL_MERGE_STATES = new Set(['merged', 'needs-review', 'failed']);
15
+ exports.TERMINAL_MERGE_STATES = new Set(['merged', 'needs-review', 'failed', 'released']);
16
16
  function isTerminalMergeState(s) {
17
17
  return exports.TERMINAL_MERGE_STATES.has(s);
18
18
  }
@@ -46,7 +46,7 @@ function listRailWorktrees(db, railIndex) {
46
46
  * process (mark failed + sweep). */
47
47
  function listNonTerminalRailWorktrees(db) {
48
48
  return db
49
- .prepare(`SELECT * FROM rail_worktrees WHERE merge_state NOT IN ('merged','needs-review','failed') ORDER BY rail_index, ticket_id`)
49
+ .prepare(`SELECT * FROM rail_worktrees WHERE merge_state NOT IN ('merged','needs-review','failed','released') ORDER BY rail_index, ticket_id`)
50
50
  .all();
51
51
  }
52
52
  function deleteRailWorktree(db, id) {
@@ -23,6 +23,8 @@ const loop_effect_1 = require("./loop-effect");
23
23
  const rail_pr_decision_1 = require("./rail-pr-decision");
24
24
  const rail_isolated_launch_1 = require("./rail-isolated-launch");
25
25
  const worktree_manager_1 = require("./worktree-manager");
26
+ const rail_worktree_release_1 = require("./rail-worktree-release");
27
+ const project_git_1 = require("./project-git");
26
28
  const pr_publisher_1 = require("./pr-publisher");
27
29
  const ids_1 = require("./ids");
28
30
  const agent_chat_registry_1 = require("./agent-chat-registry");
@@ -36,7 +38,7 @@ const VALID_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
36
38
  function prDeliveryContinuesTickets(delivery, ticketIds) {
37
39
  if (delivery.decision !== 'pr_draft' && delivery.decision !== 'pr_ready')
38
40
  return false;
39
- if (!delivery.prUrl || !delivery.branch)
41
+ if (!delivery.prUrl || !delivery.branch || delivery.prState !== 'pr-created')
40
42
  return false;
41
43
  const covered = new Set(delivery.ticketIds);
42
44
  return ticketIds.length > 0 && ticketIds.every((id) => covered.has(id));
@@ -51,6 +53,11 @@ function emitPrDeliveryUpdate(c, prDeliveryId) {
51
53
  (0, agent_chat_registry_1.getAgentChatManager)()?.updatePrDecisionCard(row.origin_conversation_id, (0, rail_pr_store_1.toPrDecisionCardEnvelope)(c.project.id, snap));
52
54
  }
53
55
  }
56
+ function reconcileAndEmitFailedPrDeliveries(c) {
57
+ for (const row of (0, rail_pr_store_1.reconcileFailedBuildingPrDeliveries)(c.db)) {
58
+ emitPrDeliveryUpdate(c, row.id);
59
+ }
60
+ }
54
61
  function createRailsRouter() {
55
62
  const router = (0, express_1.Router)({ mergeParams: true });
56
63
  function ctx(req) {
@@ -114,6 +121,7 @@ function createRailsRouter() {
114
121
  // (non-terminal) delivery per rail slot, so a refreshed client re-renders
115
122
  // the decision surface without waiting for a broadcast. The store lists
116
123
  // newest-first within each rail — keep the first per index.
124
+ reconcileAndEmitFailedPrDeliveries(c);
117
125
  const prDeliveries = {};
118
126
  for (const row of (0, rail_pr_store_1.listActivePrDeliveries)(c.db)) {
119
127
  if (!(row.rail_index in prDeliveries))
@@ -674,15 +682,43 @@ function createRailsRouter() {
674
682
  if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'building', { runIds: loopRunIds })) {
675
683
  emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
676
684
  }
677
- void Promise.allSettled(sharedRunSettles).then(() => {
678
- const current = (0, rail_pr_store_1.getPrDelivery)(c.db, fallbackPrDeliveryId);
679
- if (current?.decision !== 'building')
680
- return;
681
- if (current.pr_state === 'pr-created' && current.pr_url && current.branch) {
682
- if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_ready')) {
683
- emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
685
+ void Promise.allSettled(sharedRunSettles).then((settled) => {
686
+ void (async () => {
687
+ const current = (0, rail_pr_store_1.getPrDelivery)(c.db, fallbackPrDeliveryId);
688
+ if (current?.decision !== 'building')
689
+ return;
690
+ if (current.pr_state === 'pr-created' && current.pr_url && current.branch) {
691
+ const succeeded = settled.every((s) => s.status === 'fulfilled' && s.value === true);
692
+ if (!succeeded) {
693
+ if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'implementation_failed')) {
694
+ emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
695
+ }
696
+ return;
697
+ }
698
+ await (0, worktree_manager_1.commitWorktree)(worktree_manager_1.defaultGitRunner, c.project.path, `specrails: PR follow-up (${loopRunIds.join(', ')})`);
699
+ const pushed = await (0, pr_publisher_1.pushBranch)(pr_publisher_1.defaultExec, {
700
+ repoDir: c.project.path,
701
+ branch: current.branch,
702
+ baseBranch: current.base_branch,
703
+ });
704
+ if (pushed.state === 'local-only') {
705
+ if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_failed', { prState: 'local-only' })) {
706
+ emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
707
+ }
708
+ return;
709
+ }
710
+ if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_ready', { prState: 'pr-created' })) {
711
+ emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
712
+ }
684
713
  }
685
- }
714
+ })().catch((err) => {
715
+ console.error('[rails-router] existing PR fallback push failed:', err);
716
+ if (fallbackPrDeliveryId) {
717
+ if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_failed', { prState: 'local-only' })) {
718
+ emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
719
+ }
720
+ }
721
+ });
686
722
  });
687
723
  }
688
724
  }
@@ -883,5 +919,55 @@ function createRailsRouter() {
883
919
  res.status(500).json({ error: 'pr-decision failed', detail: err.message });
884
920
  }
885
921
  });
922
+ // POST /rails/pr-checkout — move the user's main checkout to the delivered PR
923
+ // branch. This is intentionally separate from PR decisions: it does not accept,
924
+ // publish, discard, or merge anything. It only releases Specrails-managed
925
+ // worktrees for this delivery (keeping branches) and then checks out the PR
926
+ // branch in the real project repo so the user can test/edit locally.
927
+ router.post('/pr-checkout', async (req, res) => {
928
+ const c = ctx(req);
929
+ const { prDeliveryId } = req.body ?? {};
930
+ if (typeof prDeliveryId !== 'string' || !prDeliveryId) {
931
+ res.status(400).json({ error: 'prDeliveryId is required' });
932
+ return;
933
+ }
934
+ const row = (0, rail_pr_store_1.getPrDelivery)(c.db, prDeliveryId);
935
+ if (!row) {
936
+ res.status(404).json({ error: 'Unknown prDeliveryId' });
937
+ return;
938
+ }
939
+ const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
940
+ if (!snap.branch || !snap.prUrl) {
941
+ res.status(409).json({ error: 'checkout_unavailable', detail: 'delivery has no PR branch' });
942
+ return;
943
+ }
944
+ try {
945
+ const info = await (0, project_git_1.getProjectGitInfo)(c.project.path);
946
+ if (!info.git) {
947
+ res.status(409).json({ error: 'checkout_unavailable', detail: 'project is not a git repository' });
948
+ return;
949
+ }
950
+ if (info.dirty) {
951
+ res.status(409).json({ error: 'checkout_dirty', detail: 'Working tree has uncommitted changes. Commit or stash them before checkout.' });
952
+ return;
953
+ }
954
+ await (0, rail_worktree_release_1.releaseRailWorktrees)({
955
+ db: c.db,
956
+ git: worktree_manager_1.defaultGitRunner,
957
+ repoDir: c.project.path,
958
+ worktreeIds: snap.worktreeIds,
959
+ });
960
+ const result = await (0, project_git_1.checkoutProjectReviewBranch)(c.project.path, snap.branch);
961
+ if (!result.ok) {
962
+ res.status(409).json({ error: 'checkout_failed', detail: result.error });
963
+ return;
964
+ }
965
+ res.json({ ok: true, git: await (0, project_git_1.getProjectGitInfo)(c.project.path) });
966
+ }
967
+ catch (err) {
968
+ console.error('[rails-router] pr-checkout error:', err);
969
+ res.status(500).json({ error: 'pr-checkout failed', detail: err.message });
970
+ }
971
+ });
886
972
  return router;
887
973
  }