specrails-desktop 2.26.2 → 2.28.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 (319) hide show
  1. package/client/dist/assets/{ActivityFeedPage-bDe4VitF.js → ActivityFeedPage-DcMmDAP3.js} +1 -1
  2. package/client/dist/assets/AgentBrowserCapture-CZ3oc1UL.js +1 -0
  3. package/client/dist/assets/AgentIntegrationsModal-DkqooCFn.js +1 -0
  4. package/client/dist/assets/{AgentModeCodePane-Ci39DcOo.js → AgentModeCodePane-CM77ljte.js} +2 -2
  5. package/client/dist/assets/AgentModeJobsPane-DbZzogbi.js +1 -0
  6. package/client/dist/assets/AgentsPage-BZIml7eL.js +86 -0
  7. package/client/dist/assets/{AnalyticsPage-Da21wWWw.js → AnalyticsPage-B4qflNxi.js} +1 -1
  8. package/client/dist/assets/{BarChart-BiLTq3uC.js → BarChart-Ckstq9fl.js} +1 -1
  9. package/client/dist/assets/CodePage-DFZXLbI1.js +2 -0
  10. package/client/dist/assets/{DesktopAnalyticsPage-COkzgMmd.js → DesktopAnalyticsPage-DbgMa8OO.js} +1 -1
  11. package/client/dist/assets/DocsDialog-BQc9Kwev.js +11 -0
  12. package/client/dist/assets/DocsPage-OsV44ECy.js +11 -0
  13. package/client/dist/assets/{ExportDropdown-DDhJH-Jr.js → ExportDropdown-a43XHO3Y.js} +1 -1
  14. package/client/dist/assets/IntegrationsPage-C3E8hZhB.js +1 -0
  15. package/client/dist/assets/InteractiveJobComposer-zj8SrOAl.js +19 -0
  16. package/client/dist/assets/JobDetailModal-Cm6ZeVoh.js +1 -0
  17. package/client/dist/assets/JobDetailPage-DN67LsdX.js +1 -0
  18. package/client/dist/assets/JobsPage-DF9wKGbw.js +1 -0
  19. package/client/dist/assets/{LoopBuilderPage-DsLLrtHa.js → LoopBuilderPage-RJ6GGjli.js} +2 -2
  20. package/client/dist/assets/{LoopPreviewModal-C-fDEgnu.js → LoopPreviewModal-B2wmifF6.js} +1 -1
  21. package/client/dist/assets/LoopsPage-DdlvH-dJ.js +1 -0
  22. package/client/dist/assets/PluginsPage-9DI0a6jI.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-dgy5hqPd.js +1 -0
  24. package/client/dist/assets/{TemplatePreviewModal-DcHFbLA8.js → TemplatePreviewModal-CERf1IYB.js} +1 -1
  25. package/client/dist/assets/agent-BRJCESkc2.js +1 -0
  26. package/client/dist/assets/agent-BuYz6fJd.js +1 -0
  27. package/client/dist/assets/agent-CxnRzL6Z.js +1 -0
  28. package/client/dist/assets/agent-D4JAuEGN2.js +1 -0
  29. package/client/dist/assets/agent-DNQlI8EN.js +1 -0
  30. package/client/dist/assets/agent-DyzUH89R.js +1 -0
  31. package/client/dist/assets/agent-MaTxBlg5.js +1 -0
  32. package/client/dist/assets/agent-ostmMYNz2.js +1 -0
  33. package/client/dist/assets/{brain-BBNUUrUI.js → brain-BPbhM1Jx.js} +1 -1
  34. package/client/dist/assets/dashboard-5C2FOZQX.js +1 -0
  35. package/client/dist/assets/{dashboard-C6w0rICe.js → dashboard-B6iQ-IL_.js} +1 -1
  36. package/client/dist/assets/dashboard-BFfnCtq52.js +1 -0
  37. package/client/dist/assets/dashboard-BIC8KtRf2.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/dashboard-DBuLfWo9.js +1 -0
  42. package/client/dist/assets/{dist-js-BxjTIvdT.js → dist-js-Byjcju1W.js} +1 -1
  43. package/client/dist/assets/{dist-js-DdP_Gl3p.js → dist-js-Dh-9lbLD.js} +1 -1
  44. package/client/dist/assets/index-89SuhD4Y.js +158 -0
  45. package/client/dist/assets/index-DOGzWJhO.css +2 -0
  46. package/client/dist/assets/integrations-By80EBkf.js +1 -0
  47. package/client/dist/assets/integrations-CAjq5tdW2.js +1 -0
  48. package/client/dist/assets/integrations-CG2dp3Kd.js +1 -0
  49. package/client/dist/assets/integrations-CP_aiV_8.js +1 -0
  50. package/client/dist/assets/integrations-CUNe3t9l2.js +1 -0
  51. package/client/dist/assets/integrations-CWjh5VRk.js +1 -0
  52. package/client/dist/assets/integrations-JOHSCvB3.js +1 -0
  53. package/client/dist/assets/integrations-RJfzXkPx2.js +1 -0
  54. package/client/dist/assets/{lib-BviHZ_gK.js → lib-yKyTNJ1c.js} +1 -1
  55. package/client/dist/assets/nav-B2_ToNqS.js +1 -0
  56. package/client/dist/assets/nav-BAMkVEId.js +1 -0
  57. package/client/dist/assets/nav-BTg8s-cD.js +1 -0
  58. package/client/dist/assets/nav-BY_4EWRv.js +1 -0
  59. package/client/dist/assets/nav-CVXtv0wX2.js +1 -0
  60. package/client/dist/assets/nav-D0Erv9ZJ2.js +1 -0
  61. package/client/dist/assets/nav-DUuw9VIU2.js +1 -0
  62. package/client/dist/assets/nav-S4HM94vW.js +1 -0
  63. package/client/dist/assets/settings-BYCMtxOz.js +1 -0
  64. package/client/dist/assets/{settings-BHdPchJP.js → settings-B_v3zgJf.js} +1 -1
  65. package/client/dist/assets/{settings-BeCczBFS2.js → settings-BemR_e3F.js} +1 -1
  66. package/client/dist/assets/{settings-D54_cgl12.js → settings-Bp24JHlv2.js} +1 -1
  67. package/client/dist/assets/{settings-C_nY14Yz.js → settings-BvIwQ3A1.js} +1 -1
  68. package/client/dist/assets/{settings-BlLi719f.js → settings-Bx2ObkTR2.js} +1 -1
  69. package/client/dist/assets/{settings-CaZ26a0S.js → settings-C_T0-j9d.js} +1 -1
  70. package/client/dist/assets/{settings-cn0tbve62.js → settings-DhaFKpcw2.js} +1 -1
  71. package/client/dist/assets/{upload-CRuuzzvp.js → upload-Qh1NE3xi.js} +1 -1
  72. package/client/dist/assets/{value-CIr5hDMt.js → value-BMPeyInx.js} +1 -1
  73. package/client/dist/index.html +29 -29
  74. package/package.json +1 -1
  75. package/server/dist/agent-chat-router.js +2 -0
  76. package/server/dist/agent-operator-prompt.js +17 -1
  77. package/server/dist/db.js +51 -2
  78. package/server/dist/dev-ports.js +35 -0
  79. package/server/dist/global-plugins-router.js +62 -0
  80. package/server/dist/headroom-manager.js +971 -0
  81. package/server/dist/headroom-routing.js +53 -0
  82. package/server/dist/index.js +9 -6
  83. package/server/dist/mcp/guide.js +3 -0
  84. package/server/dist/mcp/tools/catalog.js +2 -0
  85. package/server/dist/mcp/tools/env.js +72 -0
  86. package/server/dist/mcp/tools/jobs.js +110 -2
  87. package/server/dist/path-resolver.js +84 -18
  88. package/server/dist/project-env.js +30 -0
  89. package/server/dist/project-registry.js +4 -2
  90. package/server/dist/project-router-jobs.js +126 -0
  91. package/server/dist/project-router-settings.js +10 -1
  92. package/server/dist/queue-manager.js +9 -1
  93. package/server/dist/rail-isolated-launch.js +5 -4
  94. package/server/dist/rail-pr-decision.js +27 -17
  95. package/server/dist/rail-pr-store.js +36 -0
  96. package/server/dist/rails-router.js +7 -1
  97. package/server/dist/setup-manager.js +32 -29
  98. package/server/dist/setup-prerequisites.js +3 -1
  99. package/server/dist/transient-children.js +266 -9
  100. package/server/dist/util/cli-prompt.js +3 -0
  101. package/server/dist/workspace-resolution.js +3 -3
  102. package/server/dist/worktree-env-discovery.js +172 -0
  103. package/client/dist/assets/AgentBrowserCapture-DQyeEm2V.js +0 -1
  104. package/client/dist/assets/AgentModeJobsPane-C-g1WNVG.js +0 -1
  105. package/client/dist/assets/AgentsPage-BWgglK7V.js +0 -86
  106. package/client/dist/assets/CodePage-BUcAp7SC.js +0 -2
  107. package/client/dist/assets/DocsDialog-PZHMN18B.js +0 -11
  108. package/client/dist/assets/DocsPage-D3fChfkj.js +0 -11
  109. package/client/dist/assets/IntegrationsPage-F87uxxVH.js +0 -3
  110. package/client/dist/assets/InteractiveJobComposer-DqDCiCa0.js +0 -19
  111. package/client/dist/assets/JobDetailModal-BWJR60fi.js +0 -1
  112. package/client/dist/assets/JobDetailPage-CjWE_jyd.js +0 -1
  113. package/client/dist/assets/JobsPage-LmnXkyCa.js +0 -1
  114. package/client/dist/assets/LoopsPage-DDNO9h9T.js +0 -1
  115. package/client/dist/assets/ProjectSettingsDialog-CX854C8e.js +0 -1
  116. package/client/dist/assets/agent-BI6vr-To.js +0 -1
  117. package/client/dist/assets/agent-BUGXI3Ok2.js +0 -1
  118. package/client/dist/assets/agent-Bph_-xt7.js +0 -1
  119. package/client/dist/assets/agent-Bvy2Csyd.js +0 -1
  120. package/client/dist/assets/agent-Bwkyg2oX2.js +0 -1
  121. package/client/dist/assets/agent-CLCyDaZh.js +0 -1
  122. package/client/dist/assets/agent-Cqs14QMr2.js +0 -1
  123. package/client/dist/assets/agent-DJ0EBUuP.js +0 -1
  124. package/client/dist/assets/dashboard-BjjS1OrL.js +0 -1
  125. package/client/dist/assets/dashboard-Bk6oSk1H2.js +0 -1
  126. package/client/dist/assets/dashboard-BrH-GdOg2.js +0 -1
  127. package/client/dist/assets/dashboard-BurhUI8_.js +0 -1
  128. package/client/dist/assets/dashboard-CAUnfDQL2.js +0 -1
  129. package/client/dist/assets/dashboard-C_ljpeYO.js +0 -1
  130. package/client/dist/assets/dashboard-D6OqDdj6.js +0 -1
  131. package/client/dist/assets/index-C695aPdV.css +0 -2
  132. package/client/dist/assets/index-Dw1N2-ci.js +0 -156
  133. package/client/dist/assets/integrations-4KZnYECp.js +0 -1
  134. package/client/dist/assets/integrations-7W266oqF2.js +0 -1
  135. package/client/dist/assets/integrations-BNuauK3o.js +0 -1
  136. package/client/dist/assets/integrations-CHy8tnpV.js +0 -1
  137. package/client/dist/assets/integrations-Cl_wD5AI.js +0 -1
  138. package/client/dist/assets/integrations-CuWnMR5L.js +0 -1
  139. package/client/dist/assets/integrations-Cy8nZ_dv2.js +0 -1
  140. package/client/dist/assets/integrations-zMqY4VU12.js +0 -1
  141. package/client/dist/assets/nav-40HzpIFb.js +0 -1
  142. package/client/dist/assets/nav-8yPhBn00.js +0 -1
  143. package/client/dist/assets/nav-B6PkGfTk2.js +0 -1
  144. package/client/dist/assets/nav-BHqMLrS72.js +0 -1
  145. package/client/dist/assets/nav-CGy4yx3C.js +0 -1
  146. package/client/dist/assets/nav-CXEDcRdv2.js +0 -1
  147. package/client/dist/assets/nav-CzKzuwH8.js +0 -1
  148. package/client/dist/assets/nav-EI_OhEAo.js +0 -1
  149. package/client/dist/assets/settings-D9bDDe2O.js +0 -1
  150. /package/client/dist/assets/{activity-BZNfOdiS.js → activity-BZZlEG3a.js} +0 -0
  151. /package/client/dist/assets/{activity--iN4Ad2R2.js → activity-BbWiDfn-2.js} +0 -0
  152. /package/client/dist/assets/{activity-CIOLxGvh.js → activity-D3Ic-HTS.js} +0 -0
  153. /package/client/dist/assets/{activity-DtxrAUMo2.js → activity-DFbvNqhE2.js} +0 -0
  154. /package/client/dist/assets/{activity-D8vQJrpW.js → activity-Dbl71S-E.js} +0 -0
  155. /package/client/dist/assets/{activity-BuVo5sPB.js → activity-DtzRy92x.js} +0 -0
  156. /package/client/dist/assets/{activity-6hM_ZSg9.js → activity-DvECTYGt.js} +0 -0
  157. /package/client/dist/assets/{activity-D2r2h3SE2.js → activity-FleOlK0l2.js} +0 -0
  158. /package/client/dist/assets/{addspec-D-JLDsUV.js → addspec-BKrH2Ucz.js} +0 -0
  159. /package/client/dist/assets/{addspec-DfhDbg5c.js → addspec-B_LQMHW3.js} +0 -0
  160. /package/client/dist/assets/{addspec-bsNDdqRm2.js → addspec-Bw_YQwo62.js} +0 -0
  161. /package/client/dist/assets/{addspec-BMtLZLW62.js → addspec-CFxGvmJR2.js} +0 -0
  162. /package/client/dist/assets/{addspec-mgPOt5q02.js → addspec-CR8MB5Fr2.js} +0 -0
  163. /package/client/dist/assets/{addspec-CP0HRuxY.js → addspec-DaA-CbTt.js} +0 -0
  164. /package/client/dist/assets/{addspec-DxW6oPsk.js → addspec-DrXqP8ip.js} +0 -0
  165. /package/client/dist/assets/{addspec-CRuvWn8d.js → addspec-NBbcTbLc.js} +0 -0
  166. /package/client/dist/assets/{agents-BpoUaoW92.js → agents-B9pFSJQI2.js} +0 -0
  167. /package/client/dist/assets/{agents-78bTZGZh.js → agents-BHgn--3j.js} +0 -0
  168. /package/client/dist/assets/{agents-BUdEi3-N.js → agents-B_qGaB0F.js} +0 -0
  169. /package/client/dist/assets/{agents-Cev2oFfX.js → agents-CGDn_3kB.js} +0 -0
  170. /package/client/dist/assets/{agents-bvyRLczL.js → agents-CP8_ezYG.js} +0 -0
  171. /package/client/dist/assets/{agents-BLhWCmDS2.js → agents-CV-_xBvX2.js} +0 -0
  172. /package/client/dist/assets/{agents-CWo-KyAB.js → agents-HFCqQH0Z.js} +0 -0
  173. /package/client/dist/assets/{agents-DDtCJ5M02.js → agents-VsyFhGOS2.js} +0 -0
  174. /package/client/dist/assets/{agentstudio-BMQxc_bz.js → agentstudio-BEp7Sb46.js} +0 -0
  175. /package/client/dist/assets/{agentstudio-Dtq3dGYk.js → agentstudio-BOjG6tgJ.js} +0 -0
  176. /package/client/dist/assets/{agentstudio-C_Jq8Tl4.js → agentstudio-Br3WD7Xs.js} +0 -0
  177. /package/client/dist/assets/{agentstudio-sI27TpBu2.js → agentstudio-CuTt3ZGe2.js} +0 -0
  178. /package/client/dist/assets/{agentstudio-CWgXG5W52.js → agentstudio-D2A5peWV2.js} +0 -0
  179. /package/client/dist/assets/{agentstudio-CZm2rOzt.js → agentstudio-Dxe_94PK.js} +0 -0
  180. /package/client/dist/assets/{agentstudio-COZriimI2.js → agentstudio-RfZavULt2.js} +0 -0
  181. /package/client/dist/assets/{agentstudio-jKMTdT70.js → agentstudio-RsxnZtUA.js} +0 -0
  182. /package/client/dist/assets/{aiedit-igmw-ZoZ2.js → aiedit-26p5A_HY2.js} +0 -0
  183. /package/client/dist/assets/{aiedit-6CHdb_iv2.js → aiedit-CV520D4W2.js} +0 -0
  184. /package/client/dist/assets/{aiedit-pc5JDUq1.js → aiedit-CcugGPbS.js} +0 -0
  185. /package/client/dist/assets/{aiedit-eMN4eQPF.js → aiedit-D0Syl-mC.js} +0 -0
  186. /package/client/dist/assets/{aiedit-hknZlly82.js → aiedit-DnBzjlVz2.js} +0 -0
  187. /package/client/dist/assets/{aiedit-B03pD_Na.js → aiedit-DrIjSojo.js} +0 -0
  188. /package/client/dist/assets/{aiedit-DYtnZU9H.js → aiedit-DrfHQnlz.js} +0 -0
  189. /package/client/dist/assets/{aiedit-BtRqRxML.js → aiedit-a-Z-g5iC.js} +0 -0
  190. /package/client/dist/assets/{analytics-C5dpiS0o.js → analytics-4-eskbtX.js} +0 -0
  191. /package/client/dist/assets/{analytics-CR1TJqUi2.js → analytics-4HvXb0Vn2.js} +0 -0
  192. /package/client/dist/assets/{analytics-rmHuJv0V.js → analytics-Bd9rHGjV.js} +0 -0
  193. /package/client/dist/assets/{analytics-BWMUZ2Pq2.js → analytics-Bpe41fnX2.js} +0 -0
  194. /package/client/dist/assets/{analytics-Bj-SmKHa.js → analytics-CpSTNfui.js} +0 -0
  195. /package/client/dist/assets/{analytics-Ce-sPORu.js → analytics-CxDYcBl9.js} +0 -0
  196. /package/client/dist/assets/{analytics-CCprAqk3.js → analytics-DEe5BMz8.js} +0 -0
  197. /package/client/dist/assets/{analytics-DfNdXR0y2.js → analytics-DcOITHla2.js} +0 -0
  198. /package/client/dist/assets/{attachments-BjyMmrwE.js → attachments-CA2f3qL3.js} +0 -0
  199. /package/client/dist/assets/{attachments-DCZOL1RU.js → attachments-CHDbhKeH.js} +0 -0
  200. /package/client/dist/assets/{attachments-rNn1ekRy2.js → attachments-CKAY5aYG2.js} +0 -0
  201. /package/client/dist/assets/{attachments-BgBNSqQd.js → attachments-CgOQ9K-6.js} +0 -0
  202. /package/client/dist/assets/{attachments-DPCmkFZu2.js → attachments-D2jwJXQ42.js} +0 -0
  203. /package/client/dist/assets/{attachments-C9GTTJsx2.js → attachments-DlEQu56g2.js} +0 -0
  204. /package/client/dist/assets/{attachments-Ci6_42Kh.js → attachments-DvxQbBTC.js} +0 -0
  205. /package/client/dist/assets/{attachments-DL-bPy9E.js → attachments-jVdKzAXn.js} +0 -0
  206. /package/client/dist/assets/{auth-Cvsb7-Ek.js → auth-DpZq5p1e.js} +0 -0
  207. /package/client/dist/assets/{browser-Cvg-QHvd2.js → browser-BQFDxrRq.js} +0 -0
  208. /package/client/dist/assets/{browser-rWKj-RR_2.js → browser-BbJ_q_OT2.js} +0 -0
  209. /package/client/dist/assets/{browser-Cc0iDqUi.js → browser-C4VJ5CW12.js} +0 -0
  210. /package/client/dist/assets/{browser-BPkhILSJ.js → browser-Cl-FwPXs.js} +0 -0
  211. /package/client/dist/assets/{browser-CdS6O1Qb.js → browser-DM8G151q.js} +0 -0
  212. /package/client/dist/assets/{browser-FRv8P02E.js → browser-Do2jf8_N.js} +0 -0
  213. /package/client/dist/assets/{browser-BqLqJVVT.js → browser-JsiohNmW.js} +0 -0
  214. /package/client/dist/assets/{browser-BijVYG0Y2.js → browser-kA31eYhu2.js} +0 -0
  215. /package/client/dist/assets/{chat-VMflhzxn.js → chat-0dg5hOo_.js} +0 -0
  216. /package/client/dist/assets/{chat-DdwZjT8K2.js → chat-BMWD_Rx12.js} +0 -0
  217. /package/client/dist/assets/{chat-Bom6bqK3.js → chat-BalviUAD.js} +0 -0
  218. /package/client/dist/assets/{chat-DsbwJhus.js → chat-CJddjN3D2.js} +0 -0
  219. /package/client/dist/assets/{chat-Dnwg-NpQ.js → chat-D5xEtsf3.js} +0 -0
  220. /package/client/dist/assets/{chat-vylxqR0P2.js → chat-Ds8GvCnv.js} +0 -0
  221. /package/client/dist/assets/{chat-BgL3zv6x2.js → chat-SaG-ng1x2.js} +0 -0
  222. /package/client/dist/assets/{chat-Bh3NdgJs.js → chat-n43nZGvn.js} +0 -0
  223. /package/client/dist/assets/{code-thSEptMx.js → code-B62fClk-.js} +0 -0
  224. /package/client/dist/assets/{code-DzC1rMk12.js → code-Bit_z2Xd2.js} +0 -0
  225. /package/client/dist/assets/{code-BRcDlze52.js → code-BotxM43N2.js} +0 -0
  226. /package/client/dist/assets/{code-UPVvmAKM2.js → code-C9U6M5-e2.js} +0 -0
  227. /package/client/dist/assets/{code-BdMy3Ffq.js → code-CIPmDEd0.js} +0 -0
  228. /package/client/dist/assets/{code-DgeHz9CB.js → code-DUe1Bouz.js} +0 -0
  229. /package/client/dist/assets/{code-CwnBPPyA.js → code-F_KjccGf.js} +0 -0
  230. /package/client/dist/assets/{code-CeQ0FZpC.js → code-HDK4P6Rx.js} +0 -0
  231. /package/client/dist/assets/{commands-DkU0qliq.js → commands-BFQZc9AG.js} +0 -0
  232. /package/client/dist/assets/{commands-u-4eEHCN.js → commands-BMkC5-ki.js} +0 -0
  233. /package/client/dist/assets/{commands-CSDs9q_2.js → commands-BllkJauO.js} +0 -0
  234. /package/client/dist/assets/{commands-xHDDGfcP2.js → commands-CWX9WmDX2.js} +0 -0
  235. /package/client/dist/assets/{commands-C4Y3C-PF2.js → commands-CzFSoIiq2.js} +0 -0
  236. /package/client/dist/assets/{commands-CckaBWdV.js → commands-DCJ-R9E4.js} +0 -0
  237. /package/client/dist/assets/{commands-JWMfGDmr.js → commands-KBbhsnw8.js} +0 -0
  238. /package/client/dist/assets/{commands-RHOchN4n2.js → commands-cKTaLwwV2.js} +0 -0
  239. /package/client/dist/assets/{common-DmqBPwZG.js → common-BfQD173j.js} +0 -0
  240. /package/client/dist/assets/{common-DhWTkocg.js → common-CF8iGXrG.js} +0 -0
  241. /package/client/dist/assets/{common-Bwafujnc2.js → common-Cfmwcrl52.js} +0 -0
  242. /package/client/dist/assets/{common-C-vU-j-I.js → common-D2cimsul.js} +0 -0
  243. /package/client/dist/assets/{common-C5HKSbbz.js → common-DH7fjyL1.js} +0 -0
  244. /package/client/dist/assets/{common-CYaT2yHp.js → common-DXrmj6WD.js} +0 -0
  245. /package/client/dist/assets/{common-BM3LFLCE2.js → common-DZbXlmq72.js} +0 -0
  246. /package/client/dist/assets/{common-DiPTT7V_2.js → common-DtXmzX2o2.js} +0 -0
  247. /package/client/dist/assets/{explore-UvW1jEMa.js → explore-BEpIs343.js} +0 -0
  248. /package/client/dist/assets/{explore-CNBzrEPS.js → explore-BO_OinBD.js} +0 -0
  249. /package/client/dist/assets/{explore-C0k8HrkN.js → explore-Bhy7WWXv.js} +0 -0
  250. /package/client/dist/assets/{explore-B7fblXBb2.js → explore-CAc8Qzu32.js} +0 -0
  251. /package/client/dist/assets/{explore-D_mWBYCS2.js → explore-DCZL9gRN2.js} +0 -0
  252. /package/client/dist/assets/{explore-Dahc1lOx.js → explore-DW_YTkQh.js} +0 -0
  253. /package/client/dist/assets/{explore-BHtEpoNN2.js → explore-ELbrSZyQ2.js} +0 -0
  254. /package/client/dist/assets/{explore-BKVV5JSu.js → explore-_XyU5e7R.js} +0 -0
  255. /package/client/dist/assets/{format-command-CuWuRvka.js → format-command-Db1viey1.js} +0 -0
  256. /package/client/dist/assets/{jira-uNH1gETL2.js → jira-B5wszJ1-2.js} +0 -0
  257. /package/client/dist/assets/{jira-BHAKtc4B.js → jira-C5UjxHE4.js} +0 -0
  258. /package/client/dist/assets/{jira-D5efgwp_.js → jira-CQ7mrZde.js} +0 -0
  259. /package/client/dist/assets/{jira-bDs7Im2C.js → jira-DIRkZk9U.js} +0 -0
  260. /package/client/dist/assets/{jira-CPpgA-5z2.js → jira-DTYu3hJi2.js} +0 -0
  261. /package/client/dist/assets/{jira-B3613-gj.js → jira-DeIjwCK3.js} +0 -0
  262. /package/client/dist/assets/{jira-DS7ownDy.js → jira-DtZpokjJ.js} +0 -0
  263. /package/client/dist/assets/{jira-Be369RIR2.js → jira-ZdKb-50F2.js} +0 -0
  264. /package/client/dist/assets/{jobs-CE8qeIYj.js → jobs-BFIdeDTw.js} +0 -0
  265. /package/client/dist/assets/{jobs-BgQzB0Sx2.js → jobs-BJ-Gcao22.js} +0 -0
  266. /package/client/dist/assets/{jobs-1GV2-4zd2.js → jobs-DAGiPVIS2.js} +0 -0
  267. /package/client/dist/assets/{jobs-26F9gGcE.js → jobs-DAU8C_tZ.js} +0 -0
  268. /package/client/dist/assets/{jobs-BqWui9_f.js → jobs-Da0GaaSc.js} +0 -0
  269. /package/client/dist/assets/{jobs-D5sZzgLs.js → jobs-DrYmsKy5.js} +0 -0
  270. /package/client/dist/assets/{jobs-Wh7dn28q2.js → jobs-P3unxtxo2.js} +0 -0
  271. /package/client/dist/assets/{jobs-DzeuASRf.js → jobs-p360zdf1.js} +0 -0
  272. /package/client/dist/assets/{loops-CvOWsi5r.js → loops-Bi4ywYGn.js} +0 -0
  273. /package/client/dist/assets/{loops-BvqQ-bHR2.js → loops-BjK8lmoZ2.js} +0 -0
  274. /package/client/dist/assets/{loops-Brnx4Fji.js → loops-CO6q75W8.js} +0 -0
  275. /package/client/dist/assets/{loops-K5_HXfJa.js → loops-D3wdUkZV.js} +0 -0
  276. /package/client/dist/assets/{loops-D6D3XHjj.js → loops-DDs4KPu_.js} +0 -0
  277. /package/client/dist/assets/{loops-LIgO_-eM.js → loops-DKzYFcq6.js} +0 -0
  278. /package/client/dist/assets/{loops-DJNhp17p2.js → loops-Du-bNVpc2.js} +0 -0
  279. /package/client/dist/assets/{loops-vhJdbcz92.js → loops-DvGbSfxw2.js} +0 -0
  280. /package/client/dist/assets/{mcp-XJNRfxVp2.js → mcp-BG-OCJca2.js} +0 -0
  281. /package/client/dist/assets/{mcp-DtbxuH3V2.js → mcp-CbJ04oFS2.js} +0 -0
  282. /package/client/dist/assets/{mcp-sRmWE8WJ.js → mcp-CrWdq5db.js} +0 -0
  283. /package/client/dist/assets/{mcp-H-gnQUdN.js → mcp-Dd844F5_.js} +0 -0
  284. /package/client/dist/assets/{mcp-Dcl-XxkR.js → mcp-DfmzoT5t.js} +0 -0
  285. /package/client/dist/assets/{mcp-CjVKXAhD2.js → mcp-DyULurYS2.js} +0 -0
  286. /package/client/dist/assets/{mcp-CIH0pH1K.js → mcp-HwBbQDk0.js} +0 -0
  287. /package/client/dist/assets/{mcp-N1nzp8oL.js → mcp-zSJ2TsJj.js} +0 -0
  288. /package/client/dist/assets/{setup-BuQNKztt2.js → setup-1vN48B-m2.js} +0 -0
  289. /package/client/dist/assets/{setup-BwMovM9z2.js → setup-BmJ0Kx16.js} +0 -0
  290. /package/client/dist/assets/{setup-RiUHzq7E.js → setup-D2CmgAro.js} +0 -0
  291. /package/client/dist/assets/{setup-BXpw07r-.js → setup-DQWsu2TR.js} +0 -0
  292. /package/client/dist/assets/{setup-D84y3wx-.js → setup-JeBwoXoM.js} +0 -0
  293. /package/client/dist/assets/{setup-DU0ULX8q2.js → setup-T_ApeQDV2.js} +0 -0
  294. /package/client/dist/assets/{setup-BiduSX9d.js → setup-mXnzww4f.js} +0 -0
  295. /package/client/dist/assets/{setup-7J8TjG4O.js → setup-q0wRHUp1.js} +0 -0
  296. /package/client/dist/assets/{specs-CWtKcVDC.js → specs-BGRKhM8t.js} +0 -0
  297. /package/client/dist/assets/{specs-CBDvEYKv.js → specs-C6J2zrLM.js} +0 -0
  298. /package/client/dist/assets/{specs-D2clrt2c2.js → specs-CT8exGOy2.js} +0 -0
  299. /package/client/dist/assets/{specs-C9Sd7BhH2.js → specs-Ce5y2iFc2.js} +0 -0
  300. /package/client/dist/assets/{specs-CIPr9reA2.js → specs-DZDoZsDq.js} +0 -0
  301. /package/client/dist/assets/{specs-PozUNhqb.js → specs-DbtIzV8a.js} +0 -0
  302. /package/client/dist/assets/{specs-DTplt2XM.js → specs-GkiL-snY.js} +0 -0
  303. /package/client/dist/assets/{specs-CmEZY57v.js → specs-PGIOzMtc.js} +0 -0
  304. /package/client/dist/assets/{terminal-xGbCZJHg.js → terminal-BNSg2lR7.js} +0 -0
  305. /package/client/dist/assets/{terminal-De2Brd23.js → terminal-BTpE7iki.js} +0 -0
  306. /package/client/dist/assets/{terminal-DFPNRLKp.js → terminal-CFt56A5f.js} +0 -0
  307. /package/client/dist/assets/{terminal-BRm03iFA2.js → terminal-Cvabyzm42.js} +0 -0
  308. /package/client/dist/assets/{terminal-DxQWCqSD2.js → terminal-D0z12_sJ2.js} +0 -0
  309. /package/client/dist/assets/{terminal-Z7KsDM_n.js → terminal-DDk_bTw0.js} +0 -0
  310. /package/client/dist/assets/{terminal-cqfd3EDV.js → terminal-DEyt0prF.js} +0 -0
  311. /package/client/dist/assets/{terminal-WSAvHXoU.js → terminal-a6Uq8B0p.js} +0 -0
  312. /package/client/dist/assets/{tickets-CkfbISuK.js → tickets-BHv3ewvq.js} +0 -0
  313. /package/client/dist/assets/{tickets-C3b8cHhd2.js → tickets-BIxU86j12.js} +0 -0
  314. /package/client/dist/assets/{tickets-SuX_HkK4.js → tickets-C2BhwzwR.js} +0 -0
  315. /package/client/dist/assets/{tickets-DTHo9c3C.js → tickets-D1IeX2EW.js} +0 -0
  316. /package/client/dist/assets/{tickets-BoXom7l8.js → tickets-DZut8Ryt.js} +0 -0
  317. /package/client/dist/assets/{tickets-CWYFvx1W2.js → tickets-LfK6LzKl2.js} +0 -0
  318. /package/client/dist/assets/{tickets-CFXnJsYF.js → tickets-dQfsc7Ph.js} +0 -0
  319. /package/client/dist/assets/{tickets-B7zT4IRf.js → tickets-sVun0I6v.js} +0 -0
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.registerJobsRoutes = registerJobsRoutes;
7
+ const path_1 = __importDefault(require("path"));
4
8
  const db_1 = require("./db");
