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
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerLoopRunRoutes = registerLoopRunRoutes;
4
+ const loop_graph_1 = require("./loop-graph");
4
5
  const feature_flags_1 = require("./feature-flags");
5
6
  const loops_store_1 = require("./loops-store");
6
7
  const loop_runs_store_1 = require("./loop-runs-store");
@@ -14,6 +15,12 @@ const spec_models_1 = require("./spec-models");
14
15
  const workspace_resolution_1 = require("./workspace-resolution");
15
16
  const loop_command_catalog_1 = require("./loop-command-catalog");
16
17
  const ids_1 = require("./ids");
18
+ const project_repositories_1 = require("./project-repositories");
19
+ const multi_repo_execution_1 = require("./multi-repo-execution");
20
+ const rails_store_1 = require("./rails-store");
21
+ const rail_pr_store_1 = require("./rail-pr-store");
22
+ const rail_isolation_1 = require("./rail-isolation");
23
+ const process_admission_1 = require("./process-admission");
17
24
  function registerLoopRunRoutes(deps) {
18
25
  const { router, ctx } = deps;
19
26
  // GET a single loop run's live/terminal state. Backs the companion's running
@@ -59,7 +66,7 @@ function registerLoopRunRoutes(deps) {
59
66
  : (0, run_duration_stats_1.getJobCommandDurationRange)(c.db, (0, run_duration_stats_1.jobCommandShape)(command));
60
67
  res.json({ range, minSamples: run_duration_stats_1.MIN_DURATION_SAMPLES });
61
68
  });
