specrails-desktop 2.36.0 → 2.42.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (510) hide show
  1. package/README.md +94 -247
  2. package/client/dist/assets/ActivityFeedPage-DoEMumQS.js +1 -0
  3. package/client/dist/assets/AgentBrowserCapture-DIh4K0gh.js +1 -0
  4. package/client/dist/assets/AgentModeAnalyticsPane-mbq4WEoO.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-CKh7zuA4.js +2 -0
  6. package/client/dist/assets/AgentModeJobsPane-BDokPiLJ.js +1 -0
  7. package/client/dist/assets/AgentsPage-BFVAZbBj.js +87 -0
  8. package/client/dist/assets/AnalyticsPage-3Y9LuWwz.js +1 -0
  9. package/client/dist/assets/CodePage-C9KyuV_C.js +3 -0
  10. package/client/dist/assets/DesktopAnalyticsPage-Bhi54bXQ.js +1 -0
  11. package/client/dist/assets/{DocsDialog-C76VdBJC.js → DocsDialog-Dj6mt4ga.js} +2 -2
  12. package/client/dist/assets/{DocsPage-ziMlj6B6.js → DocsPage-24yWglNI.js} +1 -1
  13. package/client/dist/assets/ExportDropdown-ophapHrT.js +1 -0
  14. package/client/dist/assets/InteractiveJobComposer-C1ucmxQh.js +19 -0
  15. package/client/dist/assets/JobDetailModal-DNWmgVHD.js +1 -0
  16. package/client/dist/assets/JobDetailPage-BcajZyuu.js +1 -0
  17. package/client/dist/assets/JobsPage-B9nTZw6e.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-Oc48iOFW.js +7 -0
  19. package/client/dist/assets/LoopPreviewModal-ae0Fr6SU.js +1 -0
  20. package/client/dist/assets/LoopsPage-CUEqzUBI.js +1 -0
  21. package/client/dist/assets/MinimizedChatsContext-Deg39peL.js +1 -0
  22. package/client/dist/assets/PluginsPage-C1xtARIx.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-JZzrXFBN.js +1 -0
  24. package/client/dist/assets/RepositoryDeliveries-CSDsiVUz.js +5 -0
  25. package/client/dist/assets/RepositoryScopeSelector-D-RCaknl.js +1 -0
  26. package/client/dist/assets/ReviewPacketPage-x5EfGTiL.js +1 -0
  27. package/client/dist/assets/TemplatePreviewModal-BSyuZxGq.js +1 -0
  28. package/client/dist/assets/TicketDetailModalContext-DqyF0so-.js +14 -0
  29. package/client/dist/assets/Trans-SnMX4MhW.js +1 -0
  30. package/client/dist/assets/agent-BdT0SB1D.js +1 -0
  31. package/client/dist/assets/agent-Bh9K7iEt.js +1 -0
  32. package/client/dist/assets/agent-C4ibdFk9.js +1 -0
  33. package/client/dist/assets/agent-CSFY22Zo.js +1 -0
  34. package/client/dist/assets/agent-DAnqxUn8.js +1 -0
  35. package/client/dist/assets/agent-DTQiu0Mi.js +1 -0
  36. package/client/dist/assets/agent-T54WuR_Y.js +1 -0
  37. package/client/dist/assets/agent-i75WDZOA.js +1 -0
  38. package/client/dist/assets/browser-05vRDg_y.js +1 -0
  39. package/client/dist/assets/browser-Bulb-cB1.js +1 -0
  40. package/client/dist/assets/browser-C7f-Jsg1.js +1 -0
  41. package/client/dist/assets/browser-CG0PE1j_.js +1 -0
  42. package/client/dist/assets/browser-CMzaqAjo.js +1 -0
  43. package/client/dist/assets/browser-DMajYdNX.js +1 -0
  44. package/client/dist/assets/browser-DxcOHrdP.js +1 -0
  45. package/client/dist/assets/browser-ObE7xVo3.js +1 -0
  46. package/client/dist/assets/builder-765NeYTp.js +1 -0
  47. package/client/dist/assets/builder-8MG3V6ea.js +1 -0
  48. package/client/dist/assets/builder-B-1UBs5K.js +1 -0
  49. package/client/dist/assets/builder-BTiGnPT0.js +1 -0
  50. package/client/dist/assets/builder-Cx9Rz-7u.js +1 -0
  51. package/client/dist/assets/builder-DKFMELBl.js +1 -0
  52. package/client/dist/assets/builder-OcmKrC0h.js +1 -0
  53. package/client/dist/assets/builder-P9qVBXFZ.js +1 -0
  54. package/client/dist/assets/{button-CJqFnUOL.js → button-DCBqEaZl.js} +1 -1
  55. package/client/dist/assets/{code-CkUuW4m9.js → code-B37GRg9C.js} +1 -1
  56. package/client/dist/assets/{code-B0O8-p8k.js → code-BBOEZbf9.js} +1 -1
  57. package/client/dist/assets/{code-B1VLZDhS.js → code-BRm-jcyx.js} +1 -1
  58. package/client/dist/assets/{code-Bpj4Ddml.js → code-CF7w62CR.js} +1 -1
  59. package/client/dist/assets/{code-BykBZ5-e.js → code-CLuvJt2r.js} +1 -1
  60. package/client/dist/assets/{code-B86IMAfz.js → code-D0RCm59f.js} +1 -1
  61. package/client/dist/assets/code-DaZ-ZwCT.js +1 -0
  62. package/client/dist/assets/{code-cAqaqMTx.js → code-iIHkGaCL.js} +1 -1
  63. package/client/dist/assets/commands-0XjAftla.js +1 -0
  64. package/client/dist/assets/commands-BAWSYHjQ.js +1 -0
  65. package/client/dist/assets/commands-BBwowgiE.js +1 -0
  66. package/client/dist/assets/commands-BacjIZLN.js +1 -0
  67. package/client/dist/assets/commands-Bs62IAZ3.js +1 -0
  68. package/client/dist/assets/commands-CLPdh-Hh.js +1 -0
  69. package/client/dist/assets/commands-DlbFzgrp.js +1 -0
  70. package/client/dist/assets/commands-LjPSsi5F.js +1 -0
  71. package/client/dist/assets/common-B5AZNYMH.js +1 -0
  72. package/client/dist/assets/common-BFZ0c_vN.js +1 -0
  73. package/client/dist/assets/common-BrlwEnL7.js +1 -0
  74. package/client/dist/assets/common-BttQx3z4.js +1 -0
  75. package/client/dist/assets/common-CM65k_-e.js +1 -0
  76. package/client/dist/assets/common-D4mvFFlu.js +1 -0
  77. package/client/dist/assets/common-TZ9Ny96S.js +1 -0
  78. package/client/dist/assets/common-nFpSF4GC.js +1 -0
  79. package/client/dist/assets/dashboard-B14uYXkG.js +1 -0
  80. package/client/dist/assets/dashboard-BK_mez-z.js +1 -0
  81. package/client/dist/assets/dashboard-BfBir_6o.js +1 -0
  82. package/client/dist/assets/dashboard-Bt0cCzeG.js +1 -0
  83. package/client/dist/assets/dashboard-BwkjxI6H.js +1 -0
  84. package/client/dist/assets/{dashboard-C8WOEiPu.js → dashboard-C3bDKf7s.js} +1 -1
  85. package/client/dist/assets/dashboard-CdvjGfog.js +1 -0
  86. package/client/dist/assets/dashboard-fRrP0Kv0.js +1 -0
  87. package/client/dist/assets/dialog-SRw01_QU.js +45 -0
  88. package/client/dist/assets/dist-js-CJ_2XX1Y.js +1 -0
  89. package/client/dist/assets/formatDistanceToNow-DSj5DprF.js +1 -0
  90. package/client/dist/assets/getTimezoneOffsetInMilliseconds-YhD8rIEs.js +1 -0
  91. package/client/dist/assets/index-DwAmL7m5.css +2 -0
  92. package/client/dist/assets/index-EzrkUb8B.js +76 -0
  93. package/client/dist/assets/{jira-api-B1-MUehg.js → jira-api-CSCqGr-Q.js} +1 -1
  94. package/client/dist/assets/{jobs-CN_zv4Gr.js → jobs-B8rrzfyZ.js} +1 -1
  95. package/client/dist/assets/{jobs-DUlWSYq2.js → jobs-BS6RTurf.js} +1 -1
  96. package/client/dist/assets/{jobs-Bpks6doU.js → jobs-BTT0Bsks.js} +1 -1
  97. package/client/dist/assets/{jobs-Bgq-PZkC.js → jobs-CezU69AE.js} +1 -1
  98. package/client/dist/assets/{jobs-SwYS2M-B.js → jobs-ClFJae0s.js} +1 -1
  99. package/client/dist/assets/{jobs-BAls3bWq.js → jobs-DJSFIFVy.js} +1 -1
  100. package/client/dist/assets/{jobs-DhxKgWFq.js → jobs-DhgGr8vm.js} +1 -1
  101. package/client/dist/assets/{jobs-B_TeSQxb.js → jobs-DzMjUcaN.js} +1 -1
  102. package/client/dist/assets/mcp-6rMoZfEO.js +1 -0
  103. package/client/dist/assets/mcp-BD8sL5mP.js +1 -0
  104. package/client/dist/assets/mcp-BkcA3I5s.js +1 -0
  105. package/client/dist/assets/mcp-CCpSelOh.js +1 -0
  106. package/client/dist/assets/mcp-CUAN6ORK.js +1 -0
  107. package/client/dist/assets/mcp-CqbRxRTE.js +1 -0
  108. package/client/dist/assets/mcp-DP2YfVhO.js +1 -0
  109. package/client/dist/assets/mcp-DUVQamDz.js +1 -0
  110. package/client/dist/assets/narration-B-ynWTpn.js +1 -0
  111. package/client/dist/assets/narration-Bj_laZwg.js +1 -0
  112. package/client/dist/assets/narration-BxHpmMv_.js +1 -0
  113. package/client/dist/assets/narration-C1Px0-0j.js +1 -0
  114. package/client/dist/assets/narration-CAXhGbzM.js +1 -0
  115. package/client/dist/assets/narration-DQ1l_Kq6.js +1 -0
  116. package/client/dist/assets/narration-XISXwqZd.js +1 -0
  117. package/client/dist/assets/narration-kwzfglQo.js +1 -0
  118. package/client/dist/assets/nav-1-Hh2kOz.js +1 -0
  119. package/client/dist/assets/nav-B34QkESm.js +1 -0
  120. package/client/dist/assets/nav-Bap_UXAq.js +1 -0
  121. package/client/dist/assets/{nav-pYuXq7sf.js → nav-BwxXIB24.js} +1 -1
  122. package/client/dist/assets/nav-Ch-zHBG5.js +1 -0
  123. package/client/dist/assets/nav-CygC_WN-.js +1 -0
  124. package/client/dist/assets/{nav-iwjCg5FR.js → nav-KnnLQtOQ.js} +1 -1
  125. package/client/dist/assets/nav-m00uxYUG.js +1 -0
  126. package/client/dist/assets/packet-BQ2tXfJI.js +1 -0
  127. package/client/dist/assets/packet-BdpOJ1Cy.js +1 -0
  128. package/client/dist/assets/packet-C3Ievb9R.js +1 -0
  129. package/client/dist/assets/packet-C4zJIG0Y.js +1 -0
  130. package/client/dist/assets/packet-Cx-zRuWn.js +1 -0
  131. package/client/dist/assets/packet-DUTGX52M.js +1 -0
  132. package/client/dist/assets/packet-DhsKtUPB.js +1 -0
  133. package/client/dist/assets/packet-z3LJ8Zyy.js +1 -0
  134. package/client/dist/assets/project-repositories-DYhTvwQ1.js +1 -0
  135. package/client/dist/assets/provider-capabilities-B-wOHfLP.js +1 -0
  136. package/client/dist/assets/settings-BBt2xtDQ.js +1 -0
  137. package/client/dist/assets/settings-BJB9RKJj.js +1 -0
  138. package/client/dist/assets/settings-CHwQR2rB.js +1 -0
  139. package/client/dist/assets/settings-CrMi3kSi.js +1 -0
  140. package/client/dist/assets/settings-DKyhDGD1.js +1 -0
  141. package/client/dist/assets/settings-Deupji9c.js +1 -0
  142. package/client/dist/assets/settings-DqRMmwAM.js +1 -0
  143. package/client/dist/assets/settings-J4rj6vo-.js +1 -0
  144. package/client/dist/assets/setup-2xGgu4Sk.js +1 -0
  145. package/client/dist/assets/setup-Ba11l2JA.js +1 -0
  146. package/client/dist/assets/setup-BzW4kMok.js +1 -0
  147. package/client/dist/assets/setup-COzwYhkJ.js +1 -0
  148. package/client/dist/assets/setup-CRVULhtF.js +1 -0
  149. package/client/dist/assets/setup-DgvlxwpE.js +1 -0
  150. package/client/dist/assets/setup-Dw_wYeEY.js +1 -0
  151. package/client/dist/assets/setup-ZPtEeJPW.js +1 -0
  152. package/client/dist/assets/spending-BqbdPm_4.js +1 -0
  153. package/client/dist/assets/square-Ci-PFcFe.js +1 -0
  154. package/client/dist/assets/{terminal-BFRZnMPP.js → terminal-BAzpHtpY.js} +1 -1
  155. package/client/dist/assets/{terminal-IahE4LW9.js → terminal-C7dHeR3x.js} +1 -1
  156. package/client/dist/assets/{terminal-tITa81iz.js → terminal-CMla5Qsz.js} +1 -1
  157. package/client/dist/assets/{terminal-BZiIu3rf.js → terminal-CdNXH-n9.js} +1 -1
  158. package/client/dist/assets/{terminal-ScjyXNxQ.js → terminal-Cw5bZsmV.js} +1 -1
  159. package/client/dist/assets/{terminal-C2NAd7BW.js → terminal-DFUP4eNv.js} +1 -1
  160. package/client/dist/assets/{terminal-d7GadXbN.js → terminal-DGqljeK_.js} +1 -1
  161. package/client/dist/assets/{terminal-QpXZU2Bn.js → terminal-cU70NodO.js} +1 -1
  162. package/client/dist/assets/useDesktop-RSMTyy7V.js +1 -0
  163. package/client/dist/assets/useSharedWebSocket-BOdS5_Iz.js +2 -0
  164. package/client/dist/index.html +34 -35
  165. package/docs/README.md +3 -0
  166. package/docs/agent-live-steering.md +57 -0
  167. package/docs/ci-cd.md +57 -0
  168. package/docs/code-explorer.md +48 -0
  169. package/docs/codex.md +12 -1
  170. package/docs/features/detachable-mission-windows.md +59 -0
  171. package/docs/guide/de/integrations/5-mcp-server.md +13 -9
  172. package/docs/guide/de/integrations/6-agent-chat.md +1 -0
  173. package/docs/guide/en/integrations/5-mcp-server.md +13 -9
  174. package/docs/guide/en/integrations/6-agent-chat.md +1 -0
  175. package/docs/guide/es/integrations/5-mcp-server.md +13 -9
  176. package/docs/guide/es/integrations/6-agent-chat.md +1 -0
  177. package/docs/guide/fr/integrations/5-mcp-server.md +13 -9
  178. package/docs/guide/fr/integrations/6-agent-chat.md +1 -0
  179. package/docs/guide/it/integrations/5-mcp-server.md +13 -9
  180. package/docs/guide/it/integrations/6-agent-chat.md +1 -0
  181. package/docs/guide/ja/integrations/5-mcp-server.md +13 -9
  182. package/docs/guide/ja/integrations/6-agent-chat.md +1 -0
  183. package/docs/guide/pt/integrations/5-mcp-server.md +13 -9
  184. package/docs/guide/pt/integrations/6-agent-chat.md +1 -0
  185. package/docs/guide/zh/integrations/5-mcp-server.md +13 -9
  186. package/docs/guide/zh/integrations/6-agent-chat.md +1 -0
  187. package/docs/internals/README.md +1 -0
  188. package/docs/internals/browser-capture-performance.md +55 -3
  189. package/docs/internals/browser-login-popups.md +45 -0
  190. package/docs/internals/browser-native-retina-audit.md +39 -0
  191. package/docs/internals/ci-cd-audit.md +39 -0
  192. package/docs/internals/core-runtime-updates.md +68 -0
  193. package/docs/internals/embedded-browser-native-webview-evaluation.md +2 -0
  194. package/docs/internals/implementation-reliability-audit.md +93 -0
  195. package/docs/internals/interactive-jobs.md +110 -6
  196. package/docs/internals/mcp-mission-audit.md +76 -0
  197. package/docs/internals/project-builder.md +404 -42
  198. package/docs/internals/safe-pr-review-flow.md +52 -10
  199. package/docs/internals/startup-recovery-audit.md +58 -0
  200. package/docs/mcp.md +74 -41
  201. package/docs/mission-processes.md +59 -0
  202. package/docs/multi-repo-projects.md +51 -0
  203. package/docs/platforms/windows-parity.md +70 -0
  204. package/docs/platforms/windows.md +13 -11
  205. package/docs/running-pipelines.md +14 -0
  206. package/mcp-bridge/dist/specrails-mcp.js +7186 -0
  207. package/package.json +9 -4
  208. package/server/dist/agent-chat-manager.js +537 -68
  209. package/server/dist/agent-chat-router.js +87 -6
  210. package/server/dist/agent-context-resolver.js +129 -25
  211. package/server/dist/agent-defaults.js +19 -11
  212. package/server/dist/agent-input-store.js +206 -0
  213. package/server/dist/agent-mcp-config.js +54 -15
  214. package/server/dist/agent-operator-prompt.js +255 -65
  215. package/server/dist/agent-spec-framing.js +202 -0
  216. package/server/dist/agent-steering.js +218 -0
  217. package/server/dist/agent-store.js +153 -0
  218. package/server/dist/api-not-found.js +8 -0
  219. package/server/dist/background-process-control.js +164 -0
  220. package/server/dist/background-process-service.js +61 -0
  221. package/server/dist/background-process-store.js +242 -0
  222. package/server/dist/background-windows-bootstrap.js +238 -0
  223. package/server/dist/blueprint-chat-manager.js +476 -62
  224. package/server/dist/blueprint-commit.js +21 -1
  225. package/server/dist/blueprint-draft-parser.js +122 -17
  226. package/server/dist/blueprint-generation.js +238 -0
  227. package/server/dist/blueprint-operator-prompt.js +91 -63
  228. package/server/dist/blueprint-router.js +55 -3
  229. package/server/dist/blueprint-spec-fixtures.js +85 -0
  230. package/server/dist/blueprint-spec-quality.js +57 -16
  231. package/server/dist/blueprint-store.js +92 -1
  232. package/server/dist/browser-capture-manager.js +86 -30
  233. package/server/dist/browser-playwright.js +168 -45
  234. package/server/dist/browser-viewport.js +26 -0
  235. package/server/dist/chat-manager.js +23 -15
  236. package/server/dist/chromium-archive.cjs +83 -0
  237. package/server/dist/chromium-resolver.js +72 -64
  238. package/server/dist/code-activity.js +145 -0
  239. package/server/dist/code-explorer-router.js +714 -114
  240. package/server/dist/core-compat.js +28 -1
  241. package/server/dist/core-execution.js +111 -0
  242. package/server/dist/core-package.js +4 -16
  243. package/server/dist/core-runtime.js +202 -0
  244. package/server/dist/core-update-manager.js +161 -60
  245. package/server/dist/core-update-state.js +29 -0
  246. package/server/dist/db.js +66 -35
  247. package/server/dist/delivery-evidence.js +142 -39
  248. package/server/dist/desktop-db.js +239 -43
  249. package/server/dist/desktop-router.js +109 -9
  250. package/server/dist/dev-ports.js +8 -3
  251. package/server/dist/file-provenance.js +34 -22
  252. package/server/dist/file-story-manager.js +62 -64
  253. package/server/dist/file-story.js +118 -35
  254. package/server/dist/file-summary-generator.js +46 -5
  255. package/server/dist/file-summary-manager.js +355 -106
  256. package/server/dist/framework-manager.js +98 -110
  257. package/server/dist/framework-reseed.js +76 -10
  258. package/server/dist/git-diagnostics.js +2 -2
  259. package/server/dist/headroom-manager.js +38 -3
  260. package/server/dist/host-control.js +52 -0
  261. package/server/dist/index.js +43 -2
  262. package/server/dist/interactive-job-session.js +149 -16
  263. package/server/dist/internal-api.js +48 -0
  264. package/server/dist/jira/jira-materializer.js +1 -0
  265. package/server/dist/json-tolerant.js +174 -0
  266. package/server/dist/legacy-migration.js +4 -2
  267. package/server/dist/loop-command-catalog.js +83 -21
  268. package/server/dist/loop-constants.js +6 -1
  269. package/server/dist/loop-decider.js +11 -1
  270. package/server/dist/loop-executors.js +194 -45
  271. package/server/dist/loop-factory.js +11 -4
  272. package/server/dist/loop-graph.js +90 -4
  273. package/server/dist/loop-run-manager.js +301 -69
  274. package/server/dist/loop-shell-invocation.js +21 -0
  275. package/server/dist/loop-step-idle.js +55 -0
  276. package/server/dist/loop-templates.js +31 -7
  277. package/server/dist/mcp/agent-capability.js +37 -4
  278. package/server/dist/mcp/guide.js +149 -36
  279. package/server/dist/mcp/mcp-admin-router.js +20 -4
  280. package/server/dist/mcp/mcp-server.js +89 -43
  281. package/server/dist/mcp/mcp-tiers.js +6 -5
  282. package/server/dist/mcp/mcp-token.js +3 -1
  283. package/server/dist/mcp/resources.js +5 -4
  284. package/server/dist/mcp/tools/catalog.js +4 -0
  285. package/server/dist/mcp/tools/code.js +125 -14
  286. package/server/dist/mcp/tools/context.js +153 -0
  287. package/server/dist/mcp/tools/git.js +15 -5
  288. package/server/dist/mcp/tools/jobs.js +84 -71
  289. package/server/dist/mcp/tools/loops.js +17 -6
  290. package/server/dist/mcp/tools/meta.js +74 -27
  291. package/server/dist/mcp/tools/mission.js +29 -0
  292. package/server/dist/mcp/tools/projects.js +22 -6
  293. package/server/dist/mcp/tools/rails.js +48 -11
  294. package/server/dist/mcp/tools/specs.js +86 -14
  295. package/server/dist/mcp/tools/support.js +1 -1
  296. package/server/dist/mcp/tools/types.js +117 -43
  297. package/server/dist/mcp/tools/watch.js +144 -51
  298. package/server/dist/milestone-chain-store.js +164 -0
  299. package/server/dist/milestone-chain.js +487 -0
  300. package/server/dist/milestone-progress.js +348 -0
  301. package/server/dist/mobile/mobile-devices.js +10 -7
  302. package/server/dist/mobile/mobile-gateway.js +2 -0
  303. package/server/dist/mobile/mobile-missions.js +214 -0
  304. package/server/dist/mobile/mobile-redact.js +17 -3
  305. package/server/dist/mobile/mobile-router.js +46 -4
  306. package/server/dist/mobile/mobile-ws.js +49 -4
  307. package/server/dist/multi-repo-bases.js +56 -0
  308. package/server/dist/multi-repo-checkout.js +101 -0
  309. package/server/dist/multi-repo-delivery.js +100 -0
  310. package/server/dist/multi-repo-execution-store.js +65 -0
  311. package/server/dist/multi-repo-execution.js +453 -0
  312. package/server/dist/offline-assemble.js +10 -8
  313. package/server/dist/openspec-runtime-plugin-commands.json +18 -0
  314. package/server/dist/openspec-runtime-plugin.js +120 -0
  315. package/server/dist/plugins/codex-spawn.js +49 -0
  316. package/server/dist/plugins/serena/manifest.js +2 -2
  317. package/server/dist/plugins/serena/templates/instructions.md +16 -0
  318. package/server/dist/pr-publisher.js +1 -1
  319. package/server/dist/profiles-router.js +3 -18
  320. package/server/dist/project-code-discovery.js +142 -0
  321. package/server/dist/project-git.js +19 -21
  322. package/server/dist/project-registry.js +272 -20
  323. package/server/dist/project-repositories.js +157 -0
  324. package/server/dist/project-repository-provenance.js +31 -0
  325. package/server/dist/project-router-background-processes.js +105 -0
  326. package/server/dist/project-router-git.js +75 -16
  327. package/server/dist/project-router-jobs.js +2 -126
  328. package/server/dist/project-router-loop-runs.js +72 -2
  329. package/server/dist/project-router-tickets.js +53 -8
  330. package/server/dist/project-router.js +239 -38
  331. package/server/dist/provider-detection.js +18 -6
  332. package/server/dist/provider-limit.js +64 -0
  333. package/server/dist/providers/claude-adapter.js +26 -1
  334. package/server/dist/providers/claude-background-tasks.js +18 -0
  335. package/server/dist/providers/claude-live-session.js +383 -0
  336. package/server/dist/providers/codex-adapter.js +19 -0
  337. package/server/dist/providers/codex-live-session.js +545 -0
  338. package/server/dist/providers/gemini-adapter.js +1 -0
  339. package/server/dist/providers/live-session-types.js +12 -0
  340. package/server/dist/providers/live-session.js +13 -0
  341. package/server/dist/providers/terminal-result.js +24 -0
  342. package/server/dist/queue-manager.js +2 -0
  343. package/server/dist/rail-isolated-launch.js +141 -16
  344. package/server/dist/rail-pr-decision.js +364 -99
  345. package/server/dist/rail-pr-store.js +24 -10
  346. package/server/dist/rail-worktree-release.js +34 -2
  347. package/server/dist/rail-worktrees-store.js +4 -4
  348. package/server/dist/rails-router.js +249 -55
  349. package/server/dist/repo-lock.js +92 -17
  350. package/server/dist/review-packet.js +23 -13
  351. package/server/dist/schemas/file-summary.v1.json +59 -59
  352. package/server/dist/schemas/profile.v1.json +137 -137
  353. package/server/dist/setup-manager.js +52 -8
  354. package/server/dist/shell-integration/bash-shim.bash +45 -0
  355. package/server/dist/shell-integration/fish-shim.fish +36 -0
  356. package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
  357. package/server/dist/shell-integration/zsh-shim.zsh +47 -0
  358. package/server/dist/smash-runner.js +1 -0
  359. package/server/dist/spawn-lifecycle.js +44 -27
  360. package/server/dist/spec-contract-prompt.js +143 -0
  361. package/server/dist/spec-draft-parser.js +6 -1
  362. package/server/dist/spec-models.js +1 -0
  363. package/server/dist/stuck-run-detector.js +1 -0
  364. package/server/dist/terminal-manager.js +21 -1
  365. package/server/dist/terminal-shell-integration.js +4 -1
  366. package/server/dist/transient-children.js +619 -230
  367. package/server/dist/util/cli-prompt.js +78 -5
  368. package/server/dist/util/sqlite-migrations.js +20 -0
  369. package/server/dist/util/win-spawn.js +18 -3
  370. package/server/dist/util/windows-provider-shell.js +29 -0
  371. package/server/dist/verification-sentinel.js +14 -0
  372. package/server/dist/vitest-setup.js +2 -0
  373. package/server/dist/windows-job-supervisor.js +133 -0
  374. package/server/dist/workspace-manager.js +9 -30
  375. package/server/dist/worktree-manager.js +17 -2
  376. package/server/dist/worktree-overlay.js +3 -1
  377. package/client/dist/assets/ActivityFeedPage-tAQcWs1R.js +0 -1
  378. package/client/dist/assets/AgentBrowserCapture-BX8BNae5.js +0 -1
  379. package/client/dist/assets/AgentModeAnalyticsPane-BVAqCL0K.js +0 -2
  380. package/client/dist/assets/AgentModeCodePane-9XfqQc9c.js +0 -2
  381. package/client/dist/assets/AgentModeJobsPane-BCjLsPhc.js +0 -1
  382. package/client/dist/assets/AgentsPage-C8J_42sm.js +0 -87
  383. package/client/dist/assets/AnalyticsPage-BtQuyfJm.js +0 -1
  384. package/client/dist/assets/CodePage-BZ2MwdCu.js +0 -2
  385. package/client/dist/assets/DesktopAnalyticsPage-Bh_29V4g.js +0 -1
  386. package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
  387. package/client/dist/assets/InteractiveJobComposer-BCZiSfzc.js +0 -19
  388. package/client/dist/assets/JobDetailModal-NAJmuYIT.js +0 -1
  389. package/client/dist/assets/JobDetailPage-fE0ZycUX.js +0 -1
  390. package/client/dist/assets/JobsPage-z5M_HVJG.js +0 -1
  391. package/client/dist/assets/LoopBuilderPage-j1FQS47c.js +0 -7
  392. package/client/dist/assets/LoopPreviewModal-DhDJjdCe.js +0 -1
  393. package/client/dist/assets/LoopsPage-Ll7zORZb.js +0 -1
  394. package/client/dist/assets/MinimizedChatsContext-Bd3OqR_S.js +0 -1
  395. package/client/dist/assets/PluginsPage-DN4kGvJt.js +0 -2
  396. package/client/dist/assets/ProjectSettingsDialog-OgsgxjzF.js +0 -1
  397. package/client/dist/assets/ReviewPacketPage-DFog4sXn.js +0 -1
  398. package/client/dist/assets/TemplatePreviewModal-L2XxqiOD.js +0 -1
  399. package/client/dist/assets/TicketDetailModal-CsF-AWZX.js +0 -14
  400. package/client/dist/assets/Trans-Bsz9_gyq.js +0 -1
  401. package/client/dist/assets/agent-BQgKv_2F.js +0 -1
  402. package/client/dist/assets/agent-C1NRDJqL.js +0 -1
  403. package/client/dist/assets/agent-CLNleiy6.js +0 -1
  404. package/client/dist/assets/agent-CmceArkQ.js +0 -1
  405. package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
  406. package/client/dist/assets/agent-DB1JeE64.js +0 -1
  407. package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
  408. package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
  409. package/client/dist/assets/auth-D_5bQv5W.js +0 -1
  410. package/client/dist/assets/ban-E4EaZe3a.js +0 -1
  411. package/client/dist/assets/bot-DzuuuC5R.js +0 -1
  412. package/client/dist/assets/browser-BgqHDMXP.js +0 -1
  413. package/client/dist/assets/browser-CsKJtrpv.js +0 -1
  414. package/client/dist/assets/browser-Cu58EpZU.js +0 -1
  415. package/client/dist/assets/browser-CvXOJm-j.js +0 -1
  416. package/client/dist/assets/browser-DOrFh1c7.js +0 -1
  417. package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
  418. package/client/dist/assets/browser-LPgxvJu2.js +0 -1
  419. package/client/dist/assets/browser-eL-3MIEI.js +0 -1
  420. package/client/dist/assets/builder-0IWtosUm.js +0 -1
  421. package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
  422. package/client/dist/assets/builder-BnqaaBkT.js +0 -1
  423. package/client/dist/assets/builder-BxxfGgAO.js +0 -1
  424. package/client/dist/assets/builder-C5IIgWsq.js +0 -1
  425. package/client/dist/assets/builder-CHuahItu.js +0 -1
  426. package/client/dist/assets/builder-CjWosXL_.js +0 -1
  427. package/client/dist/assets/builder-CpCIOoKj.js +0 -1
  428. package/client/dist/assets/code-CHPkOkDS.js +0 -1
  429. package/client/dist/assets/commands-6tYQih5m.js +0 -1
  430. package/client/dist/assets/commands-BK2QinXN.js +0 -1
  431. package/client/dist/assets/commands-C2Nca76h.js +0 -1
  432. package/client/dist/assets/commands-CKdLihNX.js +0 -1
  433. package/client/dist/assets/commands-CYIBTAlc.js +0 -1
  434. package/client/dist/assets/commands-CjUtYJIM.js +0 -1
  435. package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
  436. package/client/dist/assets/commands-D_ZHYQug.js +0 -1
  437. package/client/dist/assets/common-6MLbD-jM.js +0 -1
  438. package/client/dist/assets/common-BKGEu4rm.js +0 -1
  439. package/client/dist/assets/common-BNbnv8cE.js +0 -1
  440. package/client/dist/assets/common-BqGWtDbt.js +0 -1
  441. package/client/dist/assets/common-DBtTzeYm.js +0 -1
  442. package/client/dist/assets/common-DEW3Nr8n.js +0 -1
  443. package/client/dist/assets/common-DHsv9N5d.js +0 -1
  444. package/client/dist/assets/common-mkrEcW-Y.js +0 -1
  445. package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
  446. package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
  447. package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
  448. package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
  449. package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
  450. package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
  451. package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
  452. package/client/dist/assets/dialog-CdWCb02j.js +0 -45
  453. package/client/dist/assets/dist-bVqfYmby.js +0 -1
  454. package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
  455. package/client/dist/assets/formatDistanceToNow-DQCvMgUc.js +0 -1
  456. package/client/dist/assets/getTimezoneOffsetInMilliseconds-C16cxSe1.js +0 -1
  457. package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
  458. package/client/dist/assets/i18n-hnX2DPmo.js +0 -2
  459. package/client/dist/assets/index-DIhMEAVw.js +0 -66
  460. package/client/dist/assets/index-DwygcU7s.css +0 -2
  461. package/client/dist/assets/mcp-B8X8wBLu.js +0 -1
  462. package/client/dist/assets/mcp-C2AojZZN.js +0 -1
  463. package/client/dist/assets/mcp-D8BT78_L.js +0 -1
  464. package/client/dist/assets/mcp-Df5RWBNk.js +0 -1
  465. package/client/dist/assets/mcp-Dj7BgMQW.js +0 -1
  466. package/client/dist/assets/mcp-Q7iT7rcz.js +0 -1
  467. package/client/dist/assets/mcp-YlvNvJbW.js +0 -1
  468. package/client/dist/assets/mcp-mKFpTSRb.js +0 -1
  469. package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
  470. package/client/dist/assets/narration-BGuiEDj3.js +0 -1
  471. package/client/dist/assets/narration-BuSoqRmv.js +0 -1
  472. package/client/dist/assets/narration-CB9t29fy.js +0 -1
  473. package/client/dist/assets/narration-CU3x8FHD.js +0 -1
  474. package/client/dist/assets/narration-CWeXhfP5.js +0 -1
  475. package/client/dist/assets/narration-DRaDnhT5.js +0 -1
  476. package/client/dist/assets/narration-DtEbQgCw.js +0 -1
  477. package/client/dist/assets/nav-ApJVg3xu.js +0 -1
  478. package/client/dist/assets/nav-BKNky1Bg.js +0 -1
  479. package/client/dist/assets/nav-C6vWWKYx.js +0 -1
  480. package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
  481. package/client/dist/assets/nav-CsmFBIDf.js +0 -1
  482. package/client/dist/assets/nav-DjkmQJjd.js +0 -1
  483. package/client/dist/assets/packet-B1IxaL5f.js +0 -1
  484. package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
  485. package/client/dist/assets/packet-By8fqYxW.js +0 -1
  486. package/client/dist/assets/packet-CZdvA44E.js +0 -1
  487. package/client/dist/assets/packet-CrjHRQCP.js +0 -1
  488. package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
  489. package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
  490. package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
  491. package/client/dist/assets/play-DNlrawbS.js +0 -1
  492. package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
  493. package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
  494. package/client/dist/assets/settings-C1-7cXzC.js +0 -1
  495. package/client/dist/assets/settings-CAFaT38b.js +0 -1
  496. package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
  497. package/client/dist/assets/settings-DHh7_IF5.js +0 -1
  498. package/client/dist/assets/settings-DQXAmdcv.js +0 -1
  499. package/client/dist/assets/settings-guzlhpup.js +0 -1
  500. package/client/dist/assets/settings-sZfTPMEH.js +0 -1
  501. package/client/dist/assets/setup-3jWaoVUO.js +0 -1
  502. package/client/dist/assets/setup-BjgRneb2.js +0 -1
  503. package/client/dist/assets/setup-BzFIjv-f.js +0 -1
  504. package/client/dist/assets/setup-C4VfQsxE.js +0 -1
  505. package/client/dist/assets/setup-DfUkpDbN.js +0 -1
  506. package/client/dist/assets/setup-ECe90Pmu.js +0 -1
  507. package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
  508. package/client/dist/assets/setup-jwfLMiM2.js +0 -1
  509. package/client/dist/assets/spending-EC9xqio-.js +0 -1
  510. package/client/dist/assets/useDesktop-BQVt8lFd.js +0 -1