5
9
  const queue_manager_1 = require("./queue-manager");
6
10
  const feature_flags_1 = require("./feature-flags");
@@ -12,7 +16,22 @@ const provider_selection_1 = require("./provider-selection");
12
16
  const loop_runs_store_1 = require("./loop-runs-store");
13
17
  const metrics_1 = require("./metrics");
14
18
  const ticket_store_1 = require("./ticket-store");
19
+ const transient_children_1 = require("./transient-children");
15
20
  const project_router_helpers_1 = require("./project-router-helpers");
21
+ function backgroundStartBusyReason(c) {
22
+ const activeJobId = typeof c.queueManager?.getActiveJobId === 'function' ? c.queueManager.getActiveJobId() : null;
23
+ if (activeJobId)
24
+ return `job ${activeJobId} is still running`;
25
+ try {
26
+ const runningLoops = (0, loop_runs_store_1.listRunningLoopRuns)(c.db, c.project.id);
27
+ if (runningLoops.length > 0)
28
+ return `loop run ${runningLoops[0].id} is still running`;
29
+ }
30
+ catch {
31
+ // If the loop table is unavailable in a test harness, do not block startup.
32
+ }
33
+ return null;
34
+ }
16
35
  function registerJobsRoutes(deps) {
17
36
  const { router, registry, ctx, ticketPath } = deps;
18
37
  // ─── Queue / Spawn routes ────────────────────────────────────────────────────
@@ -86,6 +105,113 @@ function registerJobsRoutes(deps) {
86
105
  },
87
106
  });