62
- router.post('/:projectId/loop-runs', (req, res) => {
69
+ router.post('/:projectId/loop-runs', async (req, res) => {
63
70
  if (!(0, feature_flags_1.isLoopsEnabled)()) {
64
71
  res.status(404).json({ error: 'Not Found' });
65
72
  return;
@@ -80,6 +87,11 @@ function registerLoopRunRoutes(deps) {
80
87
  res.status(400).json({ error: 'Loop must be published before it can run' });
81
88
  return;
82
89
  }
90
+ const validation = (0, loop_graph_1.validateLoopGraph)(loop.graph);
91
+ if (!validation.valid) {
92
+ res.status(422).json({ error: 'Loop graph is invalid', errors: validation.errors });
93
+ return;
94
+ }
83
95
  const check = (0, provider_selection_1.validateRequestedProvider)(c.project, aiEngine ?? providerAlias);
84
96
  if (!check.ok) {
85
97
  res.status(400).json({ error: check.error });
@@ -139,6 +151,63 @@ function registerLoopRunRoutes(deps) {
139
151
  && (0, runtime_1.isReasoningEffortValidForModel)(adapter, model, globalAgentDefaults.pipelineEffort)) {
140
152
  effort = globalAgentDefaults.pipelineEffort;
141
153
  }
154
+ let repositoryIds;
155
+ const primary = (0, project_repositories_1.getProjectRepositories)(c.project).find((repository) => repository.isPrimary);
156
+ try {
157
+ repositoryIds = (0, project_repositories_1.validateTicketRepositoryIds)(c.project, body.repositoryIds) ?? [primary.id];
158
+ (0, loop_graph_1.assertLoopShellRepositoryScope)(loop.graph, repositoryIds);
159
+ (0, process_admission_1.assertProcessAdmission)(c.project.id);
160
+ }
161
+ catch (error) {
162
+ res.status(error instanceof project_repositories_1.RepositoryValidationError ? error.status : error instanceof process_admission_1.ProcessAdmissionClosedError ? 409 : 400).json({ error: error instanceof Error ? error.message : 'Invalid repository scope' });
163
+ return;
164
+ }
165
+ if (repositoryIds.length !== 1 || repositoryIds[0] !== primary.id) {
166
+ if (!(0, rail_isolation_1.isolationApplies)({ loopsEnabled: true, scope: 'all', ticketCount: 1, readOnly: false })) {
167
+ res.status(409).json({ error: 'repository_isolation_required' });
168
+ return;
169
+ }
170
+ // A real empty rail makes the grouped delivery reviewable from the board
171
+ // without manufacturing a spec for a ticket-less loop.
172
+ const rails = (0, rails_store_1.getRails)(c.db);
173
+ const used = new Set([...c.railJobs.values(), ...c.railLoopRuns.values()].map((run) => run.railIndex));
174
+ let rail = rails.find((candidate) => candidate.ticketIds.length === 0 && !used.has(candidate.railIndex) && !(0, rail_pr_store_1.getActivePrDeliveryByRail)(c.db, candidate.railIndex));
175
+ let created = false;
176
+ if (!rail) {
177
+ if (rails.length >= rails_store_1.MAX_RAILS) {
178
+ res.status(409).json({ error: 'no_available_rail', detail: 'Finish a pending rail delivery before launching another isolated loop.' });
179
+ return;
180
+ }
181
+ rail = (0, rails_store_1.createRail)(c.db, loop.name);
182
+ created = true;
183
+ }
184
+ const reservationId = `preparing-${(0, ids_1.newId)()}`;
185
+ c.railLoopRuns.set(reservationId, { railIndex: rail.railIndex, ticketIds: [], requiresTerminalIntent: true });
186
+ try {
187
+ let prDeliveryId;
188
+ const ids = await (0, multi_repo_execution_1.launchMultiRepositoryRail)({
189
+ ctx: c, railIndex: rail.railIndex, ticketIds: [], repositoryIds, scope: 'all',
190
+ loopId, loopName: loop.name, loopGraph: loop.graph, provider, model, effort,
191
+ onPrDeliveryCreated: (id) => { prDeliveryId = id; },
192
+ });
193
+ res.status(202).json({ loopRunId: ids[0], railIndex: rail.railIndex, prDeliveryId, isolated: true });
194
+ }
195
+ catch (error) {
196
+ if (created && !(0, rail_pr_store_1.getActivePrDeliveryByRail)(c.db, rail.railIndex))
197
+ (0, rails_store_1.deleteRail)(c.db, rail.railIndex);
198
+ res.status(409).json({ error: 'repository_launch_failed', detail: error instanceof Error ? error.message : String(error) });
199
+ }
200
+ finally {
201
+ c.railLoopRuns.delete(reservationId);
202
+ if (created && (0, rails_store_1.getRails)(c.db).some((candidate) => candidate.railIndex === rail.railIndex)) {
203
+ const current = (0, rails_store_1.getRail)(c.db, rail.railIndex);
204
+ c.broadcast({ type: 'rail.updated', projectId: c.project.id, railIndex: current.railIndex,
205
+ changed: 'name', ticketIds: current.ticketIds, name: current.name ?? null,
206
+ mode: current.mode, profileName: current.profileName ?? null, aiEngine: current.aiEngine ?? null });
207
+ }
208
+ }
209
+ return;
210
+ }
142
211
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: c.project.slug, path: c.project.path });
143
212
  const runId = (0, ids_1.newId)();
144
213
  c.loopRunManager
@@ -148,6 +217,7 @@ function registerLoopRunRoutes(deps) {
148
217
  loopName: loop.name,
149
218
  graph: loop.graph,
150
219
  projectId: c.project.id,
220
+ repositoryId: primary.id,
151
221
  cwd: exec.cwd,
152
222
  repoDir: exec.relocated ? exec.repoDir : undefined,
153
223
  railIndex: null,
@@ -158,7 +228,7 @@ function registerLoopRunRoutes(deps) {
158
228
  model,
159
229
  effort,
160
230
  })
161
- .then((r) => c.onLoopRunFinished(r.runId, r.outcome))
231
+ .then((r) => c.onLoopRunFinished(r.runId, r.outcome, r.stallReason ? { stallReason: r.stallReason } : undefined))
162
232
  .catch((err) => {
163
233
  console.error('[loop-runs] standalone run failed:', err);
164
234
  c.onLoopRunFinished(runId, 'failed');
@@ -40,6 +40,7 @@ exports.registerTicketsRoutes = registerTicketsRoutes;
40
40
  // Domain routes extracted from project-router.ts (tickets).
41
41
  // Registered on the shared router by createProjectRouter — behaviour-preserving.
42
42
  const fs_1 = __importDefault(require("fs"));
43
+ const project_repositories_1 = require("./project-repositories");
43
44
  const ids_1 = require("./ids");
44
45
  const db_1 = require("./db");
45
46
  const providers_1 = require("./providers");
@@ -130,6 +131,26 @@ async function maybePromoteSpecToJira(c, ticketId, createLocal, broadcast) {
130
131
  }
131
132
  function registerTicketsRoutes(deps) {
132
133
  const { router, registry, ctx, ticketPath } = deps;
134
+ // Validate scope at every authoring boundary before writes, uploads or AI.
135
+ const validateRepositoryScope = (req, res, next) => {
136
+ try {
137
+ res.locals.repositoryIds = (0, project_repositories_1.validateTicketRepositoryIds)(ctx(req).project, req.body?.repositoryIds);
138
+ next();
139
+ }
140
+ catch (err) {
141
+ if (err instanceof project_repositories_1.RepositoryValidationError)
142
+ res.status(err.status).json({ error: err.message, code: err.code });
143
+ else
144
+ next(err);
145
+ }
146
+ };
147
+ router.post(['/:projectId/tickets', '/:projectId/tickets/generate-spec', '/:projectId/tickets/save-as-draft', '/:projectId/tickets/from-draft', '/:projectId/tickets/from-prompt'], validateRepositoryScope);
148
+ router.patch('/:projectId/tickets/:id', validateRepositoryScope);
149
+ const repositoryScope = (req, res) => {
150
+ // Generation can outlive a membership change; validate again when saving.
151
+ const ids = (0, project_repositories_1.validateTicketRepositoryIds)(ctx(req).project, res.locals.repositoryIds);
152
+ return ids === undefined ? {} : { repositoryIds: ids };
153
+ };
133
154
  // ─── Tickets ──────────────────────────────────────────────────────────────────
134
155
  /** Resolve the ticket storage file path for a project */
135
156
  // GET /:projectId/tickets — List all tickets with optional filters
@@ -326,6 +347,10 @@ function registerTicketsRoutes(deps) {
326
347
  `## Short Summary\n[One or two plain-language sentences, max 120 characters total, that capture the essence of this spec for a dashboard postit. No markdown, no bullets, no headings.]`;
327
348
  if (hasAttachments)
328
349
  baseSystemPrompt = `${baseSystemPrompt}\n\n${attachment_manager_1.USER_ATTACHMENT_SYSTEM_NOTE}`;
350
+ const selectedIds = res.locals.repositoryIds;
351
+ if ((0, project_repositories_1.getProjectRepositories)(project).length > 1) {
352
+ baseSystemPrompt += `\n\nThis logical project has one shared backlog across these repositories: ${JSON.stringify((0, project_repositories_1.getProjectRepositories)(project).map(({ id, name, path, isPrimary, kind }) => ({ id, name, path, isPrimary, kind })))}.\nThis spec targets repository IDs: ${JSON.stringify(selectedIds ?? [(0, project_repositories_1.getProjectRepositories)(project).find((repo) => repo.isPrimary).id])}. Describe cross-repository contracts when selected; do not create separate tickets per repository.`;
353
+ }
329
354
  const systemPrompt = baseSystemPrompt;
330
355
  const userPrompt = baseUserPrompt;
331
356
  // Generate-spec spawn args are adapter-driven. For Claude the `--tools`
@@ -512,6 +537,7 @@ function registerTicketsRoutes(deps) {
512
537
  const store = (0, ticket_store_1.mutateStore)(filePath, (s) => {
513
538
  const id = s.next_id++;
514
539
  const ticket = {
540
+ ...repositoryScope(req, res),
515
541
  id,
516
542
  title: specTitle,
517
543
  description,
@@ -703,6 +729,7 @@ function registerTicketsRoutes(deps) {
703
729
  return;
704
730
  }
705
731
  const title = providedTitle || target.title || (0, explore_draft_title_1.generateAutoTitle)(messages.map((m) => ({ role: m.role, content: m.content ?? '' })));
732
+ Object.assign(target, repositoryScope(req, res));
706
733
  target.title = title;
707
734
  if (description)
708
735
  target.description = description;
@@ -730,6 +757,7 @@ function registerTicketsRoutes(deps) {
730
757
  const existing = anyExisting && anyExisting.status === 'draft' ? anyExisting : undefined;
731
758
  const title = providedTitle || existing?.title || (0, explore_draft_title_1.generateAutoTitle)(messages.map((m) => ({ role: m.role, content: m.content ?? '' })));
732
759
  if (existing) {
760
+ Object.assign(existing, repositoryScope(req, res));
733
761
  existing.title = title;
734
762
  if (description)
735
763
  existing.description = description;
@@ -741,6 +769,7 @@ function registerTicketsRoutes(deps) {
741
769
  }
742
770
  const id = s.next_id++;
743
771
  const ticket = {
772
+ ...repositoryScope(req, res),
744
773
  id,
745
774
  title,
746
775
  description,
@@ -904,6 +933,7 @@ function registerTicketsRoutes(deps) {
904
933
  flipTarget.labels = labels;
905
934
  // PATCH-route semantics: apply only when the field was supplied;
906
935
  // metadata merges keys instead of replacing the object.
936
+ Object.assign(flipTarget, repositoryScope(req, res));
907
937
  if (hasAssignee)
908
938
  flipTarget.assignee = assignee;
909
939
  if (hasPrerequisites)
@@ -937,6 +967,7 @@ function registerTicketsRoutes(deps) {
937
967
  // Legacy: insert new ticket
938
968
  const id = s.next_id++;
939
969
  const ticket = {
970
+ ...repositoryScope(req, res),
940
971
  id,
941
972
  title: rawTitle,
942
973
  description: descriptionForStore,
@@ -1057,13 +1088,12 @@ function registerTicketsRoutes(deps) {
1057
1088
  // --resume; seeded with the just-inserted spec body). Default OFF at
1058
1089
  // this layer so Explore-client payloads stay byte-identical — the MCP
1059
1090
  // specs facade defaults it ON for agent-authored commit_drafts. The
1060
- // kill switch is checked inside the runner; Claude-only because the
1061
- // refine spawn hardcodes the `claude` binary.
1062
- const projectProviders = Array.isArray(project.providers) && project.providers.length > 0
1063
- ? project.providers
1064
- : [project.provider].filter((p) => typeof p === 'string');
1065
- const refineProvider = projectProviders.find((id) => (0, providers_1.getAdapter)(id).capabilities.structuredActions === true);
1066
- if (refineProvider) {
1091
+ // The originating agent provider is forwarded by the authenticated MCP
1092
+ // facade. Never borrow another installed provider (and its quota).
1093
+ const requestedAgentProvider = typeof body.agentProvider === 'string' ? body.agentProvider : null;
1094
+ const providerCheck = (0, provider_selection_1.validateRequestedProvider)(project, requestedAgentProvider);
1095
+ const refineProvider = providerCheck.ok ? providerCheck.provider : null;
1096
+ if (refineProvider && (0, providers_1.getAdapter)(refineProvider).capabilities.structuredActions === true) {
1067
1097
  const refineTicketId = created.id;
1068
1098
  const refineTitle = created.title;
1069
1099
  const refineDescription = created.description;
@@ -1083,7 +1113,19 @@ function registerTicketsRoutes(deps) {
1083
1113
  });
1084
1114
  }
1085
1115
  else {
1086
- console.log(`[project-router] from-draft contract refine skipped (ticket #${created.id}): no capable provider installed`);
1116
+ console.info('[project-router] agent-authored contract refine skipped', JSON.stringify({
1117
+ ticketId: created.id,
1118
+ provider: refineProvider ?? requestedAgentProvider,
1119
+ reason: 'provider-unsupported',
1120
+ }));
1121
+ broadcast({
1122
+ type: 'explore.contract_refine_failed',
1123
+ projectId: project.id,
1124
+ provider: refineProvider ?? requestedAgentProvider ?? 'unknown',
1125
+ ticketId: created.id,
1126
+ reason: 'provider-unsupported',
1127
+ timestamp: new Date().toISOString(),
1128
+ });
1087
1129
  }
1088
1130
  }
1089
1131
  }
@@ -1134,6 +1176,7 @@ function registerTicketsRoutes(deps) {
1134
1176
  const store = (0, ticket_store_1.mutateStore)(filePath, (s) => {
1135
1177
  const id = s.next_id++;
1136
1178
  const ticket = {
1179
+ ...repositoryScope(req, res),
1137
1180
  id,
1138
1181
  title,
1139
1182
  description,
@@ -1457,6 +1500,7 @@ function registerTicketsRoutes(deps) {
1457
1500
  const store = (0, ticket_store_1.mutateStore)(filePath, (s) => {
1458
1501
  const id = s.next_id++;
1459
1502
  const ticket = {
1503
+ ...repositoryScope(req, res),
1460
1504
  id,
1461
1505
  title: title.trim(),
1462
1506
  description: typeof description === 'string' ? description : '',
@@ -1532,6 +1576,7 @@ function registerTicketsRoutes(deps) {
1532
1576
  validationError = err;
1533
1577
  return;
1534
1578
  }
1579
+ Object.assign(ticket, repositoryScope(req, res));
1535
1580
  if (title !== undefined)
1536
1581
  ticket.title = title.trim();
1537
1582
  if (description !== undefined)
@@ -1,18 +1,28 @@
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.resolveDefaultSpecModel = exports.formatDescriptionWithCriteria = exports.lightlyStructurePrompt = exports.deriveFallbackShortSummary = exports.extractShortSummary = exports.stripSpecMetadataSections = void 0;
4
7
  exports.createProjectRouter = createProjectRouter;
5
8
  const express_1 = require("express");
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const project_repositories_1 = require("./project-repositories");
11
+ const file_summary_manager_1 = require("./file-summary-manager");
12
+ const project_code_discovery_1 = require("./project-code-discovery");
13
+ const feature_flags_1 = require("./feature-flags");
6
14
  const hooks_1 = require("./hooks");
7
15
  const rails_router_1 = require("./rails-router");
8
16
  const profiles_router_1 = require("./profiles-router");
9
17
  const plugins_router_1 = require("./plugins-router");
10
18
  const code_explorer_router_1 = require("./code-explorer-router");
19
+ const code_activity_1 = require("./code-activity");
11
20
  const jira_router_1 = require("./jira-router");
12
21
  const ticket_store_1 = require("./ticket-store");
13
22
  const workspace_resolution_1 = require("./workspace-resolution");
14
23
  const workspace_manager_1 = require("./workspace-manager");
15
24
  const blueprint_render_1 = require("./blueprint-render");
25
+ const milestone_progress_1 = require("./milestone-progress");
16
26
  const blueprint_draft_parser_1 = require("./blueprint-draft-parser");
17
27
  const blueprint_spec_quality_1 = require("./blueprint-spec-quality");
18
28
  const file_story_manager_1 = require("./file-story-manager");
@@ -45,6 +55,16 @@ function createProjectRouter(registry) {
45
55
  const projectId = req.params.projectId;
46
56
  const ctx = registry.getContext(projectId);
47
57
  if (!ctx) {
58
+ if (registry.getProjectRow(projectId)) {
59
+ const failure = registry.listFailedProjects().find((entry) => entry.project.id === projectId);
60
+ res.setHeader('Retry-After', '2');
61
+ res.status(503).json({
62
+ error: 'project_unavailable',
63
+ projectId,
64
+ detail: failure?.error ?? 'Project is registered but its database is not available yet.',
65
+ });
66
+ return;
67
+ }
48
68
  res.status(404).json({ error: 'Project not found' });
49
69
  return;
50
70
  }
@@ -91,45 +111,115 @@ function createProjectRouter(registry) {
91
111
  const jiraRouter = (0, jira_router_1.createJiraRouter)();
92
112
  router.use('/:projectId/jira', jiraRouter);
93
113
  // Mount Code-Explorer router. FileSummaryManager comes from ProjectContext.
114
+ router.get('/:projectId/code/discover', async (req, res) => {
115
+ if (!(0, feature_flags_1.isCodeExplorerEnabled)()) {
116
+ res.status(404).json({ error: 'code_explorer_disabled' });
117
+ return;
118
+ }
119
+ try {
120
+ res.json(await (0, project_code_discovery_1.discoverProjectCode)(ctx(req).project, {
121
+ kind: String(req.query.kind ?? 'find'),
122
+ query: String(req.query.q ?? ''),
123
+ ...(req.query.limit === undefined ? {} : { limit: Number(req.query.limit) }),
124
+ ...(req.query.path === undefined ? {} : { path: String(req.query.path) }),
125
+ caseSensitive: req.query.caseSensitive === 'true',
126
+ }));
127
+ }
128
+ catch (err) {
129
+ res.status(400).json({ error: err.message });
130
+ }
131
+ });
132
+ // Activity reads recorded evidence across memberships; unlike source reads it
133
+ // can report unavailable roots explicitly without hiding the rest of a project.
134
+ router.get(['/:projectId/code/activity', '/:projectId/repositories/:repositoryId/code/activity'], async (req, res) => {
135
+ if (!(0, feature_flags_1.isCodeExplorerEnabled)()) {
136
+ res.status(404).json({ error: 'code_explorer_disabled' });
137
+ return;
138
+ }
139
+ try {
140
+ const context = ctx(req);
141
+ res.json(await (0, code_activity_1.listCodeActivity)(context.db, context.project, (0, code_activity_1.parseCodeActivityQuery)(req.query, req.params.repositoryId), code_explorer_router_1.filterCodeExplorerActivityPaths));
142
+ }
143
+ catch (err) {
144
+ res.status(err instanceof code_activity_1.CodeActivityQueryError ? err.status : 500).json({ error: err instanceof code_activity_1.CodeActivityQueryError ? err.message : 'activity_unavailable' });
145
+ }
146
+ });
94
147
  const codeRouterByCtx = new WeakMap();
95
- router.use('/:projectId/code', (req, res, next) => {
148
+ const storyManagerByCtx = new WeakMap();
149
+ router.use(['/:projectId/repositories/:repositoryId/code', '/:projectId/code'], (req, res, next) => {
96
150
  const projectCtx = ctx(req);
97
- const codeRouter = memoizedSubRouter(codeRouterByCtx, projectCtx, () => {
151
+ let repository;
152
+ try {
153
+ repository = (0, project_repositories_1.resolveProjectRepository)(projectCtx.project, req.params.repositoryId);
154
+ }
155
+ catch (err) {
156
+ res.status(404).json({ error: 'repository_not_found', detail: err.message });
157
+ return;
158
+ }
159
+ try {
160
+ if (!node_fs_1.default.statSync(repository.path).isDirectory())
161
+ throw new Error('not a directory');
162
+ }
163
+ catch {
164
+ res.status(503).json({ error: 'repository_unavailable', repositoryId: repository.id });
165
+ return;
166
+ }
167
+ let cache = codeRouterByCtx.get(projectCtx);
168
+ if (!cache) {
169
+ cache = new Map();
170
+ codeRouterByCtx.set(projectCtx, cache);
171
+ }
172
+ const members = (0, project_repositories_1.getProjectRepositories)(projectCtx.project);
173
+ for (const [id, cached] of cache) {
174
+ if (!members.some(member => member.id === id && member.path === cached.path)) {
175
+ cache.delete(id);
176
+ projectCtx.fileSummaryManager.detachWatcher(projectCtx.project.id, id);
177
+ }
178
+ }
179
+ let codeRouter = cache.get(repository.id)?.router;
180
+ if (!codeRouter) {
98
181
  // Construction-story contribution generator — per-ctx (memoized with the
99
182
  // router). Reuses the file-summary spawn skeleton with the story prompt,
100
183
  // and the SAME monthly budget setting + ai_invocations surface, so the
101
184
  // whole Code-section AI spend rides one budget (see file-story-manager).
102
185
  const storyAdapter = (0, providers_1.getAdapter)(projectCtx.project.provider ?? 'claude');
103
- const fileStoryManager = new file_story_manager_1.FileStoryManager({
104
- db: projectCtx.db,
105
- broadcast: projectCtx.broadcast,
106
- generate: (0, file_summary_generator_1.createFileSummaryGenerator)({
107
- adapter: storyAdapter,
108
- cwd: projectCtx.project.path,
109
- systemPrompt: file_story_manager_1.buildStorySystemPrompt,
110
- }),
111
- monthToDateSpend: (projectId) => {
112
- const row = projectCtx.db.prepare(`SELECT COALESCE(SUM(total_cost_usd), 0) AS total FROM ai_invocations
186
+ let fileStoryManager = storyManagerByCtx.get(projectCtx);
187
+ if (!fileStoryManager)
188
+ fileStoryManager = new file_story_manager_1.FileStoryManager({
189
+ db: projectCtx.db,
190
+ broadcast: projectCtx.broadcast,
191
+ scheduler: projectCtx.fileSummaryManager,
192
+ generate: (0, file_summary_generator_1.createFileSummaryGenerator)({
193
+ adapter: storyAdapter,
194
+ cwd: projectCtx.project.path,
195
+ systemPrompt: file_story_manager_1.buildStorySystemPrompt,
196
+ }),
197
+ monthToDateSpend: (projectId) => {
198
+ const row = projectCtx.db.prepare(`SELECT COALESCE(SUM(total_cost_usd), 0) AS total FROM ai_invocations
113
199
  WHERE project_id = ? AND surface = 'file-summary'
114
200
  AND started_at >= strftime('%Y-%m-01', 'now')`).get(projectId);
115
- return row?.total ?? 0;
116
- },
117
- monthlyBudgetUsd: () => {
118
- const raw = (0, desktop_db_1.getDesktopSetting)(projectCtx.desktopDb, 'summary_monthly_budget_usd');
119
- const n = parseFloat(raw ?? '5.00');
120
- return isNaN(n) ? 5.0 : n;
121
- },
122
- language: () => {
123
- const raw = (0, desktop_db_1.getDesktopSetting)(projectCtx.desktopDb, 'summary_language');
124
- return raw === 'es' ? 'es' : 'en';
125
- },
126
- providerId: () => storyAdapter.id,
127
- getTicketSpec: projectCtx.getTicketSpec,
128
- });
129
- return (0, code_explorer_router_1.createCodeExplorerRouter)({
201
+ return row?.total ?? 0;
202
+ },
203
+ monthlyBudgetUsd: () => {
204
+ const raw = (0, desktop_db_1.getDesktopSetting)(projectCtx.desktopDb, 'summary_monthly_budget_usd');
205
+ const n = parseFloat(raw ?? '5.00');
206
+ return isNaN(n) ? 5.0 : n;
207
+ },
208
+ language: () => {
209
+ const raw = (0, desktop_db_1.getDesktopSetting)(projectCtx.desktopDb, 'summary_language');
210
+ return raw === 'es' ? 'es' : 'en';
211
+ },
212
+ providerId: () => storyAdapter.id,
213
+ getTicketSpec: projectCtx.getTicketSpec,
214
+ });
215
+ storyManagerByCtx.set(projectCtx, fileStoryManager);
216
+ codeRouter = (0, code_explorer_router_1.createCodeExplorerRouter)({
130
217
  db: projectCtx.db,
131
- projectPath: projectCtx.project.path,
218
+ projectPath: repository.path,
132
219
  projectId: projectCtx.project.id,
220
+ repositoryId: repository.id,
221
+ repositoryName: repository.name,
222
+ includeLegacyProvenance: repository.isPrimary,
133
223
  broadcast: projectCtx.broadcast,
134
224
  fileSummaryManager: projectCtx.fileSummaryManager,
135
225
  getTicketSpec: projectCtx.getTicketSpec,
@@ -139,27 +229,123 @@ function createProjectRouter(registry) {
139
229
  // relocated (source tree still read from project.path). Resolved per-call.
140
230
  resolveSummaryRoot: () => {
141
231
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: projectCtx.project.slug, path: projectCtx.project.path });
142
- return exec.relocated && exec.workspaceDir ? exec.workspaceDir : projectCtx.project.path;
232
+ const artifactRoot = exec.relocated && exec.workspaceDir ? exec.workspaceDir : projectCtx.project.path;
233
+ return (0, file_summary_manager_1.repositorySummaryRoot)(artifactRoot, repository);
143
234
  },
144
235
  });
145
- });
236
+ cache.set(repository.id, { path: repository.path, router: codeRouter });
237
+ }
146
238
  codeRouter(req, res, next);
147
239
  });
148
240
  // Project Builder sidebar re-entry (add-project-builder D5): read the
149
241
  // workspace-resident blueprint pair's source of truth. 404 when the project
150
242
  // has no blueprint (every non-Builder project) — the sidebar entry hides.
151
243
  router.get('/:projectId/blueprint', (req, res) => {
152
- const project = ctx(req).project;
153
- const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
154
- const workspace = exec.relocated && exec.workspaceDir
155
- ? exec.workspaceDir
156
- : (project.slug ? (0, workspace_manager_1.workspacePathFor)(project.slug) : null);
157
- const blueprint = workspace ? (0, blueprint_render_1.readBlueprint)(workspace) : null;
158
- if (!blueprint) {
244
+ const projectCtx = ctx(req);
245
+ const project = projectCtx.project;
246
+ // `progress` is the server-derived live milestone model (premium-milestone-
247
+ // progress): counts by spec state, rails, chain, derived state — the same
248
+ // payload `blueprint.milestone_progress` re-broadcasts on every mutation.
249
+ const snapshot = (0, milestone_progress_1.readMilestoneProgress)({
250
+ db: projectCtx.db,
251
+ projectId: project.id,
252
+ workspaceDir: () => (0, milestone_progress_1.resolveBlueprintWorkspace)({ slug: project.slug, path: project.path }),
253
+ ticketsPath: () => ticketPath(req),
254
+ railJobs: projectCtx.railJobs,
255
+ railLoopRuns: projectCtx.railLoopRuns,
256
+ chains: projectCtx.milestoneChains ? () => projectCtx.milestoneChains.listActive() : undefined,
257
+ });
258
+ if (!snapshot) {
159
259
  res.status(404).json({ error: 'no blueprint for this project' });
160
260
  return;
161
261
  }
162
- res.json({ blueprint });
262
+ res.json({ blueprint: snapshot.blueprint, progress: snapshot.progress });
263
+ });
264
+ // Milestone launch chain (premium-milestone-progress D3): "Launch Milestone
265
+ // N" is server-owned — chunked into ≤3-spec rails, launched through the
266
+ // ordinary rails launch route, and (sequential) chained on each chunk's
267
+ // delivered branch. The client keeps NO plan in browser storage.
268
+ router.post('/:projectId/blueprint/milestones/:n/launch', async (req, res) => {
269
+ const projectCtx = ctx(req);
270
+ const n = Number.parseInt(String(req.params.n), 10);
271
+ if (!Number.isInteger(n) || n < 1 || n > 999) {
272
+ res.status(400).json({ error: 'invalid_milestone' });
273
+ return;
274
+ }
275
+ const rawMode = (req.body ?? {}).mode;
276
+ if (rawMode !== undefined && rawMode !== 'sequential' && rawMode !== 'parallel') {
277
+ res.status(400).json({ error: "mode must be 'sequential' or 'parallel'" });
278
+ return;
279
+ }
280
+ const rawAuto = (req.body ?? {}).autoAdvance;
281
+ if (rawAuto !== undefined && typeof rawAuto !== 'boolean') {
282
+ res.status(400).json({ error: 'autoAdvance must be a boolean' });
283
+ return;
284
+ }
285
+ const chains = projectCtx.milestoneChains;
286
+ if (!chains) {
287
+ res.status(503).json({ error: 'milestone_chain_unavailable' });
288
+ return;
289
+ }
290
+ try {
291
+ const result = await chains.start(n, rawMode ?? 'sequential', { autoAdvance: rawAuto });
292
+ if (result.ok) {
293
+ res.status(202).json({ chainId: result.chainId, launched: result.launched, pending: result.pending });
294
+ }
295
+ else {
296
+ res.status(result.status).json({ error: result.error, ...(result.detail ? { detail: result.detail } : {}), ...(result.chainId ? { chainId: result.chainId } : {}) });
297
+ }
298
+ }
299
+ catch (err) {
300
+ console.error('[project-router] milestone launch error:', err);
301
+ res.status(500).json({ error: 'Failed to launch the milestone' });
302
+ }
303
+ });
304
+ router.post('/:projectId/blueprint/chains/:id/resume', async (req, res) => {
305
+ const chains = ctx(req).milestoneChains;
306
+ if (!chains) {
307
+ res.status(503).json({ error: 'milestone_chain_unavailable' });
308
+ return;
309
+ }
310
+ try {
311
+ const result = await chains.resume(String(req.params.id));
312
+ res.status(result.status).json(result.ok ? { chain: result.chain } : { error: result.error, ...(result.detail ? { detail: result.detail } : {}) });
313
+ }
314
+ catch (err) {
315
+ console.error('[project-router] chain resume error:', err);
316
+ res.status(500).json({ error: 'Failed to resume the chain' });
317
+ }
318
+ });
319
+ // Wave checkpoints (D9): flip auto-advance on a live chain; turning it on
320
+ // at a checkpoint launches the next chunk right away.
321
+ router.patch('/:projectId/blueprint/chains/:id', async (req, res) => {
322
+ const chains = ctx(req).milestoneChains;
323
+ if (!chains) {
324
+ res.status(503).json({ error: 'milestone_chain_unavailable' });
325
+ return;
326
+ }
327
+ const autoAdvance = (req.body ?? {}).autoAdvance;
328
+ if (typeof autoAdvance !== 'boolean') {
329
+ res.status(400).json({ error: 'autoAdvance must be a boolean' });
330
+ return;
331
+ }
332
+ try {
333
+ const result = await chains.setAutoAdvance(String(req.params.id), autoAdvance);
334
+ res.status(result.status).json(result.ok ? { chain: result.chain } : { error: result.error, ...(result.detail ? { detail: result.detail } : {}) });
335
+ }
336
+ catch (err) {
337
+ console.error('[project-router] chain patch error:', err);
338
+ res.status(500).json({ error: 'Failed to update the chain' });
339
+ }
340
+ });
341
+ router.post('/:projectId/blueprint/chains/:id/cancel', (req, res) => {
342
+ const chains = ctx(req).milestoneChains;
343
+ if (!chains) {
344
+ res.status(503).json({ error: 'milestone_chain_unavailable' });
345
+ return;
346
+ }
347
+ const result = chains.cancel(String(req.params.id));
348
+ res.status(result.status).json(result.ok ? { chain: result.chain } : { error: result.error, ...(result.detail ? { detail: result.detail } : {}) });
163
349
  });
164
350
  // Relocate-artifacts gate (single chokepoint for ALL project-router ticket
165
351
  // I/O): relocated ⇒ the registry entry's ticketsPath (workspace); legacy ⇒
@@ -190,6 +376,17 @@ function createProjectRouter(registry) {
190
376
  res.status(400).json({ error: 'specs must contain at least one titled spec' });
191
377
  return;
192
378
  }
379
+ let repositoryScopes;
380
+ try {
381
+ repositoryScopes = rawSpecs.map((spec) => (0, project_repositories_1.validateTicketRepositoryIds)(project, spec && typeof spec === 'object' ? spec.repositoryIds : undefined));
382
+ }
383
+ catch (err) {
384
+ if (err instanceof project_repositories_1.RepositoryValidationError) {
385
+ res.status(err.status).json({ error: err.message, code: err.code });
386
+ return;
387
+ }
388
+ throw err;
389
+ }
193
390
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
194
391
  const workspace = exec.relocated && exec.workspaceDir
195
392
  ? exec.workspaceDir
@@ -230,6 +427,7 @@ function createProjectRouter(registry) {
230
427
  ? [indexToId.get(prerequisite)]
231
428
  : [];
232
429
  const ticket = {
430
+ ...(repositoryScopes[index] ? { repositoryIds: repositoryScopes[index] } : {}),
233
431
  id,
234
432
  title: spec.title,
235
433
  description: (0, project_router_helpers_1.formatDescriptionWithCriteria)(spec.description, spec.acceptanceCriteria),
@@ -257,6 +455,9 @@ function createProjectRouter(registry) {
257
455
  milestone.status = 'committed';
258
456
  milestone.ticketIds = created.map((t) => t.id);
259
457
  (0, blueprint_render_1.writeBlueprintPair)(workspace, blueprint);
458
+ // A blueprint now exists for sure — forget any "absent" memo so the
459
+ // ticket_created broadcasts below re-derive the milestone progress.
460
+ projectCtx.milestoneProgress?.invalidate();
260
461
  for (const ticket of created) {
261
462
  projectCtx.broadcast({
262
463
  type: 'ticket_created',