@@ -21,7 +21,7 @@ const GIT_MAX_BUFFER = 4 * 1024 * 1024;
21
21
  const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
22
22
  function git(repoDir, args) {
23
23
  return new Promise((resolve, reject) => {
24
- (0, child_process_1.execFile)('git', args, { cwd: repoDir, env: file_provenance_1.GIT_EXEC_ENV, timeout: GIT_TIMEOUT_MS, maxBuffer: GIT_MAX_BUFFER, windowsHide: true }, (err, stdout, stderr) => {
24
+ (0, child_process_1.execFile)('git', args, { cwd: repoDir, env: (0, file_provenance_1.gitExecEnv)(), timeout: GIT_TIMEOUT_MS, maxBuffer: GIT_MAX_BUFFER, windowsHide: true }, (err, stdout, stderr) => {
25
25
  if (err) {
26
26
  const detail = String(stderr || err.message).trim();
27
27
  reject(new Error(detail));
@@ -56,7 +56,7 @@ function parseWorktreePorcelain(out) {
56
56
  return worktrees;
57
57
  }
58
58
  async function listLocalBranches(repoDir) {
59
- const out = await git(repoDir, ['branch', '--format=%(refname:short)']);
59
+ const out = await git(repoDir, ['branch', '--format=%(refname:lstrip=2)']);
60
60
  return out.split('\n').map((b) => b.trim()).filter(Boolean);
61
61
  }
62
62
  /** Lossless checkout preflight. Unlike the dashboard-oriented aggregate info,
@@ -90,7 +90,7 @@ async function getProjectGitInfo(repoDir) {
90
90
  let branch = null;
91
91
  let detached = false;
92
92
  try {
93
- branch = await git(repoDir, ['symbolic-ref', '--short', 'HEAD']);
93
+ branch = (await git(repoDir, ['symbolic-ref', 'HEAD'])).replace(/^refs\/heads\//, '');
94
94
  }
95
95
  catch {
96
96
  detached = true;
@@ -159,7 +159,7 @@ async function checkoutProjectBranch(repoDir, branch) {
159
159
  return { ok: false, error: `Unknown local branch: ${branch}` };
160
160
  }
161
161
  try {
162
- await git(repoDir, ['checkout', branch]);
162
+ await git(repoDir, ['checkout', '--no-overwrite-ignore', branch]);
163
163
  return { ok: true };
164
164
  }
165
165
  catch (err) {
@@ -192,19 +192,13 @@ async function checkoutProjectReviewBranch(repoDir, branch, expectedSha) {
192
192
  if (expected !== null && !COMMIT_SHA_RE.test(expected)) {
193
193
  return { ok: false, error: 'The verified delivery commit is invalid; checkout was not attempted.' };
194
194
  }
195
- let dirty = false;
196
- try {
197
- // Tracked modifications only — see inspectProjectCheckoutCleanliness.
198
- dirty = (await git(repoDir, ['status', '--porcelain', '--untracked-files=no'])) !== '';
199
- }
200
- catch (err) {
201
- return { ok: false, error: err instanceof Error ? err.message : 'git status failed' };
202
- }
203
- if (dirty)
204
- return { ok: false, error: 'Working tree has uncommitted changes. Commit or stash them before checkout.' };
205
- // Best effort: refresh remote-tracking refs before deciding whether a branch
206
- // can be materialized. Failure degrades to local-only checkout.
207
- await git(repoDir, ['fetch', 'origin']).catch(() => { });
195
+ // Git's checkout transaction preserves compatible staged/unstaged changes
196
+ // and refuses only paths it would overwrite. A blanket dirty guard prevented
197
+ // reviewing any delivery while unrelated local edits existed. Include
198
+ // ignored files in that protection (Git otherwise overwrites them by default).
199
+ const cleanliness = await inspectProjectCheckoutCleanliness(repoDir);
200
+ if (!cleanliness.ok)
201
+ return { ok: false, error: cleanliness.detail };
208
202
  let branches;
209
203
  try {
210
204
  branches = await listLocalBranches(repoDir);
@@ -213,6 +207,10 @@ async function checkoutProjectReviewBranch(repoDir, branch, expectedSha) {
213
207
  return { ok: false, error: err instanceof Error ? err.message : 'git failed' };
214
208
  }
215
209
  try {
210
+ // A verified local branch needs no network. Refresh only when it must be
211
+ // materialized from origin, avoiding a fetch timeout on every local handoff.
212
+ if (!branches.includes(target))
213
+ await git(repoDir, ['fetch', 'origin']).catch(() => { });
216
214
  if (branches.includes(target)) {
217
215
  if (expected) {
218
216
  const localSha = (await git(repoDir, ['rev-parse', '--verify', `refs/heads/${target}`])).toLowerCase();
@@ -223,7 +221,7 @@ async function checkoutProjectReviewBranch(repoDir, branch, expectedSha) {
223
221
  };
224
222
  }
225
223
  }
226
- await git(repoDir, ['checkout', target]);
224
+ await git(repoDir, ['checkout', '--no-overwrite-ignore', target]);
227
225
  }
228
226
  else if (await remoteBranchExists(repoDir, target)) {
229
227
  if (expected) {
@@ -235,7 +233,7 @@ async function checkoutProjectReviewBranch(repoDir, branch, expectedSha) {
235
233
  };
236
234
  }
237
235
  }
238
- await git(repoDir, ['checkout', '-b', target, '--track', `origin/${target}`]);
236
+ await git(repoDir, ['checkout', '--no-overwrite-ignore', '-b', target, '--track', `origin/${target}`]);
239
237
  }
240
238
  else {
241
239
  return { ok: false, error: `Unknown branch: ${target}` };
@@ -247,10 +245,10 @@ async function checkoutProjectReviewBranch(repoDir, branch, expectedSha) {
247
245
  if (expected) {
248
246
  try {
249
247
  const [currentBranch, currentSha] = await Promise.all([
250
- git(repoDir, ['rev-parse', '--abbrev-ref', 'HEAD']),
248
+ git(repoDir, ['symbolic-ref', '--quiet', 'HEAD']),
251
249
  git(repoDir, ['rev-parse', '--verify', 'HEAD']),
252
250
  ]);
253
- if (currentBranch !== target || currentSha.toLowerCase() !== expected) {
251
+ if (currentBranch !== `refs/heads/${target}` || currentSha.toLowerCase() !== expected) {
254
252
  return {
255
253
  ok: false,
256
254
  error: `Checkout changed concurrently and did not land on verified delivery ${expected.slice(0, 8)}. No reset or cleanup was performed.`,
@@ -10,6 +10,8 @@ const path_1 = __importDefault(require("path"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const os_1 = __importDefault(require("os"));
12
12
  const db_1 = require("./db");
13
+ const project_repositories_1 = require("./project-repositories");
14
+ const multi_repo_execution_store_1 = require("./multi-repo-execution-store");
13
15
  const queue_manager_1 = require("./queue-manager");
14
16
  const agent_defaults_1 = require("./agent-defaults");
15
17
  const chat_manager_1 = require("./chat-manager");
@@ -37,6 +39,13 @@ const workspace_manager_1 = require("./workspace-manager");
37
39
  const ticket_store_1 = require("./ticket-store");
38
40
  const jira_sync_manager_1 = require("./jira/jira-sync-manager");
39
41
  const stuck_run_detector_1 = require("./stuck-run-detector");
42
+ const milestone_progress_1 = require("./milestone-progress");
43
+ const milestone_chain_1 = require("./milestone-chain");
44
+ const internal_api_1 = require("./internal-api");
45
+ const blueprint_render_1 = require("./blueprint-render");
46
+ const worktree_manager_1 = require("./worktree-manager");
47
+ const integration_branch_1 = require("./integration-branch");
48
+ const db_2 = require("./db");
40
49
  const loop_run_manager_1 = require("./loop-run-manager");
41
50
  const loop_executors_1 = require("./loop-executors");
42
51
  const rail_isolated_launch_1 = require("./rail-isolated-launch");
@@ -85,6 +94,9 @@ function reprojectActivePrDeliveries(ctx) {
85
94
  return rows.length;
86
95
  }
87
96
  // ─── ProjectRegistry ──────────────────────────────────────────────────────────
97
+ class ProjectDatabaseBusyError extends Error {
98
+ }
99
+ const PROJECT_LOAD_RETRY_DELAYS_MS = [1000, 2000, 5000, 30000];
88
100
  class ProjectRegistry {
89
101
  _desktopDb;
90
102
  _contexts;
@@ -94,6 +106,8 @@ class ProjectRegistry {
94
106
  // M9: projects whose per-project DB failed to load at startup (corrupt, locked,
95
107
  // or migration-stuck). They stay registered but have no live context.
96
108
  _failedProjects;
109
+ _loadRetryTimers = new Map();
110
+ _shuttingDown = false;
97
111
  // App-wide shared browser context for "Add Spec from a website": ONE persistent
98
112
  // Chromium profile under ~/.specrails/browser-profile, so cookies/logins are
99
113
  // shared across every project. Launched lazily, disposed once at shutdown().
@@ -129,21 +143,52 @@ class ProjectRegistry {
129
143
  console.error('[project-registry] registry reconcile failed (non-fatal):', err);
130
144
  }
131
145
  for (const project of projects) {
132
- try {
133
- this._loadProjectContext(project);
134
- this._failedProjects.delete(project.id);
146
+ this._tryLoadProject(project);
147
+ }
148
+ }
149
+ _tryLoadProject(project, retry = 0) {
150
+ this._clearLoadRetry(project.id);
151
+ if (this._shuttingDown)
152
+ return;
153
+ try {
154
+ this._loadProjectContext(project);
155
+ const recovered = this._failedProjects.delete(project.id);
156
+ if (recovered) {
157
+ try {
158
+ this._broadcast({ type: 'desktop.project_recovered', projectId: project.id, timestamp: new Date().toISOString() });
159
+ }
160
+ catch { /* the available context remains authoritative */ }
135
161
  }
136
- catch (err) {
137
- // M9: a single corrupt / locked / migration-stuck per-project jobs.sqlite
138
- // must NOT crash the whole app at startup (previously it did, killing
139
- // every other project + the UI in a restart loop). Log it, record it as
140
- // failed-to-load, and keep loading the rest.
141
- const msg = err instanceof Error ? err.message : String(err);
142
- console.error(`[project-registry] failed to load project ${project.id} (${project.slug}): ${msg}`);
143
- this._failedProjects.set(project.id, { project, error: msg });
162
+ }
163
+ catch (err) {
164
+ const msg = err instanceof Error ? err.message : String(err);
165
+ console.error(`[project-registry] failed to load project ${project.id} (${project.slug}): ${msg}`);
166
+ this._failedProjects.set(project.id, { project, error: msg });
167
+ // Only an unopened DB can be retried safely: no managers/watchers or
168
+ // recovery work exist yet. Corruption and later constructor errors stay
169
+ // visible without repeatedly constructing partially initialized contexts.
170
+ if (err instanceof ProjectDatabaseBusyError) {
171
+ const timer = setTimeout(() => {
172
+ this._loadRetryTimers.delete(project.id);
173
+ const current = (0, desktop_db_1.getProject)(this._desktopDb, project.id);
174
+ if (current)
175
+ this._tryLoadProject(current, Math.min(retry + 1, PROJECT_LOAD_RETRY_DELAYS_MS.length - 1));
176
+ }, PROJECT_LOAD_RETRY_DELAYS_MS[retry]);
177
+ timer.unref?.();
178
+ this._loadRetryTimers.set(project.id, timer);
144
179
  }
145
180
  }
146
181
  }
182
+ _clearLoadRetry(id) {
183
+ const timer = this._loadRetryTimers.get(id);
184
+ if (timer)
185
+ clearTimeout(timer);
186
+ this._loadRetryTimers.delete(id);
187
+ }
188
+ /** The durable catalog includes projects whose DB could not be opened. */
189
+ listProjects() {
190
+ return (0, desktop_db_1.listProjects)(this._desktopDb);
191
+ }
147
192
  /** Projects whose per-project DB failed to load at startup (M9). */
148
193
  listFailedProjects() {
149
194
  return Array.from(this._failedProjects.values());
@@ -217,6 +262,7 @@ class ProjectRegistry {
217
262
  }
218
263
  }
219
264
  removeProject(id) {
265
+ this._clearLoadRetry(id);
220
266
  // Resolve the repo path BEFORE the DB row is deleted below so we can drop the
221
267
  // shared artifact-registry entry. Prefer the live context, fall back to the
222
268
  // desktop DB row (project may be registered-but-not-loaded, e.g. M9 failure).
@@ -249,6 +295,9 @@ class ProjectRegistry {
249
295
  // deleting the data dir here would make that effect unrecoverable.
250
296
  throw new Error('Project removal deferred: terminal job recovery is still pending');
251
297
  }
298
+ // Persistent history must be removable before deleting project databases
299
+ // or directories; a storage failure leaves the registered data retryable.
300
+ (0, transient_children_1.purgeBackgroundProcessHistory)({ projectId: id });
252
301
  try {
253
302
  ctx.chatManager.shutdown();
254
303
  }
@@ -297,6 +346,10 @@ class ProjectRegistry {
297
346
  ctx.stuckRunDetector.stop();
298
347
  }
299
348
  catch { /* ignore */ }
349
+ try {
350
+ ctx.milestoneProgress?.dispose();
351
+ }
352
+ catch { /* ignore */ }
300
353
  // Kill any terminal sessions belonging to this project
301
354
  try {
302
355
  (0, terminal_manager_1.getTerminalManager)().killAllForProject(id);
@@ -333,6 +386,9 @@ class ProjectRegistry {
333
386
  catch { /* ignore */ }
334
387
  this._contexts.delete(id);
335
388
  }
389
+ else {
390
+ (0, transient_children_1.purgeBackgroundProcessHistory)({ projectId: id });
391
+ }
336
392
  // B54: remove the ENTIRE app-managed data dir even when context hydration
337
393
  // failed. The persisted row is authoritative for registered-but-not-loaded
338
394
  // projects and contains the slug needed for safe cleanup.
@@ -383,12 +439,82 @@ class ProjectRegistry {
383
439
  getContext(id) {
384
440
  return this._contexts.get(id);
385
441
  }
386
- getContextByPath(projectPath) {
387
- const row = (0, desktop_db_1.getProjectByPath)(this._desktopDb, projectPath);
442
+ getContextByPath(projectPath, projectId) {
443
+ const row = (0, project_repositories_1.resolveRepositoryProject)((0, desktop_db_1.listProjects)(this._desktopDb), projectPath, projectId);
388
444
  if (!row)
389
445
  return undefined;
390
446
  return this._contexts.get(row.id);
391
447
  }
448
+ /** Preserve manager closures and shared stores when the repository catalog changes. */
449
+ refreshProjectRepositories(projectId) {
450
+ const row = (0, desktop_db_1.getProject)(this._desktopDb, projectId);
451
+ if (!row)
452
+ throw new project_repositories_1.RepositoryValidationError('Project not found', 'project_not_found', 404);
453
+ const context = this._contexts.get(projectId);
454
+ if (context) {
455
+ for (const previous of (0, project_repositories_1.getProjectRepositories)(context.project)) {
456
+ const next = (0, project_repositories_1.getProjectRepositories)(row).find((repository) => repository.id === previous.id);
457
+ if (!next || next.path !== previous.path) {
458
+ try {
459
+ context.fileSummaryManager.detachWatcher(projectId, previous.id);
460
+ }
461
+ catch (err) {
462
+ console.warn('[project-registry] repository watcher cleanup failed:', err);
463
+ }
464
+ }
465
+ }
466
+ Object.assign(context.project, row);
467
+ }
468
+ const failed = this._failedProjects.get(projectId);
469
+ if (failed)
470
+ Object.assign(failed.project, row);
471
+ return context?.project ?? row;
472
+ }
473
+ addRepository(projectId, input) {
474
+ const repository = (0, desktop_db_1.addProjectRepository)(this._desktopDb, projectId, input);
475
+ this.refreshProjectRepositories(projectId);
476
+ return repository;
477
+ }
478
+ updateRepository(projectId, repositoryId, input) {
479
+ if (input.path !== undefined) {
480
+ const project = (0, desktop_db_1.getProject)(this._desktopDb, projectId);
481
+ if (!project)
482
+ throw new project_repositories_1.RepositoryValidationError('Project not found', 'project_not_found', 404);
483
+ const current = (0, project_repositories_1.resolveProjectRepository)(project, repositoryId);
484
+ if (!current.isPrimary && (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(input.path)) === (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(current.path))) {
485
+ input = { ...input };
486
+ delete input.path;
487
+ }
488
+ }
489
+ if (input.path !== undefined)
490
+ this._assertRepositoryUnreferenced(projectId, repositoryId);
491
+ const repository = (0, desktop_db_1.updateProjectRepository)(this._desktopDb, projectId, repositoryId, input);
492
+ this.refreshProjectRepositories(projectId);
493
+ return repository;
494
+ }
495
+ removeRepository(projectId, repositoryId) {
496
+ this._assertRepositoryUnreferenced(projectId, repositoryId);
497
+ (0, desktop_db_1.removeProjectRepository)(this._desktopDb, projectId, repositoryId);
498
+ this.refreshProjectRepositories(projectId);
499
+ }
500
+ _assertRepositoryUnreferenced(projectId, repositoryId) {
501
+ const project = (0, desktop_db_1.getProject)(this._desktopDb, projectId);
502
+ if (!project)
503
+ throw new project_repositories_1.RepositoryValidationError('Project not found', 'project_not_found', 404);
504
+ const repository = (0, project_repositories_1.resolveProjectRepository)(project, repositoryId);
505
+ if (repository.isPrimary)
506
+ throw new project_repositories_1.RepositoryValidationError('The primary repository cannot be removed or relocated here', 'primary_repository_protected', 409);
507
+ const context = this._contexts.get(projectId);
508
+ if (!context)
509
+ throw new project_repositories_1.RepositoryValidationError('Load this project before removing or relocating a repository', 'project_unavailable', 503);
510
+ const execution = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
511
+ const store = (0, ticket_store_1.readStore)(execution.relocated ? execution.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path));
512
+ const ticketIds = Object.values(store.tickets).filter((ticket) => ticket.repositoryIds?.includes(repositoryId)).map((ticket) => ticket.id);
513
+ const references = (0, multi_repo_execution_store_1.getRepositoryExecutionReferences)(context.db, repositoryId);
514
+ if (ticketIds.length || references.runIds.length || references.deliveryIds.length) {
515
+ throw new project_repositories_1.RepositoryValidationError('Repository is referenced by specs, runs or pending deliveries', 'repository_in_use', 409, { ticketIds, ...references });
516
+ }
517
+ }
392
518
  listContexts() {
393
519
  return Array.from(this._contexts.values());
394
520
  }
@@ -400,6 +526,9 @@ class ProjectRegistry {
400
526
  * exiting anyway).
401
527
  */
402
528
  shutdown() {
529
+ this._shuttingDown = true;
530
+ for (const id of this._loadRetryTimers.keys())
531
+ this._clearLoadRetry(id);
403
532
  for (const ctx of this._contexts.values()) {
404
533
  try {
405
534
  ctx.queueManager.shutdown();
@@ -447,6 +576,10 @@ class ProjectRegistry {
447
576
  ctx.stuckRunDetector.stop();
448
577
  }
449
578
  catch { /* ignore */ }
579
+ try {
580
+ ctx.milestoneProgress?.dispose();
581
+ }
582
+ catch { /* ignore */ }
450
583
  // Release chokidar watchers + abort in-flight generations so a restart
451
584
  // does not leak handles/children — mirror removeProject()'s per-project teardown.
452
585
  try {
@@ -473,13 +606,33 @@ class ProjectRegistry {
473
606
  // A context is visible synchronously, but no subprocess launch may enter
474
607
  // until durable loop callbacks and isolated worktrees have been reconciled.
475
608
  (0, process_admission_1.beginProjectProcessQuiescence)(project.id);
476
- const db = (0, db_1.initDb)(project.db_path);
609
+ let db;
610
+ try {
611
+ db = (0, db_1.initDb)(project.db_path);
612
+ }
613
+ catch (err) {
614
+ const code = err?.code;
615
+ if (typeof code === 'string' && /^SQLITE_(BUSY|LOCKED)(_|$)/.test(code)) {
616
+ throw new ProjectDatabaseBusyError(err instanceof Error ? err.message : String(err), { cause: err });
617
+ }
618
+ throw err;
619
+ }
477
620
  // Bind broadcast with projectId so all WS messages carry context.
478
621
  // Also wire agent status: when a queued job reaches a terminal state,
479
622
  // clear current_job_id on any agent that was assigned to it.
623
+ // Milestone progress tap (premium-milestone-progress): every project-scoped
624
+ // broadcast that can change a milestone's counts/rails/chain schedules ONE
625
+ // debounced `blueprint.milestone_progress` re-derivation. Assigned below,
626
+ // once the rail maps exist; null until then (startup messages are few).
627
+ let milestoneProgress = null;
628
+ let milestoneChains = null;
480
629
  const boundBroadcast = (msg) => {
481
630
  const enriched = { ...msg, projectId: project.id };
482
631
  this._broadcast(enriched);
632
+ // Chain first: a delivery settle may launch the next chunk (async) and
633
+ // emit its own `milestone.chain_changed`; the progress tap then coalesces.
634
+ milestoneChains?.observe(msg);
635
+ milestoneProgress?.observe(msg);
483
636
  if (msg.type === 'queue') {
484
637
  for (const job of msg.jobs) {
485
638
  if (isTerminalJobStatus(job.status)) {
@@ -811,7 +964,7 @@ class ProjectRegistry {
811
964
  }
812
965
  },
813
966
  });
814
- const chatManager = new chat_manager_1.ChatManager(boundBroadcast, db, project.path, project.name, project.provider ?? 'claude', project.id, project.slug);
967
+ const chatManager = new chat_manager_1.ChatManager(boundBroadcast, db, project.path, project.name, project.provider ?? 'claude', project.id, project.slug, () => (0, project_repositories_1.getProjectRepositories)(project));
815
968
  const setupManager = new setup_manager_1.SetupManager(boundBroadcast, (pid, sid) => (0, desktop_db_1.setProjectSetupSession)(this._desktopDb, pid, sid), (pid) => (0, desktop_db_1.clearProjectSetupSession)(this._desktopDb, pid),
816
969
  // LOW-2: resolve the per-project DB so phase-4 setup-chat AI turns record
817
970
  // an `ai_invocations` row (surface='setup'). Returns null until the project
@@ -912,11 +1065,11 @@ class ProjectRegistry {
912
1065
  // byte-identical except for the explicit per-project passthrough overlay.
913
1066
  // See resolveLoopBaseEnv.
914
1067
  const loopRunManager = new loop_run_manager_1.LoopRunManager(db, boundBroadcast, (0, loop_executors_1.createLoopExecutors)({
1068
+ pluginScope: () => ({ stateRoot: (0, workspace_resolution_1.resolveProjectExecution)(project).cwd, legacyProviderId: project.provider }),
915
1069
  env: () => (0, workspace_resolution_1.resolveLoopBaseEnv)({ slug: project.slug, path: project.path }, undefined, (0, project_env_1.applyWorktreeEnvPassthrough)(db, process.env)),
916
- // Global Specrails Agents defaults, loop path: per-agent model overrides
917
- // ride SPECRAILS_PROFILE_PATH on each ai-step spawn. Resolved per step
918
- // (no restart); inert (null) unless the global layer carries per-agent
919
- // models for the provider — byte-identical legacy behaviour otherwise.
1070
+ // Named rail profiles and global per-agent defaults ride the same
1071
+ // SPECRAILS_PROFILE_PATH snapshot in both AI transports. An explicit null
1072
+ // rail selection opts out; omitted selections keep Core's default rules.
920
1073
  profilePathFor: (0, agent_defaults_1.createLoopProfilePathResolver)({
921
1074
  desktopDb: this._desktopDb,
922
1075
  profileRoot: () => (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path }).cwd,
@@ -945,6 +1098,7 @@ class ProjectRegistry {
945
1098
  status: t.status,
946
1099
  priority: t.priority,
947
1100
  labels: t.labels,
1101
+ repositoryIds: t.repositoryIds,
948
1102
  jira_key: t.jira_key ?? null,
949
1103
  jira_url: t.jira_url ?? null,
950
1104
  openspecChangeName: typeof t.metadata?.openspecChangeName === 'string' ? t.metadata.openspecChangeName : undefined,
@@ -1074,6 +1228,15 @@ class ProjectRegistry {
1074
1228
  });
1075
1229
  const delivered = deliver();
1076
1230
  railLoopRuns.delete(runId);
1231
+ // Milestone chain fallback hook: records the engine outcome (names the
1232
+ // pause reason) and settles delivery-less chunks. Isolated chunks
1233
+ // advance from the later `rail.pr_state` settle instead.
1234
+ try {
1235
+ milestoneChains?.onRunSettled(runId, effectiveOutcome, opts?.stallReason);
1236
+ }
1237
+ catch (err) {
1238
+ console.error('[milestone-chain] onRunSettled failed:', err);
1239
+ }
1077
1240
  for (const ticketId of delivered.changedIds) {
1078
1241
  const ticket = delivered.store?.tickets[String(ticketId)];
1079
1242
  if (!ticket)
@@ -1113,7 +1276,96 @@ class ProjectRegistry {
1113
1276
  };
1114
1277
  const stuckRunDetector = new stuck_run_detector_1.StuckRunDetector(project.id, { db }, boundBroadcast);
1115
1278
  stuckRunDetector.start();
1116
- const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, stuckRunDetector, broadcast: boundBroadcast, railJobs, loopRunManager, railLoopRuns, onLoopRunFinished, getTicketSpec, desktopDb: this._desktopDb };
1279
+ const blueprintWorkspace = () => (0, milestone_progress_1.resolveBlueprintWorkspace)({ slug: project.slug, path: project.path });
1280
+ // Milestone launch chain (premium-milestone-progress D3): chunks launch
1281
+ // through the app's OWN rails launch route over loopback (the single
1282
+ // authority for every guard) — never by re-implementing the handler.
1283
+ const internalApi = (0, internal_api_1.createInternalApi)({ port: this._desktopPort });
1284
+ const railsBase = `/projects/${project.id}/rails`;
1285
+ milestoneChains = new milestone_chain_1.MilestoneChainManager(db, project.id, {
1286
+ createRail: async (name) => {
1287
+ const r = await internalApi.call('POST', railsBase, { name });
1288
+ const railIndex = r.body?.rail?.railIndex;
1289
+ return r.ok && typeof railIndex === 'number'
1290
+ ? { ok: true, railIndex }
1291
+ : { ok: false, status: r.status, ...(0, internal_api_1.internalApiError)(r) };
1292
+ },
1293
+ assignTickets: async (railIndex, ticketIds) => {
1294
+ const r = await internalApi.call('PUT', `${railsBase}/${railIndex}/tickets`, { ticketIds, mode: 'batch-implement' });
1295
+ return r.ok ? { ok: true } : { ok: false, status: r.status, ...(0, internal_api_1.internalApiError)(r) };
1296
+ },
1297
+ launch: async (railIndex, body) => {
1298
+ const r = await internalApi.call('POST', `${railsBase}/${railIndex}/launch`, body);
1299
+ if (!r.ok)
1300
+ return { ok: false, status: r.status, ...(0, internal_api_1.internalApiError)(r) };
1301
+ const data = (r.body ?? {});
1302
+ const ids = Array.isArray(data.loopRunIds) ? data.loopRunIds
1303
+ : Array.isArray(data.jobIds) ? data.jobIds
1304
+ : typeof data.jobId === 'string' ? [data.jobId] : [];
1305
+ return { ok: true, loopRunIds: ids.filter((v) => typeof v === 'string') };
1306
+ },
1307
+ activeDeliveryForRail: (railIndex) => {
1308
+ const row = (0, rail_pr_store_1.getActivePrDeliveryByRail)(db, railIndex);
1309
+ return row ? (0, rail_pr_store_1.toPrDeliverySnapshot)(row) : null;
1310
+ },
1311
+ findRailByName: (name) => {
1312
+ try {
1313
+ const hit = (0, rails_store_1.getRails)(db).find((r) => r.name === name);
1314
+ return hit ? hit.railIndex : null;
1315
+ }
1316
+ catch {
1317
+ return null;
1318
+ }
1319
+ },
1320
+ getDelivery: (id) => {
1321
+ const row = (0, rail_pr_store_1.getPrDelivery)(db, id);
1322
+ return row ? (0, rail_pr_store_1.toPrDeliverySnapshot)(row) : null;
1323
+ },
1324
+ branchExists: async (branch) => {
1325
+ const r = await worktree_manager_1.defaultGitRunner.run(['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], project.path);
1326
+ return r.code === 0;
1327
+ },
1328
+ readTickets: () => {
1329
+ try {
1330
+ return Object.values((0, ticket_store_1.readStore)(ticketStorePath()).tickets).map((t) => ({ id: t.id, status: t.status, labels: t.labels ?? [] }));
1331
+ }
1332
+ catch {
1333
+ return [];
1334
+ }
1335
+ },
1336
+ readBlueprint: () => { const ws = blueprintWorkspace(); return ws ? (0, blueprint_render_1.readBlueprint)(ws) : null; },
1337
+ integrationBranch: async () => {
1338
+ try {
1339
+ return (await (0, integration_branch_1.resolveIntegrationBranch)(worktree_manager_1.defaultGitRunner, { repoDir: project.path, projectSetting: (0, db_2.getProjectSettings)(db).integrationBranch })).branch;
1340
+ }
1341
+ catch {
1342
+ return null;
1343
+ }
1344
+ },
1345
+ runState: (runId) => {
1346
+ const run = (0, loop_runs_store_1.getLoopRun)(db, runId);
1347
+ if (!run)
1348
+ return null;
1349
+ return { settled: run.status !== 'running' && run.status !== 'paused', outcome: run.final_outcome ?? null };
1350
+ },
1351
+ broadcast: boundBroadcast,
1352
+ });
1353
+ milestoneProgress = new milestone_progress_1.MilestoneProgressBroadcaster({
1354
+ projectId: project.id,
1355
+ read: () => (0, milestone_progress_1.readMilestoneProgress)({
1356
+ db,
1357
+ projectId: project.id,
1358
+ workspaceDir: blueprintWorkspace,
1359
+ ticketsPath: ticketStorePath,
1360
+ railJobs,
1361
+ railLoopRuns,
1362
+ chains: () => milestoneChains?.listForProgress() ?? [],
1363
+ }),
1364
+ persistDone: (milestoneId) => (0, milestone_progress_1.markMilestoneDone)(blueprintWorkspace(), milestoneId) !== null,
1365
+ broadcast: boundBroadcast,
1366
+ });
1367
+ const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, stuckRunDetector, broadcast: boundBroadcast, railJobs, loopRunManager, railLoopRuns, onLoopRunFinished, getTicketSpec, desktopDb: this._desktopDb, milestoneProgress };
1368
+ ctx.milestoneChains = milestoneChains;
1117
1369
  this._contexts.set(project.id, ctx);
1118
1370
  const loopRecoveryOk = this._recoverOrphanLoopRuns(project, db, railLoopRuns, onLoopRunFinished, orphanLoopRuns);
1119
1371
  if (loopRecoveryOk) {