88
107
  });
108
+ router.get('/:projectId/background-processes', (req, res) => {
109
+ const c = ctx(req);
110
+ const chatId = typeof req.query.chatId === 'string' && req.query.chatId.trim() ? req.query.chatId.trim() : undefined;
111
+ res.json({
112
+ processes: (0, transient_children_1.listBackgroundProcesses)({
113
+ projectId: c.project.id,
114
+ ...(chatId ? { chatId } : {}),
115
+ }),
116
+ });
117
+ });
118
+ router.get('/:projectId/background-processes/:pid/logs', (req, res) => {
119
+ const c = ctx(req);
120
+ const pid = Number(req.params.pid);
121
+ const chatId = typeof req.query.chatId === 'string' ? req.query.chatId : '';
122
+ const limit = typeof req.query.limit === 'string' ? Number(req.query.limit) : undefined;
123
+ if (!Number.isFinite(pid)) {
124
+ res.status(400).json({ error: 'pid must be numeric' });
125
+ return;
126
+ }
127
+ if (!chatId.trim()) {
128
+ res.status(400).json({ error: 'chatId is required' });
129
+ return;
130
+ }
131
+ if (limit !== undefined && (!Number.isFinite(limit) || limit <= 0)) {
132
+ res.status(400).json({ error: 'limit must be a positive number' });
133
+ return;
134
+ }
135
+ const logs = (0, transient_children_1.getBackgroundProcessLogs)(pid, {
136
+ projectId: c.project.id,
137
+ chatId: chatId.trim(),
138
+ ...(limit !== undefined ? { limit } : {}),
139
+ });
140
+ if (!logs) {
141
+ res.status(404).json({ error: 'background process logs not found for project/chat' });
142
+ return;
143
+ }
144
+ res.json(logs);
145
+ });
146
+ router.post('/:projectId/background-processes', (req, res) => {
147
+ const { command, cwd, chatId, confirmed, allowWhileBusy } = req.body ?? {};
148
+ const c = ctx(req);
149
+ if (!command || typeof command !== 'string' || !command.trim()) {
150
+ res.status(400).json({ error: 'command is required' });
151
+ return;
152
+ }
153
+ if (!chatId || typeof chatId !== 'string' || !chatId.trim()) {
154
+ res.status(400).json({ error: 'chatId is required' });
155
+ return;
156
+ }
157
+ if (confirmed !== true) {
158
+ res.status(400).json({ error: 'confirmed must be true after explicit user confirmation' });
159
+ return;
160
+ }
161
+ const busyReason = backgroundStartBusyReason(c);
162
+ if (busyReason && allowWhileBusy !== true) {
163
+ res.status(409).json({
164
+ error: 'project is busy',
165
+ reason: busyReason,
166
+ hint: 'Wait for the running job/loop to finish, or pass allowWhileBusy:true only after explicit user confirmation.',
167
+ });
168
+ return;
169
+ }
170
+ const resolvedCwd = path_1.default.resolve(c.project.path, typeof cwd === 'string' && cwd.trim() ? cwd : '.');
171
+ const root = path_1.default.resolve(c.project.path);
172
+ if (resolvedCwd !== root && !resolvedCwd.startsWith(root + path_1.default.sep)) {
173
+ res.status(400).json({ error: 'cwd must stay within the project' });
174
+ return;
175
+ }
176
+ try {
177
+ const process = (0, transient_children_1.startBackgroundProcess)(command.trim(), resolvedCwd, chatId.trim(), c.project.id, {
178
+ onStarted: (process) => c.broadcast({
179
+ type: 'background_process.started',
180
+ process,
181
+ projectId: process.projectId,
182
+ timestamp: new Date().toISOString(),
183
+ }),
184
+ onExited: (process) => c.broadcast({
185
+ type: 'background_process.exited',
186
+ process,
187
+ projectId: process.projectId,
188
+ timestamp: new Date().toISOString(),
189
+ }),
190
+ });
191
+ res.status(202).json({ ok: true, process });
192
+ }
193
+ catch (err) {
194
+ res.status(500).json({ error: err instanceof Error ? err.message : 'background process failed to start' });
195
+ }
196
+ });
197
+ router.delete('/:projectId/background-processes/:pid', (req, res) => {
198
+ const c = ctx(req);
199
+ const pid = Number(req.params.pid);
200
+ const chatId = typeof req.query.chatId === 'string' ? req.query.chatId : '';
201
+ if (!Number.isFinite(pid)) {
202
+ res.status(400).json({ error: 'pid must be numeric' });
203
+ return;
204
+ }
205
+ if (!chatId.trim()) {
206
+ res.status(400).json({ error: 'chatId is required' });
207
+ return;
208
+ }
209
+ if (!(0, transient_children_1.killOwnedBackgroundProcess)(pid, { projectId: c.project.id, chatId: chatId.trim() })) {
210
+ res.status(404).json({ error: 'background process not found for project/chat' });
211
+ return;
212
+ }
213
+ res.json({ ok: true, pid, status: 'killing' });
214
+ });
89
215
  // Returns the resolved default model for Add Spec + the full provider
