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
@@ -74,6 +74,28 @@ route).
74
74
 
75
75
  ## Day-0 chat (no project exists)
76
76
 
77
+ **Decision cards (`BuilderDecisionCard`).** The interview prose keeps
78
+ asking the user to type "surprise me" and, once the five dimensions are
79
+ decided, to "approve the blueprint" — and the hero composer's Surprise-me
80
+ button only exists before the first turn (`showSurpriseMe`). Each ask gets ONE
81
+ premium card in the thread (same glass shell as `AgentSpecDraftCard`: accent
82
+ header band, identity chip, CTA pill, `motion` enter/exit, reduced-motion
83
+ safe) in two modes. **Offer** — clickable, rendered after the newest SETTLED
84
+ Builder reply by a deterministic rule (no prose detection): `phase === 'chat'`,
85
+ not busy, no stream buffer, last message is the assistant's, snapshot not
86
+ `generating`; `surprise` while the readiness `blueprint` step is not `done`,
87
+ `approve` once it is `done` and no M1 spec exists yet (specs step pending with
88
+ count 0). One click sends the canonical prompt (`prompts.surpriseMe` /
89
+ `prompts.approve`) through `session.surpriseMe()` / `session.approveBlueprint()`
90
+ tagged with an **intent**. **Settled** — the user turn that carried the intent
91
+ renders as the non-clickable "Decision taken · time" card IN PLACE of the raw
92
+ prompt bubble, so the decision stays fixed in the thread; the intent is
93
+ PERSISTED on the message row (`blueprint_messages.intent`, desktop-db
94
+ migration 24; `POST /send { intent: 'surprise' | 'approve' }`, validated,
95
+ anything else dropped; `GET /conversations/:id` returns it) so it survives a
96
+ resume and a locale switch. i18n `builder:decisionCard.*` + `prompts.approve`
97
+ ×8.
98
+
77
99
  The provider selector excludes adapters that cannot enforce the blueprint
78
100
  generator's pure-output policy. A direct Kimi request is rejected before
79
101
  `BlueprintChatManager` spawns. This does not prevent the approved blueprint
@@ -106,9 +128,159 @@ from declaring Kimi among the new project's target providers.
106
128
  partial subset as the latest draft; `specsComplete: true` appears only on the
107
129
  complete set. If generation cannot finish, the prior non-complete proposal
108
130
  remains the last valid state and cannot be committed.