90
216
  // allow-list so the modal can render its picker without maintaining its
91
217
  // own copy of the model lists. Source of truth is `server/spec-models.ts`.
@@ -76,7 +76,7 @@ function registerSettingsRoutes(deps) {
76
76
  res.json({ scope: merged });
77
77
  });
78
78
  router.patch('/:projectId/settings', (req, res) => {
79
- const { pipelineTelemetryEnabled, orchestratorModel, prePrompt, freestylePrePrompt } = req.body ?? {};
79
+ const { pipelineTelemetryEnabled, orchestratorModel, prePrompt, freestylePrePrompt, worktreeEnvPassthrough } = req.body ?? {};
80
80
  const patch = {};
81
81
  if (pipelineTelemetryEnabled !== undefined) {
82
82
  patch.pipelineTelemetryEnabled = Boolean(pipelineTelemetryEnabled);
@@ -103,6 +103,15 @@ function registerSettingsRoutes(deps) {
103
103
  }
104
104
  patch.freestylePrePrompt = freestylePrePrompt;
105
105
  }
106
+ if (worktreeEnvPassthrough !== undefined) {
107
+ try {
108
+ patch.worktreeEnvPassthrough = (0, db_1.normalizeWorktreeEnvPassthrough)(worktreeEnvPassthrough);
109
+ }
110
+ catch (err) {
111
+ res.status(400).json({ error: err instanceof Error ? err.message : 'invalid worktreeEnvPassthrough' });
112
+ return;
113
+ }
114
+ }
106
115
  if (req.body?.integrationBranch !== undefined) {
107
116
  const ib = req.body.integrationBranch;
108
117
  if (typeof ib !== 'string') {
@@ -33,6 +33,7 @@ const binary_probe_1 = require("./binary-probe");
33
33
  const workspace_manager_1 = require("./workspace-manager");
34
34
  const claude_trust_1 = require("./claude-trust");
35
35
  const workspace_resolution_1 = require("./workspace-resolution");
36
+ const project_env_1 = require("./project-env");
36
37
  const framework_manager_1 = require("./framework-manager");
37
38
  const openspec_shim_1 = require("./openspec-shim");
38
39
  const artifact_registry_1 = require("./artifact-registry");
@@ -1650,6 +1651,13 @@ class QueueManager {
1650
1651
  // claude honours OTEL_* env vars natively; codex does not and instead
1651
1652
  // gets signals synthesised by the codex-otel-bridge attached below.
1652
1653
  let spawnEnv = process.env;
1654
+ // Per-project worktree/job env passthrough. The setting stores names only;
1655
+ // values are read from the server env (with login-shell recovery for missing
1656
+ // names) at spawn time. Apply this before Specrails' own env overlays so
1657
+ // internal SPECRAILS_* control-plane values always win.
1658
+ if (this._db) {
1659
+ spawnEnv = (0, project_env_1.applyWorktreeEnvPassthrough)(this._db, spawnEnv);
1660
+ }
1653
1661
  const telemetryEnabled = !!(this._projectId && this._db && (0, db_1.getProjectSettings)(this._db).pipelineTelemetryEnabled);
1654
1662
  // Resolve the framework version ONCE at spawn time — `framework/current`
1655
1663
  // is read from `~/.specrails/framework/current`. A concurrent atomic swap
@@ -1668,7 +1676,7 @@ class QueueManager {
1668
1676
  if (frameworkVersion)
1669
1677
  extra['specrails.framework_version'] = frameworkVersion;
1670
1678
  spawnEnv = {
1671
- ...process.env,
1679
+ ...spawnEnv,
1672
1680
  ...buildTelemetryEnv(jobId, this._projectId, this._desktopPort, extra, adapter.id),
1673
1681
  };
1674
1682
  }
@@ -497,8 +497,9 @@ async function launchIsolatedRail(input, io = {}) {
497
497
  // Build-settle: persist the per-unit branch outcomes + this launch's
498
498
  // worktree ledger ids on the row (the deferred create-pr / discard actions
499
499
  // reconstruct their inputs from these — nothing survives in memory), then
500
- // hand the decision to the user. 0 succeeded units → nothing deliverable
501
- // → 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".
502
503
  const worktreeIds = allocated.map((a) => a.ledgerId);
503
504
  const anySucceeded = results.some((r) => r.succeeded);
504
505
  const settledContinuation = anySucceeded ? launchContinuation : null;
@@ -522,7 +523,7 @@ async function launchIsolatedRail(input, io = {}) {
522
523
  ? 'pr_failed'
523
524
  : (settledContinuation.isDraft === false ? 'pr_ready' : 'pr_draft'))
524
525
  : 'on_review')
525
- : 'discarded';
526
+ : 'implementation_failed';
526
527
  const patch = settledContinuation
527
528
  ? {
528
529
  branches,
@@ -543,7 +544,7 @@ async function launchIsolatedRail(input, io = {}) {
543
544
  syncOriginCard('update');
544
545
  }
545
546
  if (!anySucceeded) {
546
- // AUTO-DISCARD CLEANUP (0 succeeded): unmount every worktree NOW rather
547
+ // FAILED-IMPLEMENTATION CLEANUP (0 succeeded): unmount every worktree NOW rather
547
548
  // than leaving it for a restart's reconcile sweep — a still-mounted
548
549
  // worktree POISONS the next run of the same ticket: the worktree path is
549
550
  // keyed by ticketId, so the next allocation silently reuses this
@@ -107,7 +107,8 @@ function actionAllowed(action, row) {
107
107
  return row.decision === 'pr_draft' && row.pr_url !== null;
108
108
  case 'discard':
109
109
  return row.decision === 'on_review' || row.decision === 'pr_draft' ||
110
- row.decision === 'pr_ready' || row.decision === 'pr_failed';
110
+ row.decision === 'pr_ready' || row.decision === 'implementation_failed' ||
111
+ row.decision === 'pr_failed';
111
112
  case 'poll-merge':
112
113
  return (row.decision === 'pr_draft' || row.decision === 'pr_ready') && row.pr_url !== null;
113
114
  case 'merge-local':
@@ -438,9 +439,15 @@ async function runPublish(deps, row) {
438
439
  }
439
440
  async function runDiscard(deps, row) {
440
441
  const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
442
+ const implementationFailed = row.decision === 'implementation_failed';
441
443
  // 1. Close the PR (drops its head branch) when one exists. Best-effort — a
442
444
  // gh failure must never leave the discard half-done.
443
- 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) {
444
451
  try {
445
452
  const r = await deps.exec.run('gh', ['pr', 'close', row.pr_url, '--delete-branch'], deps.project.path);
446
453
  if (r.code !== 0) {
@@ -454,12 +461,13 @@ async function runDiscard(deps, row) {
454
461
  // 2. Remove the launch's worktrees (still on disk unless a restart already
455
462
  // swept them) and close their ledger rows — the reconcile-style removal,
456
463
  // branch deletion deferred to step 3 (a checked-out branch can't be -D'd).
457
- const branchSet = new Set(snap.branches.map((b) => b.branch));
464
+ const branchSet = new Set(implementationFailed ? [] : snap.branches.map((b) => b.branch));
458
465
  for (const wtId of snap.worktreeIds) {
459
466
  const wt = (0, rail_worktrees_store_1.getRailWorktree)(deps.db, wtId);
460
467
  if (!wt)
461
468
  continue;
462
- branchSet.add(wt.branch);
469
+ if (!implementationFailed)
470
+ branchSet.add(wt.branch);
463
471
  await (0, worktree_manager_1.removeWorktree)(deps.git, {
464
472
  repoDir: deps.project.path, worktreePath: wt.worktree_path, branch: wt.branch, deleteBranch: false,
465
473
  }).catch(() => { });
@@ -471,22 +479,24 @@ async function runDiscard(deps, row) {
471
479
  // best-effort from the same ticket data the create-pr path names it from
472
480
  // (the delivered head is already covered by row.branch). Missing branches
473
481
  // are tolerated; the integration branch is NEVER deleted.
474
- if (row.branch)
475
- branchSet.add(row.branch);
476
- const succeededUnits = snap.branches.filter((b) => b.succeeded);
477
- if (succeededUnits.length > 1) {
478
- try {
479
- 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
+ }
480
493
  }
481
- catch {
482
- /* 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(() => { });
483
498
  }
484
499
  }
485
- for (const branch of branchSet) {
486
- if (!branch || branch === row.base_branch)
487
- continue;
488
- await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
489
- }
490
500
  const conflict = casTransition(deps, row, 'discarded');
491
501
  if (conflict)
492
502
  return conflict;
@@ -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.
@@ -53,6 +53,11 @@ function emitPrDeliveryUpdate(c, prDeliveryId) {
53
53
  (0, agent_chat_registry_1.getAgentChatManager)()?.updatePrDecisionCard(row.origin_conversation_id, (0, rail_pr_store_1.toPrDecisionCardEnvelope)(c.project.id, snap));
54
54
  }
55
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
+ }
56
61
  function createRailsRouter() {
57
62
  const router = (0, express_1.Router)({ mergeParams: true });
58
63
  function ctx(req) {
@@ -116,6 +121,7 @@ function createRailsRouter() {
116
121
  // (non-terminal) delivery per rail slot, so a refreshed client re-renders
117
122
  // the decision surface without waiting for a broadcast. The store lists
118
123
  // newest-first within each rail — keep the first per index.
124
+ reconcileAndEmitFailedPrDeliveries(c);
119
125
  const prDeliveries = {};
120
126
  for (const row of (0, rail_pr_store_1.listActivePrDeliveries)(c.db)) {
121
127
  if (!(row.rail_index in prDeliveries))
@@ -684,7 +690,7 @@ function createRailsRouter() {
684
690
  if (current.pr_state === 'pr-created' && current.pr_url && current.branch) {
685
691
  const succeeded = settled.every((s) => s.status === 'fulfilled' && s.value === true);
686
692
  if (!succeeded) {
687
- if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_failed', { prState: 'local-only' })) {
693
+ if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'implementation_failed')) {
688
694
  emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
689
695
  }
690
696
  return;
@@ -275,12 +275,12 @@ function deployTemplates(projectPath, selectedAgents) {
275
275
  // Full install: 7-phase enrichment flow (claude /specrails:enrich)
276
276
  exports.CHECKPOINTS = [
277
277
  { key: 'base_install', name: 'Base installation' },
278
- { key: 'repo_analysis', name: 'Repository analysis' },
279
- { key: 'stack_conventions', name: 'Stack & conventions' },
280
- { key: 'product_discovery', name: 'Product discovery' },
278
+ { key: 'agent_selection', name: 'Agent selection' },
279
+ { key: 'codebase_analysis', name: 'Codebase analysis' },
280
+ { key: 'vpc_discovery', name: 'VPC discovery' },
281
281
  { key: 'agent_generation', name: 'Agent generation' },
282
- { key: 'command_config', name: 'Command configuration' },
283
- { key: 'final_verification', name: 'Final verification' },
282
+ { key: 'persona_synthesis', name: 'Persona synthesis' },
283
+ { key: 'command_generation', name: 'Command generation' },
284
284
  ];
285
285
  // Quick install: 3-phase non-interactive flow (npx init --from-config)
286
286
  exports.QUICK_CHECKPOINTS = [
@@ -303,19 +303,20 @@ function checkFilesystem(projectPath) {
303
303
  const hasCommands = (((0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'commands', 'sr')) && hasFiles((0, path_1.join)(projectPath, dir, 'commands', 'sr'), /\.md$/)) ||
304
304
  ((0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'commands', 'specrails')) && hasFiles((0, path_1.join)(projectPath, dir, 'commands', 'specrails'), /\.md$/)));
305
305
  const hasCLAUDE = (0, fs_1.existsSync)((0, path_1.join)(projectPath, 'CLAUDE.md'));
306
+ const hasInstallConfig = (0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails', 'install-config.yaml')) ||
307
+ (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'install-config.yaml'));
308
+ const hasAgentConfig = (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'agents.yaml'));
309
+ const hasBacklogConfig = (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'backlog-config.json'));
306
310
  return {
307
311
  base_install: hasBaseInstall,
308
- // repo_analysis: detected when setup templates exist and CLAUDE.md is written
309
- // (Claude writes CLAUDE.md after analyzing the repo)
310
- repo_analysis: hasBaseInstall && (hasCLAUDE || hasSetupTemplates),
311
- // stack_conventions: detected when rules files are generated
312
- stack_conventions: hasRules,
313
- product_discovery: hasPersonas,
312
+ // Back-compat filesystem signals from older cores are translated to the
313
+ // current integration-contract checkpoint keys.
314
+ agent_selection: hasInstallConfig || hasAgentConfig || hasBacklogConfig || hasAgents || hasCommands,
315
+ codebase_analysis: hasBaseInstall && (hasCLAUDE || hasSetupTemplates),
316
+ vpc_discovery: hasPersonas,
317
+ persona_synthesis: hasPersonas,
314
318
  agent_generation: hasAgents,
315
- command_config: hasCommands,
316
- // Final verification: agents + commands must exist (manifest from install.sh is unreliable —
317
- // it's created during scaffolding before /setup generates the actual artifacts)
318
- final_verification: hasAgents && hasCommands,
319
+ command_generation: hasCommands || (hasAgents && hasRules),
319
320
  };
320
321
  }
321
322
  function hasFiles(dir, pattern) {
@@ -357,21 +358,23 @@ function readEnrichMdContent(projectPath) {
357
358
  // ─── Stream-based checkpoint detection ───────────────────────────────────────
358
359
  function detectCheckpointFromText(text) {
359
360
  const hits = [];
360
- // Match phase headers from Claude's /specrails:enrich output (and legacy /setup)
361
+ // Match phase headers from Claude's /specrails:enrich output (and legacy /setup).
362
+ // Older specrails-core prompts used different checkpoint names; keep accepting
363
+ // those phrases but emit only the current integration-contract keys.
361
364
  if (/phase\s*1|codebase\s*analysis|repository\s*analysis/i.test(text)) {
362
- hits.push({ key: 'repo_analysis', detail: 'Analyzing codebase...' });
365
+ hits.push({ key: 'codebase_analysis', detail: 'Analyzing codebase...' });
363
366
  }
364
- if (/phase\s*2|user\s*personas|product\s*discovery/i.test(text)) {
365
- hits.push({ key: 'product_discovery', detail: 'Generating personas...' });
367
+ if (/phase\s*2|user\s*personas|product\s*discovery|vpc\s*discovery/i.test(text)) {
368
+ hits.push({ key: 'vpc_discovery', detail: 'Discovering personas...' });
366
369
  }
367
370
  if (/phase\s*3|configuration|agent\s*selection|backlog\s*provider/i.test(text)) {
368
- hits.push({ key: 'stack_conventions', detail: 'Configuring stack...' });
371
+ hits.push({ key: 'agent_selection', detail: 'Configuring agents...' });
369
372
  }
370
373
  if (/generating\s*all\s*files|writing.*agent|sr-architect|sr-developer|sr-reviewer/i.test(text)) {
371
374
  hits.push({ key: 'agent_generation', detail: 'Generating agents...' });
372
375
  }
373
376
  if (/command\s*selection|installing.*commands|\.claude\/commands\/(sr|specrails)/i.test(text)) {
374
- hits.push({ key: 'command_config', detail: 'Configuring commands...' });
377
+ hits.push({ key: 'command_generation', detail: 'Configuring commands...' });
375
378
  }
376
379
  // TUI output patterns from specrails-core init --from-config.
377
380
  // Covers both the retired bash installer (✓ config loaded, reading
@@ -385,7 +388,7 @@ function detectCheckpointFromText(text) {
385
388
  hits.push({ key: 'agent_generation', detail: 'Installing specrails artefacts...' });
386
389
  }
387
390
  if (/writing\s*manifest|wrote\s+.*specrails-manifest/i.test(text)) {
388
- hits.push({ key: 'final_verification' });
391
+ hits.push({ key: 'command_generation' });
389
392
  }
390
393
  if (/✓\s*installed|installation\s*complete|init\s*complete|update\s*complete/i.test(text)) {
391
394
  hits.push({ key: 'quick_complete' });
@@ -394,7 +397,7 @@ function detectCheckpointFromText(text) {
394
397
  if (text.includes('.specrails-version') || text.includes('specrails/specrails-version'))
395
398
  hits.push({ key: 'base_install' });
396
399
  if (text.includes('/agents/personas/') && text.includes('.md')) {
397
- hits.push({ key: 'product_discovery', detail: 'Writing personas...' });
400
+ hits.push({ key: 'persona_synthesis', detail: 'Writing personas...' });
398
401
  }
399
402
  // Claude path: .claude/agents/sr-<name>.md
400
403
  if (/\/agents\/sr-[^/]+\.md/.test(text)) {
@@ -407,24 +410,24 @@ function detectCheckpointFromText(text) {
407
410
  hits.push({ key: 'agent_generation', detail: 'Writing agent skills...' });
408
411
  }
409
412
  if ((text.includes('/commands/sr/') || text.includes('/commands/specrails/')) && text.includes('.md')) {
410
- hits.push({ key: 'command_config', detail: 'Writing commands...' });
413
+ hits.push({ key: 'command_generation', detail: 'Writing commands...' });
411
414
  }
412
415
  // Codex enrich/doctor skills (the non-rail commands) also indicate
413
- // command_config progress.
416
+ // command_generation progress.
414
417
  if (/\.codex\/skills\/(enrich|doctor)\/SKILL\.md/.test(text)) {
415
- hits.push({ key: 'command_config', detail: 'Writing codex command skills...' });
418
+ hits.push({ key: 'command_generation', detail: 'Writing codex command skills...' });
416
419
  }
417
420
  if (text.includes('/rules/') && text.includes('.md')) {
418
- hits.push({ key: 'stack_conventions', detail: 'Writing conventions...' });
421
+ hits.push({ key: 'command_generation', detail: 'Writing conventions...' });
419
422
  }
420
423
  // Codex sandbox / approval policy lives inside .codex/config.toml
421
424
  // (top-level `sandbox_mode` + `approval_policy` keys, per codex
422
425
  // 0.128.0+). There is no separate Starlark rules file.
423
426
  if (/\.codex\/config\.toml/.test(text)) {
424
- hits.push({ key: 'stack_conventions', detail: 'Writing codex sandbox config...' });
427
+ hits.push({ key: 'agent_selection', detail: 'Writing codex sandbox config...' });
425
428
  }
426
429
  if (text.includes('.specrails-manifest.json') || text.includes('specrails/specrails-manifest.json')) {
427
- hits.push({ key: 'final_verification' });
430
+ hits.push({ key: 'command_generation' });
428
431
  }
429
432
  return hits;
430
433
  }
@@ -122,7 +122,7 @@ function brokenSymlinkHint(label, command, resolvedPath) {
122
122
  const where = resolvedPath ? ` at ${resolvedPath}` : '';
123
123
  return `${label} found${where} but failed to execute — possibly a broken symlink or a stale install. Reinstall ${command} or remove the stale link${resolvedPath ? ` at ${resolvedPath}` : ''}.`;
124
124
  }
125
- const BUNDLED_TOOL_KEYS = new Set(['node', 'npm', 'npx', 'git']);
125
+ const BUNDLED_TOOL_KEYS = new Set(['node', 'npm', 'npx', 'git', 'uv']);
126
126
  function isBundledTool(key) {
127
127
  return BUNDLED_TOOL_KEYS.has(key);
128
128
  }
@@ -138,6 +138,7 @@ function getBundledToolCandidates(runtimesBase, tool) {
138
138
  npx: [path_1.default.join(runtimesBase, 'node', 'npx.cmd')],
139
139
  git: [path_1.default.join(runtimesBase, 'git', 'cmd', 'git.exe'), path_1.default.join(runtimesBase, 'git', 'bin', 'git.exe')],
140
140
  gh: [path_1.default.join(runtimesBase, 'gh', 'bin', 'gh.exe')],
141
+ uv: [path_1.default.join(runtimesBase, 'uv', 'uv.exe'), path_1.default.join(runtimesBase, 'uv', 'bin', 'uv.exe')],
141
142
  };
142
143
  return map[tool];
143
144
  }
@@ -147,6 +148,7 @@ function getBundledToolCandidates(runtimesBase, tool) {
147
148
  npx: [path_1.default.join(runtimesBase, 'node', 'bin', 'npx')],
148
149
  git: [path_1.default.join(runtimesBase, 'git', 'bin', 'git')],
149
150
  gh: [path_1.default.join(runtimesBase, 'gh', 'bin', 'gh')],
151
+ uv: [path_1.default.join(runtimesBase, 'uv', 'bin', 'uv'), path_1.default.join(runtimesBase, 'uv', 'uv')],
150
152
  };
151
153
  return map[tool];
152
154
  }