131
+ - **Lazy conversation row**: the client no longer POSTs a conversation when
132
+ the Builder opens. `useBuilderSession.ensureConversation()` creates the row
133
+ (with the composer's provider/model) on the FIRST send, single-flight, or
134
+ `resume()` rehydrates a persisted one — so opening and closing the Builder
135
+ never leaves empty orphan rows behind.
136
+
137
+ ## Snapshot hardening (harden-project-builder-snapshots)
138
+
139
+ > Why: a real run (WebTetris, 8 specs, `specsComplete: true` claimed by the
140
+ > model) ended with a greyed-out *Create specs* button reading "Generation is
141
+ > not complete yet." The 8-spec block had been REJECTED by `JSON.parse` and
142
+ > dropped **silently** — `continue // malformed; drop silently` — so the panel
143
+ > kept the interview snapshot (`m1Specs: []`), the model never learned its
144
+ > block was unusable ("no reemito un nuevo snapshot"), and because the
145
+ > snapshot lived only in client memory (persisted rows were STRIPPED of the
146
+ > JSON) the whole batch was lost when the panel closed. Two grave bugs with
147
+ > one root: the snapshot pipeline had no feedback loop and no durability.
148
+
149
+ **1. Nothing is dropped silently (parser).** `parseBlueprintDraftBlocks`
150
+ (server + client mirror) now returns `rejected: BlueprintRejectedBlock[]`
151
+ (`{index, reason: 'invalid_json'|'missing_version'|'truncated', detail}`),
152
+ `repaired` and `truncated` next to the existing fields. Before rejecting, every
153
+ block runs through `parseJsonTolerant` (`server/json-tolerant.ts` ⇄
154
+ `client/src/lib/json-tolerant.ts`): strict `JSON.parse` first, then ONE
155
+ string-aware repair pass for the mistakes a model makes when hand-writing a
156
+ ~20 KB payload — raw newlines/tabs/control chars inside strings, a stray inner
157
+ `"`, trailing commas, `//` / `/* */` comments outside strings, a nested
158
+ ```` ```json ```` fence, prose around the object, invalid `\x` escapes — and
159
+ parses again (`repaired: true`, repair kinds listed). An UNTERMINATED trailing
160
+ fence in a settled reply (the output limit cut the block) is now reported as
161
+ `truncated` (with the number of spec titles that had started, via
162
+ `countStartedSpecs`) and CUT from the transcript — raw partial JSON never
163
+ reaches the chat. The `invalid_json` detail carries the parser message plus a
164
+ ±40-char excerpt around the failing position so the model can fix it.
165
+
166
+ **2. The app tells the model (repair loop).** `BlueprintChatManager._runTurn`
167
+ audits every settled turn: `planSnapshotRepair(parse)` returns a repair
168
+ request when (a) no snapshot was accepted but a block was emitted (kind
169
+ `invalid_json` / `truncated`), or (b) the accepted raw snapshot declares
170
+ `specsComplete: true` and the deterministic M1 gate
171
+ (`auditRawBlueprintForM1`, the same `analyzeBuilderSpecBatch` the commit runs)
172
+ disagrees (kind `quality`, detail = one line per issue). Then it runs **ONE
173
+ automatic repair turn** on the SAME session (`--resume`, so the model has its
174
+ own context) with a byte-stable prompt from `blueprint-operator-prompt.ts`
175
+ `buildSnapshotRepairPrompt(kind, detail)` — "APP CHECK: … re-emit the
176
+ COMPLETE snapshot … reply with ONLY the block" (the `truncated` variant asks
177
+ to tighten descriptions so the block fits). `blueprint.repairing
178
+ {kind, attempt, manual}` is broadcast first; the repair turn streams like any
179
+ turn; ONE `blueprint.done` follows. Outcomes: repaired block accepted ⇒
180
+ `snapshot.status='accepted', repaired: true, repairAttempted: true`; still
181
+ unusable ⇒ `status='rejected'` with the freshest diagnostic (never
182
+ `blueprint.error` — the user's turn succeeded); a quality repair that still
183
+ fails the gate ⇒ the snapshot is delivered WITH `qualityIssues` so the UI can
184
+ list them. Bounded: a repair never nests another; no session id / provider
185
+ without `nativeResume` ⇒ the rejection is reported without a repair. Every
186
+ spawn records its own `agent_invocations` row (a repair bills too). Manual
187
+ repair: `POST /api/blueprint/conversations/:id/repair-snapshot` →
188
+ `BlueprintChatManager.repairSnapshot(id)` decides what to ask from the
189
+ PERSISTED state (pending rejection ⇒ JSON/truncated prompt; claimed-complete
190
+ snapshot failing the gate ⇒ quality prompt) so it works after a restart; 202
191
+ `{kind}` / 409 `streaming|nothing_to_repair|no_session` / 404. The operator
192
+ prompts (both `BUILDER_INSTRUCTIONS` and `BUILDER_SYSTEM_PROMPT`) gained the
193
+ JSON hygiene rules (escape `\n` / `\"`, no trailing commas, no nested
194
+ fences, nothing after the closing fence, "if the app reports a rejected or
195
+ cut-off block answer with ONLY the corrected snapshot").
196
+
197
+ **3. Durable snapshots (migration 23, `desktop.sqlite`).**
198
+ `blueprint_conversations` += `blueprint_json` (normalized accepted snapshot),
199
+ `raw_blueprint_json` (exact payload — the commit/readiness evidence),
200
+ `snapshot_updated_at`, `snapshot_issue_json` (the pending rejection until the
201
+ next accepted block), `committed_project_id` (set by the commit's
202
+ `markCommitted` IO hook right after `register`; the commit body accepts an
203
+ optional `conversationId`, unknown ids are ignored, a failing link never fails
204
+ the commit). `blueprint_messages` += `raw_content` — the model's UNSTRIPPED
205
+ reply whenever it carried a block (forensics; a later parser fix can re-read
206
+ an old rejected snapshot). A block-only reply persists `content=''` with the
207
+ raw payload; `GET /conversations/:id` hides empty rows and never exposes
208
+ `raw_content`. Store helpers: `saveBlueprintSnapshot`,
209
+ `saveBlueprintSnapshotIssue`, `getBlueprintSnapshot` (normalizes through
210
+ `coerceBlueprint`; corrupt JSON reads as null), `markBlueprintCommitted`,
211
+ `listResumableBlueprintConversations`.
212
+
213
+ **4. Resume ("Continue where you left off").** `GET
214
+ /api/blueprint/conversations?resumable=1` lists unfinished conversations
215
+ (never committed, ≥1 assistant reply) newest first with a snapshot summary
216
+ (`productName`, `platform`, `specCount`, `specsComplete`, `dimensionsFilled`,
217
+ `hasSnapshot`, `pendingIssue`, `messageCount`). `GET /conversations/:id` now
218
+ returns the transcript + `blueprint` + `rawBlueprint` + `snapshot`
219
+ (`accepted` with `claimsComplete`/`qualityIssues`, `rejected` with
220
+ reason/detail, or `none`) + `snapshotUpdatedAt`. Client:
221
+ `BuilderRecentBlueprints` (under the hero composer while the session is empty)
222
+ → `session.resume(id)` rehydrates messages, snapshot pair, provider/model and
223
+ the provider session (later turns `--resume`); two-step inline discard →
224
+ `DELETE`. The exit confirm copy no longer threatens to discard the blueprint —
225
+ it says where to pick it up.
226
+
227
+ **5. Readiness, made legible (client).** `client/src/lib/blueprint-readiness.ts`
228
+ `deriveReadiness` turns the same deterministic report into three steps —
229
+ **blueprint** (5 dimensions) · **specs** (count within 5–10 and
230
+ `specsComplete`) · **audit** (issues excluding the two batch-level codes) —
231
+ with structured params, and `localizeQualityIssue(t, issue)` maps every
232
+ `(field, code)` to a `builder:quality.*` key (`{{n}}`, `{{heading}}`,
233
+ `{{label}}`, `{{min}}`/`{{max}}`/`{{count}}`, `{{criterion}}`, `{{other}}`;
234
+ unknown codes fall back to the English message). Both quality analyzers
235
+ (server `analyzeBuilderSpecBatch` and the client mirror) now attach `params`
236
+ to each issue. `BlueprintReadiness` (`client/src/components/project-builder/`)
237
+ replaces the old CTA + raw-English hint in BOTH surfaces (floating panel side
238
+ pane and the Agent-Mode workspace sidebar) and in the M2+ shell: the three
239
+ steps, the snapshot status (repairing pill / rejected card with reason +
240
+ diagnostic + **Ask for the snapshot again** / "repaired automatically" note),
241
+ the audit issues per spec with **Ask the Builder to fix these** (shown only
242
+ when the model claimed completion — the only case the app can repair), and the
243
+ primary CTA whose disabled hint names the FIRST blocker in plain language.
244
+ `useBuilderSession` exposes `snapshot: BuilderSnapshotState`
245
+ (`idle|repairing|accepted|rejected`), `readiness`, `generation`, `recent`,
246
+ `resume`, `discardRecent`, `repairSnapshot`, `conversationId`; a block-only
247
+ `blueprint.done` (empty `fullText`) appends no bubble; a legacy `done` without
248
+ `snapshot` still parses the settled text. `BlueprintPanel` shows a
249
+ Complete / In progress pill on the M1 header and a pulse while repairing.
250
+
251
+ **6. Live generation progress.** While a block streams in (hidden by the tail
252
+ cut — previously a static "Thinking…" for up to a minute) `describeStreamingSnapshot`
253
+ counts the spec titles started inside the open fence and
254
+ `BuilderGenerationProgress` renders "Writing the Milestone-1 specs… · spec N"
255
+ with a soft progress bar (specs ÷ cap, capped at 95% until the block closes),
256
+ switching to the repair label during a repair turn. Used in the day-0 thread
257
+ and the M2+ shell.
258
+
259
+ **M2+ (`MilestoneGenerateShell`)** rides `ChatManager`, which has no
260
+ app-driven repair turn; it gets the tolerant parser + rejection diagnostics +
261
+ the same readiness surface for free, and its repair button sends a localized
262
+ user message (`builder:prompts.repairSnapshot`) instead.
263
+
264
+ **WS contract.** `blueprint.done` += `snapshot` (see `BlueprintDoneMessage`);
265
+ new `blueprint.repairing`. i18n: `builder` namespace gained `generation.*`,
266
+ `readiness.*`, `snapshot.*`, `recent.*`, `quality.*` (×8, parity-tested).
109
267
 
110
268
  ## blueprint-draft protocol
111
269
 
270
+ **Fence tolerance (bug fix, 2026-09-04).** Models mirror the schema example
271
+ and Sonnet emitted the whole snapshot inside a ```` ```json ```` fence: the
272
+ parsers only knew ```` ```blueprint-draft ````, so the raw JSON landed in the
273
+ chat, no snapshot was accepted (no rejection either) and the panel stayed at
274
+ 0/5 dimensions. Two-sided fix: the prompt's example is now fenced
275
+ `blueprint-draft` with an explicit rule ("the fence language is EXACTLY
276
+ `blueprint-draft` — a json or bare fence is NOT a snapshot"), and both parsers
277
+ run `promoteJsonBlueprintFences` first — a CLOSED ```` ```json ```` / bare
278
+ fence whose body is a `{…}` object with an integer `blueprintVersion` is
279
+ promoted to a blueprint-draft block (outside proper blocks only; ordinary json
280
+ fences and invalid payloads are untouched), and an OPEN one is reported
281
+ `truncated` / hidden from the live stream (`cutUnterminatedBlock`,
282
+ `describeStreamingSnapshot`).
283
+
112
284
  Fenced ` ```blueprint-draft ` JSON blocks. FULL snapshots, LAST syntactically
113
285
  valid block wins, streaming tail cut (unterminated trailing fence never
114
286
  parsed/shown — `cutUnterminatedBlock`). Unknown keys dropped; missing or
@@ -145,7 +317,14 @@ files remain readable (and returns null for missing/corrupt input).
145
317
  ## Canonical rich-spec contract (M1 and generated M2+)
146
318
 
147
319
  There is no Builder-specific “lite spec” format. Every detailed Builder spec
148
- uses the normal Specrails contract:
320
+ uses the normal Specrails contract, and since premium-milestone-progress the
321
+ prose that teaches it lives in ONE module — `server/spec-contract-prompt.ts`
322
+ (`premiumSpecContract(mode)`, `premiumSpecContractCompact(mode)`,
323
+ `PREMIUM_SCAFFOLD_EXAMPLE`, `SPEC_DEPTH_FLOORS`) — consumed by the Builder
324
+ operator prompt, `ChatManager._buildMilestoneSystemPrompt` (M2+) and the
325
+ agent's super-spec section, so the three authors never drift. `mode` selects
326
+ the grounding hook: `day0` (no repo — every module/path/route is labelled
327
+ *planned*) vs `verified` (only paths that were inspected).
149
328
 
150
329
  1. `kind`: `scaffold`, `feature`, or `verification`
151
330
  2. an English, action-oriented, unique `title`
@@ -153,28 +332,113 @@ uses the normal Specrails contract:
153
332
  4. `description` with exactly these `##` headings, once and in this order:
154
333
  `Problem Statement`, `Proposed Solution`, `Out of Scope`,
155
334
  `Technical Considerations`, `Estimated Complexity`
156
- 5. a separate `acceptanceCriteria[]` containing 4–10 non-empty, independently
157
- testable outcomes; `description` MUST NOT contain `## Acceptance Criteria`
335
+ 5. a separate `acceptanceCriteria[]` containing **6–10** independently
336
+ testable outcomes (each ≥ 20 chars, covering the happy path, at least one
337
+ failure/edge case and an automated verification); `description` MUST NOT
338
+ contain `## Acceptance Criteria`
158
339
  6. a catalog-valid `priority`, non-empty domain labels, and an optional
159
340
  `dependsOnIndex` that points strictly backward (the M1 scaffold omits it)
160
341
 
161
- Every named description section has a non-empty body. `Out of Scope` and
162
- `Technical Considerations` each contain at least 2 bullets; Estimated
163
- Complexity includes a reasoned estimate. Day-0 technical considerations name
164
- the selected stack, planned components/contracts, risks, and inter-spec
165
- dependencies but never fabricate repository paths. Generated M2+ specs first
166
- inspect the real project and may name only verified existing paths and
167
- identifiers; their criteria cover behavior, failure/edge cases, and tests.
342
+ **Depth floors (`SPEC_DEPTH_FLOORS`, enforced by the deterministic gate on
343
+ both sides):** Problem Statement ≥ 200 chars of narrative (who, what breaks
344
+ today, why now, what good looks like); Proposed Solution ≥ 500 chars — a
345
+ numbered user journey followed by the five `###` sub-blocks *User
346
+ experience · Data model · Interfaces & contracts · Planned modules · Key
347
+ decisions*; Out of Scope ≥ 3 bullets, each naming WHERE the exclusion goes
348
+ (a later milestone / never); Technical Considerations ≥ 5 labelled bullets
349
+ (**Architecture**, **Data & contracts**, **Failure handling & edge cases**,
350
+ **Security & privacy**, **Testing strategy**, **Dependencies**, **Risks &
351
+ mitigations**); Estimated Complexity = a reasoned estimate naming the main
352
+ uncertainty. `PREMIUM_SCAFFOLD_EXAMPLE` is the mandatory first spec written
353
+ at that depth; "shorter is a defect, not a style". The floors were raised
354
+ because the old minima ("at least two bullets", 4–10 criteria) became the
355
+ ceiling the model aimed at (design D6/D8). Test fixtures that need a
356
+ gate-valid spec use `server/blueprint-spec-fixtures.ts` ⇄
357
+ `client/src/lib/__tests__/premium-spec-fixture.ts`.
168
358
 
169
359
  `server/blueprint-spec-quality.ts` is the shared deterministic authority. It
170
360
  validates `specsComplete=true`, the complete-set size, all fields/sections
171
- above (including both 2-bullet minima), unique titles, the M1 first-item
172
- `kind='scaffold'` rule, and backward-only dependencies. Both commit paths run
173
- it before any filesystem, registry, blueprint, milestone, or ticket-store
174
- mutation and return a stable spec/field-oriented detail when it rejects. A
175
- prompt is a generation aid, never the integrity boundary. Validation receives
176
- the exact raw generated payload; normalized compatibility views are not
177
- commit evidence.
361
+ above (including the depth floors — issue codes `section_depth` carries the
362
+ heading + min chars, `section_bullets` the min, `criteria_count` the 6–10
363
+ bounds, `criterion_short` the 20-char floor), unique titles, the M1
364
+ first-item `kind='scaffold'` rule, and backward-only dependencies. Both
365
+ commit paths run it before any filesystem, registry, blueprint, milestone,
366
+ or ticket-store mutation and return a stable spec/field-oriented detail when
367
+ it rejects. A prompt is a generation aid, never the integrity boundary.
368
+ Validation receives the exact raw generated payload; normalized
369
+ compatibility views are not commit evidence.
370
+
371
+ ### App-driven batched generation (premium-milestone-progress D7)
372
+
373
+ "Emit all 5–10 specs complete in ONE reply" capped every spec's depth by the
374
+ output budget (ten premium specs do not fit) and the old `truncated` repair
375
+ told the model to *tighten* — institutionalising thin specs. Generation is
376
+ now driven by the app, in turns on the SAME session (`server/blueprint-
377
+ generation.ts` + the drive closure in `BlueprintChatManager._runTurn`):
378
+
379
+ 1. **Outline.** After approval the Builder emits ONE `blueprint-draft` FULL
380
+ snapshot with every spec's `kind/title/shortSummary/priority/labels/
381
+ dependsOnIndex` decided but `description: ""`, `acceptanceCriteria: []`,
382
+ `specsComplete: false`. `isOutlineSnapshot` (≥ `M1_SPECS_MIN` specs, every
383
+ body empty) on a resumable session (`capabilities.nativeResume` + a session
384
+ id) starts the drive; the outline is broadcast as the first
385
+ `blueprint.done { continuing: true }` frame so the panel lists the specs
386
+ immediately.
387
+ 2. **Detail turns.** `APP CONTINUE` names `SPECS_PER_DETAIL_TURN = 2` specs
388
+ by index + title; the model answers with one fenced `spec-detail` block
389
+ per spec — `{ "index": n, "spec": { …complete premium spec… } }` — and
390
+ nothing else. `parseGenerationBlocks` extracts/strips them (tolerant JSON,
391
+ `truncated` on an open fence), `mergeSpecDetails` merges by index (an
392
+ omitted key keeps the outline's value, out-of-range indexes are ignored).
393
+ A range that is still unfilled gets ONE `APP CHECK` re-ask
394
+ (`buildDetailRepairPrompt`); still unfilled ⇒ the drive **halts**
395
+ (`blueprint.done { snapshot.generationHalted: true }`, `specsComplete`
396
+ forced false, the outline/partial snapshot persisted — nothing is lost).
397
+ 3. **Audit turn.** `APP AUDIT` asks for one `spec-audit` block
398
+ `{ specsComplete, issues[], fixes[{ index, spec }] }`. Fixes merge like
399
+ details; a verdict with zero fixes still applies (`specsComplete`). A
400
+ verdict of `false` WITH issues gets ONE corrections turn
401
+ (`buildAuditIssuesPrompt` — `spec-detail` blocks for the affected specs
402
+ only); a reply without any block lets the deterministic gate judge.
403
+ 4. **Quality repair.** The pre-existing repair tail runs unchanged after the
404
+ audit (`planSnapshotRepair` → `quality` when the model claims complete and
405
+ the gate disagrees); the repair reply may now be `spec-detail` patches
406
+ instead of a whole snapshot (`applyReply`).
407
+
408
+ Bounds: `MAX_GENERATION_TURNS = 8` (outline + 5 detail turns for 10 specs +
409
+ audit + one repair); every turn persists its snapshot (`saveBlueprintSnapshot`)
410
+ and records its own `agent_invocations` row; generation fences are stripped
411
+ from the transcript exactly like `blueprint-draft` blocks (the raw reply
412
+ survives in `raw_content`). Wire: `blueprint.generating { phase:
413
+ outline|details|audit|repair, from, to, total, turn, totalTurns }` announces
414
+ each phase; every intermediate `blueprint.done` carries `continuing: true` +
415
+ `snapshot.generation` (the client keeps `busy`, appends no bubble, refreshes
416
+ the panel); the final frame carries the descriptor without `continuing`.
417
+ **Resume:** `POST /conversations/:id/repair-snapshot` now also resumes a
418
+ halted drive — when no rejection is pending and the persisted snapshot still
419
+ has unfilled specs it answers `202 { kind: 'resume' }` and continues from
420
+ the next unfilled range (turn ordinal re-derived from what is already
421
+ written), which the readiness panel exposes as **Continue generating**
422
+ (`snapshot-halted` notice: "N of M specs written in full"). Providers without
423
+ `nativeResume` get the `GENERATION MODE: single response` line appended to
424
+ each user turn and keep the single-snapshot behaviour (never driven).
425
+
426
+ Client: `BuilderSnapshotState` gains `{ status: 'generating', generation }`
427
+ and `accepted.generationHalted`; `BuilderGenerationProgress` renders the
428
+ phase label ("Writing specs 3–4 of 8 in full…"), a `turn x/y` pill and a REAL
429
+ ratio from the descriptor (falls back to the streaming spec count for
430
+ single-response providers); `BuilderConversation` keeps that progress chip
431
+ between batched turns instead of the generic thinking chip; readiness
432
+ `specs` step params carry `written` (specs with a body). **Honest audit
433
+ during the drive:** an outline's empty bodies are NOT audit failures — while
434
+ `snapshot.status === 'generating'` `deriveReadiness(…, { generating })` puts
435
+ the specs + audit steps in the `writing` state ("2 of 8 written" / "after the
436
+ specs are written", spinner, batch hint) and lists NO issues; a halted partial
437
+ batch keeps only the issues of WRITTEN specs (the unwritten tail simply waits);
438
+ the panel's spec card shows "writing…" / "not written yet" instead of
439
+ "0 acceptance criteria" for an unwritten entry. i18n `builder:generation.*`,
440
+ `builder:snapshot.halted.*`, `builder:readiness.*.writing`,
441
+ `builder:panel.specWriting|specPendingBody` ×8.
178
442
 
179
443
  ## Orchestrated commit (register-project-LAST)
180
444
 
@@ -244,24 +508,120 @@ day-0 blueprint generation. Kimi cannot be selected for that generation turn.
244
508
  Launching the already committed M1/M2+ tickets is ordinary Batch rail
245
509
  execution and can use Kimi.
246
510
 
247
- - **Launch Milestone 1** (`client/src/lib/milestone-launch.ts`
248
- `launchMilestone`): gather `M1`-labeled `todo` tickets → chunk into groups
249
- of ≤ `MAX_TICKETS_PER_RAIL` (3) → per chunk: `POST /rails` (server allocates
250
- the lowest free index; rails named `M1 · <k>` when the milestone needs more
251
- than one) → `PUT /rails/:i/tickets` → `POST /rails/:i/launch
252
- {mode:'batch-implement'}` (the server maps the bare mode to the batch
253
- factory loop: worktree isolation + ask-first PR). The launch route rejects
254
- any rail carrying more than 3 specs (`rail_ticket_cap_exceeded`), so the cap
255
- holds for every launch door. A failure before anything launched surfaces as
256
- a typed reason; a mid-batch failure keeps the launched rails and reports the
257
- skipped rest (`skippedCount` → partial-launch toast). Offered on the Builder
258
- done screen and the sidebar entry; existing 409 guards surface as toasts.
511
+ - **Launch Milestone N is SERVER-owned** (premium-milestone-progress D3):
512
+ `POST /api/projects/:id/blueprint/milestones/:n/launch { mode }`
513
+ (`client/src/lib/milestone-launch.ts` `launchMilestone` is one POST; the
514
+ old browser-local `MilestoneSequencerContext` + its `localStorage` plan are
515
+ GONE — `dropLegacySequentialPlans()` forgets the leftover key on load).
516
+ `server/milestone-chain.ts` `MilestoneChainManager` gathers the `M<n>`
517
+ `todo` tickets, chunks them (≤3, `chainRailName` → `M<n>` / `M<n> · k`),
518
+ persists ONE `milestone_launch_chains` row (`server/milestone-chain-store.ts`,
519
+ migration 58, partial unique index = one non-terminal chain per milestone;
520
+ CAS `updateChain`) and launches chunk 1 through the app's OWN rails launch
521
+ route over loopback (`server/internal-api.ts`, lifted from the MCP tools'
522
+ `apiCall`) so every existing guard applies and each 4xx becomes a typed
523
+ `pause_reason` (`launch_rejected:<error>`). **Sequential (default)** chains
524
+ the next chunk when the in-flight chunk's DELIVERY settles — the manager taps
525
+ the project's bound broadcast for `rail.pr_state` (the engine's
526
+ `onLoopRunFinished` fires BEFORE the delivery row leaves `building`, so it is
527
+ only the delivery-less shared-cwd fallback, recording `last_run_outcome` to
528
+ name the pause reason) — and **STACKS** it: chunk k+1 launches with
529
+ `baseBranch = chunk k's delivered branch` (the rails launch route's new
530
+ `baseBranch` param → `resolveIntegrationBranch({ explicit })`, recorded as
531
+ the delivery's `base_branch`, so a walking skeleton accumulates without
532
+ waiting for a merge). `no_changes` keeps the previous head; failure /
533
+ stall / stop / launch refusal / missing head / discarded head / lost run
534
+ PAUSE the chain (`chunk_failed | chunk_stalled | chunk_stopped |
535
+ launch_rejected:<e> | head_missing | head_discarded | run_lost`) — never
536
+ skip ahead. `POST …/blueprint/chains/:id/resume` **retries the chunk that failed**
537
+ (the row remembers it in `retry_chunk`, set by every chunk-failure pause:
538
+ `chunk_failed | chunk_stalled | chunk_stopped | provider_limit | run_lost`;
539
+ `launch_rejected` / `head_missing` keep launching the NEXT chunk) from the
540
+ current head (409 `head_missing` when the branch is gone) — run 10dedd5a
541
+ showed the old resume skipping to tickets 4–6 while 1–3 had failed. The
542
+ retry REUSES the failed attempt's rail when no undecided delivery sits on
543
+ it any more (`activeDeliveryForRail` null — e.g. after Discard), else takes
544
+ a fresh rail so the failed delivery stays reviewable; the launched entry
545
+ for that chunk is replaced (delivery rows keep the history). A NEW chain for
546
+ the same milestone likewise reuses a rail already named for the chunk
547
+ (`io.findRailByName` → `rails-store` `getRails`) when it holds no undecided
548
+ delivery, so relaunching M1 never piles up duplicate "M1 · 1" rails;
549
+ `…/cancel` stops the chain and leaves in-flight rails alone. Startup
550
+ recovery (`recoverOnStartup`, run once the HTTP server listens) replays a
551
+ chunk that settled while the server was down exactly once.
552
+ **Wave checkpoints (D9).** The row carries `auto_advance` (default 1 for
553
+ API callers; the UI sends the user's stored preference
554
+ `localStorage['specrails-desktop:milestone-auto-advance']`, default OFF)
555
+ and a non-terminal status `awaiting_approval`. When a chunk's delivery
556
+ settles successfully and auto-advance is off (and chunks remain), the
557
+ manager records the head and parks the chain at `awaiting_approval`
558
+ (`afterChunkSuccess`) instead of launching — a HEALTHY decision point, unlike
559
+ `paused` whose Resume retries the SAME chunk. `…/chains/:id/resume` launches
560
+ the NEXT chunk from `awaiting_approval` too; `PATCH …/chains/:id
561
+ { autoAdvance }` (`setAutoAdvance`) flips the flag on any active chain and,
562
+ when turning it on at a checkpoint, launches immediately. `awaiting_approval`
563
+ counts as active (one non-terminal chain per milestone, cancellable) and is
564
+ ignored by startup recovery (waiting for the user is the point); a failure
565
+ always PAUSES regardless of the flag (checkpoints are reached only by
566
+ success). Client: `launchMilestone(projectId, n, mode, { autoAdvance })`,
567
+ `setChainAutoAdvance`, `readMilestoneAutoAdvance`/`saveMilestoneAutoAdvance`
568
+ (`milestone-launch.ts`); `chainAtCheckpoint` + `isMilestoneLaunchable`
569
+ excludes a checkpoint (`milestone-progress.ts`). **Parallel**
570
+ launches every chunk at once from the integration branch (row recorded
571
+ `completed` so the progress model still orders the rails). Kill switch
572
+ `SPECRAILS_MILESTONE_CHAIN=false` ⇒ parallel, no row. Merging a STACKED
573
+ chunk sweeps its merged ancestors (`sweepMergedChainAncestors` in
574
+ `rail-pr-decision.ts`: chain-local, `git merge-base --is-ancestor`, same
575
+ CAS + ticket effect + Jira hook) and merge-local integrates into the CHAIN's
576
+ integration branch, never the feature base (`mergeLocalTargetBranch`);
577
+ discarding a delivery a later chunk was built on pauses its chain
578
+ (`pauseChainsForDiscardedHead`) and the decision surfaces warn first
579
+ (`discardStackedNote` ×3 namespaces). Offered on the Builder done screen and
580
+ the sidebar entry with the Sequential | Parallel toggle.
581
+ - **Milestone progress is SERVER-derived** (premium-milestone-progress D2):
582
+ `server/milestone-progress.ts` `deriveMilestoneProgress` builds, per
583
+ milestone, counts by spec state (`total/done/onReview/inProgress/todo/failed`
584
+ — `failed` = specs back at `todo` whose NEWEST delivery unit failed), the
585
+ milestone's rails (active runs + non-terminal deliveries, chunk-ordered), the
586
+ chain snapshot and a derived `state`
587
+ (`done` ⇐ every spec done · `delivered` ⇐ nothing pending, ≥1 on_review ·
588
+ `running` ⇐ in-progress or a live chain · `committed` · stored status).
589
+ `GET /:id/blueprint` returns `{ blueprint, progress }`;
590
+ `MilestoneProgressBroadcaster` (tapped from the bound broadcast on
591
+ ticket/rail/delivery/run/chain messages, 150 ms debounce, memoized "no
592
+ blueprint") re-broadcasts `blueprint.milestone_progress` and persists
593
+ `status:'done'` once via `markMilestoneDone` (+ `blueprint.milestone_completed`).
594
+ Display ALWAYS uses the derived state — a delivered milestone reads
595
+ "8 of 8 delivered · 0 done", never done/complete.
259
596
  - **Sidebar re-entry** (`BuilderSidebarEntry`, mounted in
260
597
  `ProjectRightSidebar` + `AgentWorkspaceSidebar`): visible iff
261
- `GET /api/projects/:id/blueprint` (project-router) returns a blueprint
262
- (404 = hidden). Progress derives LIVE from board tickets by `M<n>` label —
263
- never from stored ticket ids. Actions: Launch M1 (while launchable) +
264
- Generate M<next> (first `planned` milestone > 1).
598
+ `GET /api/projects/:id/blueprint` returns a blueprint (404 = hidden).
599
+ Reads `useMilestoneProgress(projectId)` (cached per project, live over WS —
600
+ NO board fetch on open) and renders one `MilestoneCard`
601
+ (`MilestoneProgressCard.tsx`: segmented bar done/in review/in progress/
602
+ failed/pending, honest counts, state pill, per-rail rows with decision pill +
603
+ elapsed + Review → `/review/:prDeliveryId`, chain row with k of n / waiting /
604
+ paused reason + Resume / Cancel, and at a wave checkpoint "Rail k of n
605
+ delivered — launch rail k+1?" with **Launch next rail** + the chain-level
606
+ **Continue automatically** switch (`MilestoneAutoAdvanceToggle`, PATCH; also
607
+ saves the preference) + Cancel; a running auto-off chain notes "stops after
608
+ this rail") per milestone in a 320 px flyout. Actions: Launch M1 (while
609
+ `isMilestoneLaunchable`) with the mode toggle and, for sequential mode, the
610
+ same auto-continue switch (stored preference, default OFF — the launch toast
611
+ then says "you'll be asked before each next rail") + Generate M<next> (first
612
+ `planned` milestone > 1). The Builder done screen shows the same live card
613
+ after Launch (`BuilderDoneMilestone`) and the auto-continue switch next to
614
+ Launch M1 — "Open the project" stays the exit. **Review in Mission mode:**
615
+ every Review button navigates to `/review/:prDeliveryId`, a Board ROUTE
616
+ that Mission mode never renders (the button "did nothing"); the route is
617
+ now a third `modalize` surface (`global-route-mode-transition.ts`
618
+ `reviewDeliveryIdForPath`) — `App.tsx` opens the SAME `ReviewPacketPage`
619
+ embedded in a mission modal (`prDeliveryId` + `onClose` props, no New
620
+ Mission reset), and switching back to Board routes to the page. App-level toasts
621
+ (`useMilestoneNotifications`): later chunk launched, chain paused
622
+ (+ Resume), wave checkpoint (once per rail: **Launch next rail** +
623
+ **Auto-continue** actions), milestone delivered (+ Review), milestone
624
+ complete. i18n `builder:milestoneProgress.chain.*` / `toast.*` ×8.
265
625
  - **Board classification**: new Builder tickets use
266
626
  `source='project-builder'` + `created_by='project-builder'`. For projects
267
627
  created before that source existed, `DashboardPage` also treats
@@ -305,23 +665,25 @@ execution and can use Kimi.
305
665
  retain priority, short summary, domain + `M<n>` labels, and prerequisites.
306
666
  `blueprint.json` deliberately stores only the existing milestone skeleton
307
667
  with `status='committed'` and advisory `ticketIds`; it has no detailed-M2-
308
- per-milestone schema. On 201 the shell calls `onCommitted`, increments the
309
- sidebar blueprint refresh key (no-store refetch), closes, and the next CTA
668
+ per-milestone schema. On 201 the shell calls `onCommitted`, refreshes the live
669
+ milestone-progress cache (no-store refetch), closes, and the next CTA
310
670
  resolves to the first later milestone still `planned`. Jira-connected
311
671
  projects ride the existing machinery on the store mutation.
312
672
 
313
673
  ## Tests
314
674
 
315
675
  `server/blueprint-{draft-parser,render,store,chat-manager,commit,router}.test.ts`,
316
- `server/offline-assemble.test.ts`, `server/project-router.test.ts`; client
317
- `src/lib/__tests__/{blueprint-draft,milestone-launch}.test.ts`,
676
+ `server/json-tolerant.test.ts`, `server/offline-assemble.test.ts`,
677
+ `server/project-router.test.ts`, `server/desktop-db.test.ts` (migration 23);
678
+ client `src/lib/__tests__/{blueprint-draft,blueprint-readiness,milestone-launch}.test.ts`,
318
679
  `src/hooks/__tests__/useBuilderSession.test.ts`,
319
- `src/components/__tests__/{ProjectBuilder,BuilderSidebarEntry}.test.tsx`, and
320
- `src/pages/__tests__/DashboardPage.test.tsx`.
680
+ `src/components/__tests__/{ProjectBuilder,BuilderSidebarEntry,MilestoneGenerateShell}.test.tsx`,
681
+ and `src/pages/__tests__/DashboardPage.test.tsx`.
321
682
  Locale parity covers the `builder` namespace ×8.
322
683
 
323
684
  ## Deferred (v2)
324
685
 
325
- Resume/minimize for Builder conversations (no dock chip — no `projectId` to
326
- tag), an orphan-dir startup sweeper, non-GitHub remotes, editing an existing
327
- blueprint via the day-0 Builder, a live side-panel draft during M2 generation.
686
+ Minimize-to-dock for Builder conversations (no `projectId` to tag — resume is
687
+ now the hero list instead), an orphan-dir startup sweeper, non-GitHub remotes,
688
+ editing an existing blueprint via the day-0 Builder, a live side-panel draft
689
+ during M2 generation, an app-driven repair turn for M2+ (ChatManager path).
@@ -102,10 +102,13 @@ Fresh no-change cards offer explicit **Mark done** and **Refine** outcomes. Mark
102
102
  them to the backlog. Existing-PR no-change cards only dismiss their borrowed follow-up state.
103
103
 
104
104
  **`merge-local` (remote-less acceptance).** Legal only without a real PR. Both surfaces confirm
105
- because it changes the user's checkout. Source branches are first assembled away from the user's
106
- checkout; only a complete conflict-free result may advance a still-clean, still-at-the-original-
107
- HEAD integration branch. A conflict leaves the user's checkout byte-identical and the delivery
108
- actionable.
105
+ because it advances the local integration branch. Exact delivered commits are first assembled away
106
+ from the user's checkout; only a complete conflict-free result may advance the still-at-the-original-
107
+ HEAD integration branch. Compatible staged, unstaged, untracked and ignored local files survive;
108
+ Git refuses an overwrite with a retryable `merge_local_blocked` response. If the user is on another
109
+ branch or detached HEAD, a temporary checkout claims the base without switching the user's checkout.
110
+ A base already held by another worktree remains untouched and reports `integration_branch_busy`.
111
+ Both temporary checkouts use non-force cleanup, including after a Git runner exception.
109
112
 
110
113
  ## Launch and recovery wiring (`server/rail-isolated-launch.ts`)
111
114
 
@@ -125,7 +128,9 @@ actionable.
125
128
  proven clean) — run-created build caches (`__pycache__`, `node_modules`) release cleanly, while an
126
129
  ignored path that APPEARS after settlement, a failed/oversized capture (>400 paths → null), a
127
130
  legacy row without a snapshot, or conflicting per-branch snapshots all preserve the worktree
128
- (`durableSettlementIgnoredPaths`, cleanup-and-checkout-tolerate-run-artifacts). Authenticated overlay roots are atomically renamed to a unique
131
+ (`durableSettlementIgnoredPaths`, cleanup-and-checkout-tolerate-run-artifacts). Authorized ignored
132
+ roots are archived as well: the snapshot grants release authority for their names, never deletion
133
+ authority for user edits or additions under those directories. Authenticated overlay roots are atomically renamed to a unique
129
134
  persistent same-filesystem quarantine batch and revalidated there; automatic cleanup never unlinks that
130
135
  quarantine, so a raced replacement or open-descriptor write remains recoverable. The batch root is
131
136
  persisted before the first move and pointers are never evicted while their bytes remain on disk. A recreated
@@ -333,7 +338,8 @@ attached and ready for Verify so that explicit decision can commit terminal tick
333
338
  reopening the stale attachment. A valid reopen returns to draft or ready according to GitHub;
334
339
  an exact merge racing the action terminalizes, while an exact still-closed result reports failure.
335
340
  - **merge-local** — builds the complete merge in an isolated temporary checkout and advances the
336
- user's revalidated clean base only after all branches succeed.
341
+ revalidated base only after all branches succeed, preserving compatible local work. A missing
342
+ immutable delivery object never falls back to a branch that may have moved.
337
343
 
338
344
  Terminal discard/merge/completion inserts `rail_pr_ticket_effects` in the same SQLite transaction
339
345
  as its decision and snapshots each ticket's non-null outcome owner. Before crossing into ticket JSON
@@ -411,10 +417,14 @@ the `expectedDecision` they rendered.
411
417
  result and offers the confirmed Commit & retry push action above. A `recovery_unavailable` row
412
418
  replaces that primary action with Check again; a clone without the original worktree/object never
413
419
  claims that the implementation failed or that local progress was discarded. Ordinary Checkout is restricted
414
- to a delivery with an immutable SHA; the server repeats that guard under the repo lock and refuses
415
- a dirty or unreadable main checkout before releasing a worktree or changing any ref. Checkout
416
- re-reads and uses only the current attached PR branch and immutable `delivery_sha` after acquiring
417
- the repository lock, never the pre-lock snapshot. A same-named local/remote branch must equal that
420
+ to a reviewable delivery with an immutable SHA; the server repeats that guard under the repo lock
421
+ and refuses unreadable main-checkout status. Compatible dirty edits are preserved by Git's
422
+ non-force checkout with ignored-file overwrite protection. Checkout re-reads the current delivery
423
+ branch and immutable `delivery_sha` after acquiring the repository lock, never the pre-lock snapshot.
424
+ A single succeeded local unit with `finalSha` can be checked out before creating any PR; an
425
+ unassembled batch cannot expose just its first unit as the whole result. Cleanup warnings from
426
+ unrelated preserved unit worktrees remain visible without blocking an available assembled branch.
427
+ A same-named local/remote branch must equal that
418
428
  SHA before switching, and the final branch plus HEAD are verified afterward; divergence is
419
429
  preserved and a failed fast-forward can never be reported as success. Logs remain available after settle. Buttons
420
430
  disable in flight and apply the authoritative HTTP snapshot immediately.
@@ -478,6 +488,38 @@ one-shot, consumed by the launch) with `TargetPrLaunchDialog` confirming number/
478
488
  MCP `specrails_rails(launch, targetPrNumber)` with operator-prompt teaching ("user names a PR →
479
489
  pass it; never create a duplicate"). Fork PR push support and multi-PR batches remain out of scope.
480
490
 
491
+ ## Explicit base branch + stacked chunks (premium-milestone-progress)
492
+
493
+ The launch body accepts an optional `baseBranch`: a LOCAL branch to base the isolated worktree on
494
+ instead of the integration branch. Shape-validated (`isValidBranchName`, 400 `invalid_base_branch`),
495
+ isolation-gated (400 `base_branch_requires_isolation` when loops are off, the launch would run in
496
+ the shared checkout, or isolation is unavailable), and resolved right before allocation
497
+ (`git rev-parse --verify --quiet refs/heads/<branch>`, 400 `invalid_base_branch` otherwise — a chain
498
+ never stacks on a guess). `launchIsolatedRail` threads it as `resolveIntegrationBranch({ explicit })`,
499
+ so the delivery row records it as `base_branch` and `deliverRailAsPr` creates the PR STACKED on it
500
+ (`gh pr create --base <branch>`). Today's only driver is the milestone launch chain
501
+ (`server/milestone-chain.ts`): chunk k+1 of a sequential "Launch Milestone" launches with
502
+ `baseBranch = chunk k's delivered branch`, so a greenfield walking skeleton accumulates without waiting
503
+ for a merge. MCP `specrails_rails(launch, baseBranch)` exposes the same parameter.
504
+
505
+ Two decision-side consequences keep the stacked lineage honest. **Ancestor sweep** — after any
506
+ `merged` transition (merge-local or poll-merge), `sweepMergedChainAncestors` walks the chain(s) the
507
+ merged delivery belongs to and settles every still-undecided sibling (`on_review | pr_draft |
508
+ pr_ready | pr_closed`) whose delivered head (`delivery_sha`, else a unit's `finalSha`) is PROVABLY an
509
+ ancestor of the chain's integration branch (`git merge-base --is-ancestor`) as `merged` through the
510
+ same CAS (own operation lease, released BEFORE `finalizeTransition` so the broadcast is not deferred
511
+ to a lease owner that never comes) + ticket effect (`done`) + Jira `onRailMerged(…, null)`;
512
+ `status_detail` records `merged as part of <railKey>`. Chain-local by design — a non-ancestor sibling
513
+ is untouched. **Merge-local target** — a stacked delivery's `base_branch` is the previous chunk's
514
+ FEATURE branch, so `runMergeLocalLocked` resolves `mergeLocalTargetBranch` (the chain's
515
+ `integration_branch` when the delivery belongs to a chain, else `base_branch`) for both the checkout
516
+ guard (`wrong_branch`) and the merge itself. **Discard of a stacked head** — `pauseChainsForDiscardedHead`
517
+ pauses every active chain whose launched chunks include the discarded delivery (`head_discarded`, head
518
+ rewound to the previous chunk's branch, else the integration branch) and broadcasts
519
+ `milestone.chain_changed`; the rail strip, the agent card and the review packet render
520
+ `discardStackedNote` in the discard confirm when the delivery is such a head
521
+ (`useStackedHeadDeliveryIds`).
522
+
481
523
  ## Ticket lifecycle — `on_review`
482
524
 
483
525
  New status in `TicketStatus` / `VALID_STATUSES` (`server/ticket-store.ts`), between