specrails-desktop 2.29.1 → 2.29.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.
- package/client/dist/assets/ActivityFeedPage-ClpD5CFj.js +1 -0
- package/client/dist/assets/AgentBrowserCapture-CSPs9Ofc.js +1 -0
- package/client/dist/assets/AgentModeCodePane-DwcyklA5.js +2 -0
- package/client/dist/assets/AgentModeJobsPane-C69bqlMa.js +1 -0
- package/client/dist/assets/AgentsPage-pRiRr9-W.js +86 -0
- package/client/dist/assets/AnalyticsPage-C14exKsC.js +1 -0
- package/client/dist/assets/BarChart-nBOMNfiy.js +33 -0
- package/client/dist/assets/CodePage-CK76JKm9.js +2 -0
- package/client/dist/assets/DesktopAnalyticsPage-Cnv0f3XQ.js +1 -0
- package/client/dist/assets/DocsDialog-CHUm_Ym0.js +11 -0
- package/client/dist/assets/DocsPage-COI-oDok.js +11 -0
- package/client/dist/assets/ExportDropdown-BrtfWzEG.js +1 -0
- package/client/dist/assets/IntegrationsPage-Bs00_tl4.js +1 -0
- package/client/dist/assets/InteractiveJobComposer-CypM7R1F.js +19 -0
- package/client/dist/assets/JobDetailModal-B_W0H9dw.js +1 -0
- package/client/dist/assets/JobDetailPage-CeeXOoKS.js +1 -0
- package/client/dist/assets/JobsPage-CwhJG9jw.js +1 -0
- package/client/dist/assets/LoopBuilderPage-C51t1WOM.js +7 -0
- package/client/dist/assets/LoopPreviewModal-Y2HgtIJu.js +1 -0
- package/client/dist/assets/LoopsPage-Bvn1Kw5V.js +1 -0
- package/client/dist/assets/MinimizedChatsContext-DiFvZDPW.js +1 -0
- package/client/dist/assets/PluginsPage-BGPRJpdf.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-D_GTyGLc.js +1 -0
- package/client/dist/assets/TemplatePreviewModal-szwBhd7h.js +1 -0
- package/client/dist/assets/TicketDetailModal-9UEyK974.js +14 -0
- package/client/dist/assets/Trans-B_vKZ-Hb.js +1 -0
- package/client/dist/assets/activity-DiyjsgQV.js +1 -0
- package/client/dist/assets/addon-image-BRmmRHgQ.js +1 -0
- package/client/dist/assets/addon-ligatures-qIZUPvRV.js +2 -0
- package/client/dist/assets/{addon-webgl-DMbGeI_u.js → addon-webgl-C2d7lriQ.js} +3 -3
- package/client/dist/assets/addspec-Rw8uVBC1.js +1 -0
- package/client/dist/assets/agent-4rmXoo_p.js +1 -0
- package/client/dist/assets/agent-BxZuNGih.js +1 -0
- package/client/dist/assets/agent-C3uXOYv_.js +1 -0
- package/client/dist/assets/agent-CA8Y4wbw.js +1 -0
- package/client/dist/assets/agent-CASKrhG12.js +1 -0
- package/client/dist/assets/agent-Cv-jud5n2.js +1 -0
- package/client/dist/assets/agent-DitUxYh2.js +1 -0
- package/client/dist/assets/agent-Doow4X7o.js +1 -0
- package/client/dist/assets/agents-DhG7mg6-.js +1 -0
- package/client/dist/assets/agentstudio-BIUe1Im2.js +1 -0
- package/client/dist/assets/aiedit-iIjz2_Qk.js +1 -0
- package/client/dist/assets/analytics-DZ8Aoq3I.js +1 -0
- package/client/dist/assets/api-B2M7CtDw.js +1 -0
- package/client/dist/assets/arrow-left-CpIR4j7G.js +1 -0
- package/client/dist/assets/arrow-up-right-9DoMn7fD.js +1 -0
- package/client/dist/assets/attachments-B53PG733.js +1 -0
- package/client/dist/assets/brain-DI9lHkKY.js +1 -0
- package/client/dist/assets/browser-CXUcnTMI.js +1 -0
- package/client/dist/assets/button-BUIIBQPT.js +1 -0
- package/client/dist/assets/chat-BGD7MXAy.js +1 -0
- package/client/dist/assets/chevron-right-CViMxucY.js +1 -0
- package/client/dist/assets/chunk-KS7C4IRE-Cx5sqopX.js +3 -0
- package/client/dist/assets/circle-x-CMYsN4ZH.js +1 -0
- package/client/dist/assets/code-BDXEM4ml.js +1 -0
- package/client/dist/assets/commands-DyUZQ1FI.js +1 -0
- package/client/dist/assets/common-CD1QHriA2.js +1 -0
- package/client/dist/assets/common-CwwPaRxx.js +1 -0
- package/client/dist/assets/common-DVQuhs-m.js +1 -0
- package/client/dist/assets/common-Dg2jaX7d.js +1 -0
- package/client/dist/assets/common-DuBwj6tK.js +1 -0
- package/client/dist/assets/common-MRj9MHcc.js +1 -0
- package/client/dist/assets/common-O4ZpnMLp.js +1 -0
- package/client/dist/assets/common-fyeF0v7C2.js +1 -0
- package/client/dist/assets/createLucideIcon-Bjbbp_KS.js +1 -0
- package/client/dist/assets/{css.worker-Wv5dxAWO.js → css.worker-yKcq13f2.js} +13 -13
- package/client/dist/assets/{cssMode-DBHbkBvI.js → cssMode-BM4z9OX0.js} +1 -1
- package/client/dist/assets/dashboard-B_NkSHYm.js +1 -0
- package/client/dist/assets/dashboard-ClBb_3pq.js +1 -0
- package/client/dist/assets/dashboard-CpCmJvMB.js +1 -0
- package/client/dist/assets/dashboard-Czohu35O2.js +1 -0
- package/client/dist/assets/dashboard-DcjbD0UK.js +1 -0
- package/client/dist/assets/dashboard-Dvx5F3we.js +1 -0
- package/client/dist/assets/dashboard-R1bOPjBh2.js +1 -0
- package/client/dist/assets/dashboard-owcDucDi.js +1 -0
- package/client/dist/assets/dialog-CzIekCw0.js +45 -0
- package/client/dist/assets/dist-Cez5CRkX.js +1 -0
- package/client/dist/assets/dist-bVqfYmby.js +1 -0
- package/client/dist/assets/{dist-js-CgpP6tKI.js → dist-js-CcBCbb32.js} +1 -1
- package/client/dist/assets/{dist-js-DHiN-A_E.js → dist-js-VwztRkGj.js} +1 -1
- package/client/dist/assets/download-CFHyyHcw.js +1 -0
- package/client/dist/assets/editor.api2-4ndOupAA.js +872 -0
- package/client/dist/assets/{editor.main-C9J2ikJK.js → editor.main-DxrmGfoq.js} +2 -2
- package/client/dist/assets/editor.worker-tytyn86m.js +26 -0
- package/client/dist/assets/explore-DgBACn39.js +1 -0
- package/client/dist/assets/external-link-BcU5EWZp.js +1 -0
- package/client/dist/assets/file-text-CFT-xXKN.js +1 -0
- package/client/dist/assets/formatDistanceToNow-ByHIauTQ.js +1 -0
- package/client/dist/assets/{freemarker2-J5iIIIK-.js → freemarker2-CELlTDJ2.js} +1 -1
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-BxHAksaU.js +1 -0
- package/client/dist/assets/git-branch-BRM0F5GN.js +1 -0
- package/client/dist/assets/{handlebars-DSw1l_tw.js → handlebars-NEjHJ7o_.js} +1 -1
- package/client/dist/assets/{html-h2-wE1Lj.js → html-CA10zN5A.js} +1 -1
- package/client/dist/assets/{html.worker-CQP8QQsS.js → html.worker-DSJ9zmd7.js} +12 -12
- package/client/dist/assets/{htmlMode-Cc_ij7lW.js → htmlMode-89rPYiyq.js} +1 -1
- package/client/dist/assets/i18n-CgaRxpII.js +2 -0
- package/client/dist/assets/index-BflYQpWm.css +2 -0
- package/client/dist/assets/index-C1_-BTXy.js +72 -0
- package/client/dist/assets/integrations-mB-7zoyT.js +1 -0
- package/client/dist/assets/{javascript-C1q3xv6N.js → javascript-B1gDmwzS.js} +1 -1
- package/client/dist/assets/jira-CmKWERE0.js +1 -0
- package/client/dist/assets/jira-api-BgFIr8eD.js +1 -0
- package/client/dist/assets/job-time-BCL7wee9.js +1 -0
- package/client/dist/assets/jobs-B-sDHEay2.js +1 -0
- package/client/dist/assets/jobs-BCtGfgUA.js +1 -0
- package/client/dist/assets/jobs-BHR_EnDI.js +1 -0
- package/client/dist/assets/jobs-BMbZJjJJ.js +1 -0
- package/client/dist/assets/jobs-CVFD8rar.js +1 -0
- package/client/dist/assets/jobs-DoSaFzAa.js +1 -0
- package/client/dist/assets/jobs-DzSIR8V62.js +1 -0
- package/client/dist/assets/jobs-lk1_Dq1U.js +1 -0
- package/client/dist/assets/json.worker-Oa9UrkKE.js +58 -0
- package/client/dist/assets/{jsonMode-CeaCdEhr.js → jsonMode-xHLMfzhF.js} +1 -1
- package/client/dist/assets/jsx-runtime-zu2_FqZY.js +1 -0
- package/client/dist/assets/lib-C7Mhm-gT.js +7 -0
- package/client/dist/assets/lib-CGdyxIWg.js +29 -0
- package/client/dist/assets/{liquid-eFsVK0V-.js → liquid-BY6hCUXG.js} +1 -1
- package/client/dist/assets/loader-circle-DcygOQpC.js +1 -0
- package/client/dist/assets/loops-BzfXWEwO.js +1 -0
- package/client/dist/assets/{lspLanguageFeatures-DT3-bbhn.js → lspLanguageFeatures-ira2aEJh.js} +1 -1
- package/client/dist/assets/mcp-uDkXYu4L.js +1 -0
- package/client/dist/assets/{mdx-Fuwnh1cX.js → mdx-BNx0xr3N.js} +1 -1
- package/client/dist/assets/monaco.contribution-Bt92R3Vw.js +2 -0
- package/client/dist/assets/nav-myFaXRHD.js +1 -0
- package/client/dist/assets/pencil-C9RkrIPZ.js +1 -0
- package/client/dist/assets/play-DNlrawbS.js +1 -0
- package/client/dist/assets/plus-CsnjfkmX.js +1 -0
- package/client/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/client/dist/assets/{python-BgoEs6Cj.js → python-DLdbtmNw.js} +1 -1
- package/client/dist/assets/{razor-CM1sPMMq.js → razor-B9gdhAbR.js} +1 -1
- package/client/dist/assets/react-dom-DiWzBDP9.js +1 -0
- package/client/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
- package/client/dist/assets/save-28sIlCdr.js +1 -0
- package/client/dist/assets/search-Cq_gE3Ik.js +1 -0
- package/client/dist/assets/settings-B0ypw9uw.js +1 -0
- package/client/dist/assets/setup-CmK87LuS.js +1 -0
- package/client/dist/assets/sparkles-DrY8hb0X.js +1 -0
- package/client/dist/assets/specs-DII0C8gw.js +1 -0
- package/client/dist/assets/spending-C4p06nen.js +1 -0
- package/client/dist/assets/terminal-BFRZnMPP.js +1 -0
- package/client/dist/assets/tickets-B8BuAaj5.js +1 -0
- package/client/dist/assets/tickets-B_yh4qQk.js +1 -0
- package/client/dist/assets/tickets-Bs37scwY2.js +1 -0
- package/client/dist/assets/tickets-CFk2n2Or.js +1 -0
- package/client/dist/assets/tickets-DQp7JS2R2.js +1 -0
- package/client/dist/assets/tickets-D_yDquEw.js +1 -0
- package/client/dist/assets/tickets-DpmdE8tv2.js +1 -0
- package/client/dist/assets/tickets-pAUJkDTu.js +1 -0
- package/client/dist/assets/trash-2-BOU3WdZZ.js +1 -0
- package/client/dist/assets/triangle-alert-H9T4jAmh.js +1 -0
- package/client/dist/assets/{ts.worker-METxwbDZ.js → ts.worker-BtjBFNHX.js} +48 -33
- package/client/dist/assets/{tsMode-BmT8WogI.js → tsMode-DEjSThU9.js} +1 -1
- package/client/dist/assets/{typescript-CQrQPsoW.js → typescript-CRRnD2Ts.js} +1 -1
- package/client/dist/assets/upload-DYMjAQ6M.js +1 -0
- package/client/dist/assets/useBrowserCaptureSession-2OqX0vap.js +1 -0
- package/client/dist/assets/useDesktop-X8YB9xzv.js +1 -0
- package/client/dist/assets/useSharedWebSocket-DaPh7QBL.js +1 -0
- package/client/dist/assets/utils-BC4DHlz2.js +1 -0
- package/client/dist/assets/value-s-gzBeyF.js +1 -0
- package/client/dist/assets/{workers-U0UXyLUf.js → workers-uOo7WLaa.js} +1 -1
- package/client/dist/assets/x-CBMI9ejn.js +1 -0
- package/client/dist/assets/{xml-WRxJs8_I.js → xml-EDREH2X7.js} +1 -1
- package/client/dist/assets/{yaml-CjpSTjJU.js → yaml-BjaDVX1E.js} +1 -1
- package/client/dist/assets/zap-CHo3aCD1.js +1 -0
- package/client/dist/index.html +73 -32
- package/docs/guide/en/integrations/5-mcp-server.md +2 -2
- package/docs/internals/safe-pr-review-flow.md +190 -132
- package/docs/mcp.md +15 -5
- package/package.json +11 -7
- package/server/dist/active-pr-continuation.js +16 -1
- package/server/dist/agent-chat-manager.js +360 -181
- package/server/dist/agent-cwd-manager.js +52 -8
- package/server/dist/agent-generator.js +90 -48
- package/server/dist/agent-mcp-config.js +69 -36
- package/server/dist/agent-operator-prompt.js +3 -2
- package/server/dist/agent-refine-manager.js +47 -6
- package/server/dist/agent-tier.js +13 -11
- package/server/dist/artifact-registry.js +33 -2
- package/server/dist/auth.js +5 -4
- package/server/dist/browser-capture-manager.js +92 -29
- package/server/dist/chat-manager.js +302 -46
- package/server/dist/contract-refine-runner.js +19 -6
- package/server/dist/db.js +665 -8
- package/server/dist/desktop-router.js +1 -2
- package/server/dist/headroom-manager.js +1310 -121
- package/server/dist/headroom-routing.js +120 -2
- package/server/dist/index.js +48 -13
- package/server/dist/interactive-job-session.js +237 -82
- package/server/dist/jira/jira-adf.js +10 -0
- package/server/dist/jira/jira-db.js +15 -3
- package/server/dist/jira/jira-sync-manager.js +182 -13
- package/server/dist/job-listing.js +140 -0
- package/server/dist/job-spawn-idempotency.js +67 -0
- package/server/dist/loop-run-manager.js +508 -98
- package/server/dist/loop-runs-store.js +266 -13
- package/server/dist/mcp/agent-capability.js +71 -0
- package/server/dist/mcp/guide.js +5 -2
- package/server/dist/mcp/mcp-server.js +5 -10
- package/server/dist/mcp/mcp-token.js +5 -5
- package/server/dist/mcp/tools/jobs.js +20 -13
- package/server/dist/mcp/tools/types.js +20 -42
- package/server/dist/mobile/mobile-router.js +1 -1
- package/server/dist/pr-lifecycle.js +77 -0
- package/server/dist/pr-publisher.js +94 -27
- package/server/dist/process-admission.js +82 -0
- package/server/dist/project-registry.js +572 -174
- package/server/dist/project-router-git.js +13 -2
- package/server/dist/project-router-jobs.js +111 -55
- package/server/dist/project-router-tickets.js +32 -2
- package/server/dist/providers/claude-adapter.js +21 -13
- package/server/dist/queue-manager.js +2332 -623
- package/server/dist/rail-isolated-launch.js +1137 -290
- package/server/dist/rail-isolation.js +4 -2
- package/server/dist/rail-pr-decision.js +740 -266
- package/server/dist/rail-pr-delivery.js +2 -1
- package/server/dist/rail-pr-store.js +317 -18
- package/server/dist/rail-pr-ticket-effects.js +328 -0
- package/server/dist/rail-worktree-release.js +18 -2
- package/server/dist/rails-router.js +149 -119
- package/server/dist/rails-store.js +106 -3
- package/server/dist/ticket-store.js +23 -1
- package/server/dist/transient-children.js +13 -0
- package/server/dist/util/cli-prompt.js +42 -8
- package/server/dist/util/secure-token-file.js +84 -0
- package/server/dist/worktree-manager.js +6 -5
- package/client/dist/assets/ActivityFeedPage-D8RKRRoM.js +0 -1
- package/client/dist/assets/AgentBrowserCapture-BUHPHNSN.js +0 -1
- package/client/dist/assets/AgentModeCodePane-GZc8bNGT.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-DWtQqj3k.js +0 -1
- package/client/dist/assets/AgentsPage-CKPtHkRP.js +0 -86
- package/client/dist/assets/AnalyticsPage-CZW8c77k.js +0 -1
- package/client/dist/assets/BarChart-DugEoe2q.js +0 -33
- package/client/dist/assets/CodePage-CQ-uKETG.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-Bgo4SyCH.js +0 -1
- package/client/dist/assets/DocsDialog-DxpWw_La.js +0 -11
- package/client/dist/assets/DocsPage-CmcOkK19.js +0 -11
- package/client/dist/assets/ExportDropdown-DvTS2wc7.js +0 -1
- package/client/dist/assets/IntegrationsPage-B72WxJ3Z.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-B5gh4Qoy.js +0 -19
- package/client/dist/assets/JobDetailModal-D-BeqsJ6.js +0 -1
- package/client/dist/assets/JobDetailPage-DwdozwLO.js +0 -1
- package/client/dist/assets/JobsPage-BDkinklt.js +0 -1
- package/client/dist/assets/LoopBuilderPage-CclJ3VZA.js +0 -7
- package/client/dist/assets/LoopPreviewModal-Dw4UgoLu.js +0 -1
- package/client/dist/assets/LoopsPage-D7ac4M24.js +0 -1
- package/client/dist/assets/PluginsPage-wdC-qa40.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-BlTlRtGI.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-CeMx2Red.js +0 -1
- package/client/dist/assets/activity-Dbl71S-E.js +0 -1
- package/client/dist/assets/addon-image-CwoZj17F.js +0 -1
- package/client/dist/assets/addon-ligatures-CWDrED80.js +0 -2
- package/client/dist/assets/addspec-DrXqP8ip.js +0 -1
- package/client/dist/assets/agent-B8BXmlUQ2.js +0 -1
- package/client/dist/assets/agent-BEXYxDbs.js +0 -1
- package/client/dist/assets/agent-BYc3-6Ln2.js +0 -1
- package/client/dist/assets/agent-BbJorPdT.js +0 -1
- package/client/dist/assets/agent-CewFImgL.js +0 -1
- package/client/dist/assets/agent-DfhYoUmp.js +0 -1
- package/client/dist/assets/agent-TK_rrK4d.js +0 -1
- package/client/dist/assets/agent-bHjA9R_j2.js +0 -1
- package/client/dist/assets/agents-CGDn_3kB.js +0 -1
- package/client/dist/assets/agentstudio-Dxe_94PK.js +0 -1
- package/client/dist/assets/aiedit-DrfHQnlz.js +0 -1
- package/client/dist/assets/analytics-CxDYcBl9.js +0 -1
- package/client/dist/assets/attachments-DvxQbBTC.js +0 -1
- package/client/dist/assets/brain-C92rjjYP.js +0 -1
- package/client/dist/assets/browser-DM8G151q.js +0 -1
- package/client/dist/assets/chat-0dg5hOo_.js +0 -1
- package/client/dist/assets/chunk-BNv3lrIs.js +0 -1
- package/client/dist/assets/code-B62fClk-.js +0 -1
- package/client/dist/assets/commands-DCJ-R9E4.js +0 -1
- package/client/dist/assets/common-BM3LFLCE2.js +0 -1
- package/client/dist/assets/common-Bwafujnc2.js +0 -1
- package/client/dist/assets/common-C-vU-j-I.js +0 -1
- package/client/dist/assets/common-C5HKSbbz.js +0 -1
- package/client/dist/assets/common-DXrmj6WD.js +0 -1
- package/client/dist/assets/common-DhWTkocg.js +0 -1
- package/client/dist/assets/common-DiPTT7V_2.js +0 -1
- package/client/dist/assets/common-DmqBPwZG.js +0 -1
- package/client/dist/assets/dashboard-B5ang-ZS.js +0 -1
- package/client/dist/assets/dashboard-CHHcOWVL2.js +0 -1
- package/client/dist/assets/dashboard-DBD4wPGY2.js +0 -1
- package/client/dist/assets/dashboard-DBuLfWo9.js +0 -1
- package/client/dist/assets/dashboard-DSnztwWH.js +0 -1
- package/client/dist/assets/dashboard-D_aP_InU.js +0 -1
- package/client/dist/assets/dashboard-DmBcdl1B.js +0 -1
- package/client/dist/assets/dashboard-RIcUKY1Y2.js +0 -1
- package/client/dist/assets/editor.api2-C3B0tp2x.js +0 -872
- package/client/dist/assets/editor.worker-Bd9IXS8d.js +0 -26
- package/client/dist/assets/explore-_XyU5e7R.js +0 -1
- package/client/dist/assets/index-BVbgKXUu.css +0 -2
- package/client/dist/assets/index-DyY1QyIn.js +0 -158
- package/client/dist/assets/integrations-By80EBkf.js +0 -1
- package/client/dist/assets/jira-DeIjwCK3.js +0 -1
- package/client/dist/assets/jobs-1GV2-4zd2.js +0 -1
- package/client/dist/assets/jobs-26F9gGcE.js +0 -1
- package/client/dist/assets/jobs-BgQzB0Sx2.js +0 -1
- package/client/dist/assets/jobs-BqWui9_f.js +0 -1
- package/client/dist/assets/jobs-CE8qeIYj.js +0 -1
- package/client/dist/assets/jobs-DrYmsKy5.js +0 -1
- package/client/dist/assets/jobs-DzeuASRf.js +0 -1
- package/client/dist/assets/jobs-Wh7dn28q2.js +0 -1
- package/client/dist/assets/json.worker-DzV-CpCQ.js +0 -58
- package/client/dist/assets/lib-0PFDyMxJ.js +0 -7
- package/client/dist/assets/loops-CO6q75W8.js +0 -1
- package/client/dist/assets/mcp-HwBbQDk0.js +0 -1
- package/client/dist/assets/monaco.contribution-Desq7oq4.js +0 -2
- package/client/dist/assets/nav-BTg8s-cD.js +0 -1
- package/client/dist/assets/preload-helper-DSXbuxSR.js +0 -1
- package/client/dist/assets/settings-cJNynGIZ.js +0 -1
- package/client/dist/assets/setup-D2CmgAro.js +0 -1
- package/client/dist/assets/specs-PGIOzMtc.js +0 -1
- package/client/dist/assets/terminal-CFt56A5f.js +0 -1
- package/client/dist/assets/tickets-B7zT4IRf.js +0 -1
- package/client/dist/assets/tickets-BHv3ewvq.js +0 -1
- package/client/dist/assets/tickets-BoXom7l8.js +0 -1
- package/client/dist/assets/tickets-C3b8cHhd2.js +0 -1
- package/client/dist/assets/tickets-CFXnJsYF.js +0 -1
- package/client/dist/assets/tickets-CWYFvx1W2.js +0 -1
- package/client/dist/assets/tickets-DTHo9c3C.js +0 -1
- package/client/dist/assets/tickets-SuX_HkK4.js +0 -1
- package/client/dist/assets/upload-CmR4h9H1.js +0 -1
- package/client/dist/assets/value-JA3bf47v.js +0 -1
- /package/client/dist/assets/{abap-D0E4FfVW.js → abap-08VXUWAP.js} +0 -0
- /package/client/dist/assets/{activity--iN4Ad2R2.js → activity-B-rp4h_v.js} +0 -0
- /package/client/dist/assets/{activity-6hM_ZSg9.js → activity-C6kJpm9q2.js} +0 -0
- /package/client/dist/assets/{activity-CIOLxGvh.js → activity-CGx4kb_j.js} +0 -0
- /package/client/dist/assets/{activity-BZNfOdiS.js → activity-CnoX2bH7.js} +0 -0
- /package/client/dist/assets/{activity-BuVo5sPB.js → activity-DAyekdlt2.js} +0 -0
- /package/client/dist/assets/{activity-D2r2h3SE2.js → activity-DCxtpT7j.js} +0 -0
- /package/client/dist/assets/{activity-DtxrAUMo2.js → activity-JzwRaWPD.js} +0 -0
- /package/client/dist/assets/{addspec-BMtLZLW62.js → addspec-BXjvkh5q.js} +0 -0
- /package/client/dist/assets/{addspec-CRuvWn8d.js → addspec-C4bwrmpi.js} +0 -0
- /package/client/dist/assets/{addspec-D-JLDsUV.js → addspec-CArHXUDM.js} +0 -0
- /package/client/dist/assets/{addspec-mgPOt5q02.js → addspec-DTKJcXiR.js} +0 -0
- /package/client/dist/assets/{addspec-DfhDbg5c.js → addspec-DbNBbrha2.js} +0 -0
- /package/client/dist/assets/{addspec-CP0HRuxY.js → addspec-htHGZHOe2.js} +0 -0
- /package/client/dist/assets/{addspec-bsNDdqRm2.js → addspec-lr_LP6jk.js} +0 -0
- /package/client/dist/assets/{agents-78bTZGZh.js → agents-6ZOJXWVk2.js} +0 -0
- /package/client/dist/assets/{agents-DDtCJ5M02.js → agents-BZ0XQKjg.js} +0 -0
- /package/client/dist/assets/{agents-CWo-KyAB.js → agents-BqWklnHR.js} +0 -0
- /package/client/dist/assets/{agents-BUdEi3-N.js → agents-Bz-thHkB2.js} +0 -0
- /package/client/dist/assets/{agents-bvyRLczL.js → agents-CJclElx6.js} +0 -0
- /package/client/dist/assets/{agents-BpoUaoW92.js → agents-D_l9L0VA.js} +0 -0
- /package/client/dist/assets/{agents-BLhWCmDS2.js → agents-u_C0jPB5.js} +0 -0
- /package/client/dist/assets/{agentstudio-BMQxc_bz.js → agentstudio-BF19hbXj.js} +0 -0
- /package/client/dist/assets/{agentstudio-C_Jq8Tl4.js → agentstudio-BLGvxWBx2.js} +0 -0
- /package/client/dist/assets/{agentstudio-Dtq3dGYk.js → agentstudio-D9UKiWTj2.js} +0 -0
- /package/client/dist/assets/{agentstudio-CWgXG5W52.js → agentstudio-Drs37nIM.js} +0 -0
- /package/client/dist/assets/{agentstudio-sI27TpBu2.js → agentstudio-nEnKkcXM.js} +0 -0
- /package/client/dist/assets/{agentstudio-jKMTdT70.js → agentstudio-pyFcSBKy.js} +0 -0
- /package/client/dist/assets/{agentstudio-COZriimI2.js → agentstudio-zfTzQh3U.js} +0 -0
- /package/client/dist/assets/{aiedit-hknZlly82.js → aiedit-B0KUbpB6.js} +0 -0
- /package/client/dist/assets/{aiedit-eMN4eQPF.js → aiedit-BoG24nlg2.js} +0 -0
- /package/client/dist/assets/{aiedit-pc5JDUq1.js → aiedit-CjlSRQVP.js} +0 -0
- /package/client/dist/assets/{aiedit-B03pD_Na.js → aiedit-DCjA7jLJ.js} +0 -0
- /package/client/dist/assets/{aiedit-6CHdb_iv2.js → aiedit-DNXxq_Dr.js} +0 -0
- /package/client/dist/assets/{aiedit-igmw-ZoZ2.js → aiedit-DQ-0_pvE.js} +0 -0
- /package/client/dist/assets/{aiedit-BtRqRxML.js → aiedit-olnv3QSt2.js} +0 -0
- /package/client/dist/assets/{analytics-CCprAqk3.js → analytics-BC7mwR4s.js} +0 -0
- /package/client/dist/assets/{analytics-CR1TJqUi2.js → analytics-BiNg8edP.js} +0 -0
- /package/client/dist/assets/{analytics-Bj-SmKHa.js → analytics-C0s_T2GN.js} +0 -0
- /package/client/dist/assets/{analytics-C5dpiS0o.js → analytics-CdsAPAwZ2.js} +0 -0
- /package/client/dist/assets/{analytics-BWMUZ2Pq2.js → analytics-Cr1_q-lC.js} +0 -0
- /package/client/dist/assets/{analytics-DfNdXR0y2.js → analytics-IEsKZvpW2.js} +0 -0
- /package/client/dist/assets/{analytics-rmHuJv0V.js → analytics-SRPf84OT.js} +0 -0
- /package/client/dist/assets/{apex-D_X_8J4d.js → apex-BWPQTe0t.js} +0 -0
- /package/client/dist/assets/{atom-one-dark-BMDPFwRZ.js → atom-one-dark-qiGhVQxg.js} +0 -0
- /package/client/dist/assets/{attachments-BgBNSqQd.js → attachments-D0wXXt9T2.js} +0 -0
- /package/client/dist/assets/{attachments-C9GTTJsx2.js → attachments-DenIjuVI.js} +0 -0
- /package/client/dist/assets/{attachments-BjyMmrwE.js → attachments-DhO7LPfy.js} +0 -0
- /package/client/dist/assets/{attachments-DPCmkFZu2.js → attachments-DoH5w0x4.js} +0 -0
- /package/client/dist/assets/{attachments-DCZOL1RU.js → attachments-Q9yNtiEM.js} +0 -0
- /package/client/dist/assets/{attachments-rNn1ekRy2.js → attachments-V_WZAYXx2.js} +0 -0
- /package/client/dist/assets/{attachments-DL-bPy9E.js → attachments-eaeScpjM.js} +0 -0
- /package/client/dist/assets/{auth-DpZq5p1e.js → auth-D_5bQv5W.js} +0 -0
- /package/client/dist/assets/{azcli-PZLnZ3jH.js → azcli-Bc_sGQ0U.js} +0 -0
- /package/client/dist/assets/{bat-KlBOxpO4.js → bat-i0X4ZdIN.js} +0 -0
- /package/client/dist/assets/{bicep-CgG9oF3V.js → bicep-B5-_aFwp.js} +0 -0
- /package/client/dist/assets/{browser-BPkhILSJ.js → browser-C25DkQiO.js} +0 -0
- /package/client/dist/assets/{browser-BqLqJVVT.js → browser-CiJ7-L32.js} +0 -0
- /package/client/dist/assets/{browser-rWKj-RR_2.js → browser-CvgDySd9.js} +0 -0
- /package/client/dist/assets/{browser-Cvg-QHvd2.js → browser-CyR1Kp3m2.js} +0 -0
- /package/client/dist/assets/{browser-FRv8P02E.js → browser-DwZljcRh2.js} +0 -0
- /package/client/dist/assets/{browser-Cc0iDqUi.js → browser-DxP-alBK.js} +0 -0
- /package/client/dist/assets/{browser-BijVYG0Y2.js → browser-rRCwi7Y7.js} +0 -0
- /package/client/dist/assets/{cameligo-6R45j6Ph.js → cameligo-DMUM7wLl.js} +0 -0
- /package/client/dist/assets/{chat-Bom6bqK3.js → chat-2rIB8HhT.js} +0 -0
- /package/client/dist/assets/{chat-vylxqR0P2.js → chat-BR8oe53x2.js} +0 -0
- /package/client/dist/assets/{chat-Dnwg-NpQ.js → chat-Bua03tty.js} +0 -0
- /package/client/dist/assets/{chat-BgL3zv6x2.js → chat-CZpAQan1.js} +0 -0
- /package/client/dist/assets/{chat-DsbwJhus.js → chat-CdY5SGvS.js} +0 -0
- /package/client/dist/assets/{chat-Bh3NdgJs.js → chat-DZyu3ImY2.js} +0 -0
- /package/client/dist/assets/{chat-DdwZjT8K2.js → chat-D_I7nU5K.js} +0 -0
- /package/client/dist/assets/{clojure-CLAAMO7J.js → clojure-Cm7r79vr.js} +0 -0
- /package/client/dist/assets/{clsx-CNmW0Nip.js → clsx-CjueKrWZ.js} +0 -0
- /package/client/dist/assets/{code-UPVvmAKM2.js → code-76gS0IdL.js} +0 -0
- /package/client/dist/assets/{code-BRcDlze52.js → code-C1VwxlML.js} +0 -0
- /package/client/dist/assets/{code-CeQ0FZpC.js → code-CnMZyJpr.js} +0 -0
- /package/client/dist/assets/{code-DgeHz9CB.js → code-D20ef9Cf2.js} +0 -0
- /package/client/dist/assets/{code-DzC1rMk12.js → code-DdOXaWRY.js} +0 -0
- /package/client/dist/assets/{code-CwnBPPyA.js → code-Duohuf9l.js} +0 -0
- /package/client/dist/assets/{code-BdMy3Ffq.js → code-geHmknB92.js} +0 -0
- /package/client/dist/assets/{coffee-AruSygo-.js → coffee-Ba7i2nA0.js} +0 -0
- /package/client/dist/assets/{commands-DkU0qliq.js → commands-B9RZO4qS.js} +0 -0
- /package/client/dist/assets/{commands-u-4eEHCN.js → commands-BEwbAjtD2.js} +0 -0
- /package/client/dist/assets/{commands-JWMfGDmr.js → commands-Bbxu8kLU.js} +0 -0
- /package/client/dist/assets/{commands-C4Y3C-PF2.js → commands-CDU3qv68.js} +0 -0
- /package/client/dist/assets/{commands-xHDDGfcP2.js → commands-CzezgtQj.js} +0 -0
- /package/client/dist/assets/{commands-CSDs9q_2.js → commands-DTQBF_q52.js} +0 -0
- /package/client/dist/assets/{commands-RHOchN4n2.js → commands-hOtKuOXc.js} +0 -0
- /package/client/dist/assets/{cpp-DEE5r4Pw.js → cpp-C7h46wYY.js} +0 -0
- /package/client/dist/assets/{csharp-BVXJUvbK.js → csharp-BKxtCVv1.js} +0 -0
- /package/client/dist/assets/{csp-cXOF0JoV.js → csp-bTuwJoIa.js} +0 -0
- /package/client/dist/assets/{css-C_zQuAoy.js → css-DIMkf-bt.js} +0 -0
- /package/client/dist/assets/{cypher-t95m_Q-O.js → cypher-CVaqCwHa.js} +0 -0
- /package/client/dist/assets/{dart-B4r1Tg-e.js → dart-onAF5SnQ.js} +0 -0
- /package/client/dist/assets/{dockerfile-6mt7zHQw.js → dockerfile-DZFCIeNp.js} +0 -0
- /package/client/dist/assets/{ecl-DEY_Fz_Q.js → ecl-D05T4iGw.js} +0 -0
- /package/client/dist/assets/{elixir-R6-q1c5t.js → elixir-6RTg0lbw.js} +0 -0
- /package/client/dist/assets/{explore-B7fblXBb2.js → explore-BG99VGpL.js} +0 -0
- /package/client/dist/assets/{explore-CNBzrEPS.js → explore-C0TjprJW2.js} +0 -0
- /package/client/dist/assets/{explore-C0k8HrkN.js → explore-C2dgEo7q.js} +0 -0
- /package/client/dist/assets/{explore-BHtEpoNN2.js → explore-C8NDHbob.js} +0 -0
- /package/client/dist/assets/{explore-Dahc1lOx.js → explore-CWWgzQGR.js} +0 -0
- /package/client/dist/assets/{explore-D_mWBYCS2.js → explore-CcQXpdtS.js} +0 -0
- /package/client/dist/assets/{explore-UvW1jEMa.js → explore-DsXAgnmJ2.js} +0 -0
- /package/client/dist/assets/{flow9-CNQvHBOt.js → flow9-C5_-GSwl.js} +0 -0
- /package/client/dist/assets/{format-command-CuWuRvka.js → format-command-Dfa1o2d-.js} +0 -0
- /package/client/dist/assets/{fsharp-vPZkFXwv.js → fsharp-C8Ef5oNN.js} +0 -0
- /package/client/dist/assets/{go-KUzBMYJ_.js → go-C-y9NEjX.js} +0 -0
- /package/client/dist/assets/{graphql-C3W0rCsD.js → graphql-fmXr3nnJ.js} +0 -0
- /package/client/dist/assets/{hcl-BOOMFSNX.js → hcl-CpzslTdj.js} +0 -0
- /package/client/dist/assets/{ini-CFat1WJr.js → ini-sBoK_t0W.js} +0 -0
- /package/client/dist/assets/{integrations-TAcYNh0b.js → integrations-CH0zy1Mw2.js} +0 -0
- /package/client/dist/assets/{integrations-C1ThkTC52.js → integrations-CIAjhd7L.js} +0 -0
- /package/client/dist/assets/{integrations-2YcNyAl3.js → integrations-CZdwOe8A.js} +0 -0
- /package/client/dist/assets/{integrations-BXCvXUlG2.js → integrations-Ct7omMld.js} +0 -0
- /package/client/dist/assets/{integrations-Crk2rFhL2.js → integrations-D6MSxy3-.js} +0 -0
- /package/client/dist/assets/{integrations-Dn4JiGtW.js → integrations-DbZa-Zyc.js} +0 -0
- /package/client/dist/assets/{integrations-D9YrJ_Ep.js → integrations-b8pH-vfl2.js} +0 -0
- /package/client/dist/assets/{java-Dee-CNAM.js → java-BEtHBSE6.js} +0 -0
- /package/client/dist/assets/{jira-D5efgwp_.js → jira-BySfUuQZ.js} +0 -0
- /package/client/dist/assets/{jira-bDs7Im2C.js → jira-Byw2C0F8.js} +0 -0
- /package/client/dist/assets/{jira-BHAKtc4B.js → jira-CNeYi06w2.js} +0 -0
- /package/client/dist/assets/{jira-Be369RIR2.js → jira-D11gPhxz.js} +0 -0
- /package/client/dist/assets/{jira-CPpgA-5z2.js → jira-D38Gj7AT.js} +0 -0
- /package/client/dist/assets/{jira-uNH1gETL2.js → jira-gAvOyije.js} +0 -0
- /package/client/dist/assets/{jira-DS7ownDy.js → jira-pRaL7JQo2.js} +0 -0
- /package/client/dist/assets/{julia-CGNlLYuU.js → julia-Bri6UV-V.js} +0 -0
- /package/client/dist/assets/{kotlin-CY7lhknJ.js → kotlin-BOotOW0E.js} +0 -0
- /package/client/dist/assets/{less-D0KN7hfJ.js → less-B9JPFI3C.js} +0 -0
- /package/client/dist/assets/{lexon-CuRJufPs.js → lexon-CfSJPG6W.js} +0 -0
- /package/client/dist/assets/{loops-LIgO_-eM.js → loops-CPpgMapU.js} +0 -0
- /package/client/dist/assets/{loops-DJNhp17p2.js → loops-CWWbKuVL.js} +0 -0
- /package/client/dist/assets/{loops-D6D3XHjj.js → loops-Crc9It8o2.js} +0 -0
- /package/client/dist/assets/{loops-CvOWsi5r.js → loops-DH2hZcbK2.js} +0 -0
- /package/client/dist/assets/{loops-vhJdbcz92.js → loops-DS5TmvIU2.js} +0 -0
- /package/client/dist/assets/{loops-K5_HXfJa.js → loops-P2f0cN8-.js} +0 -0
- /package/client/dist/assets/{loops-BvqQ-bHR2.js → loops-fJV-9hH7.js} +0 -0
- /package/client/dist/assets/{lua-DHvH62Zf.js → lua-CsQS60Ue.js} +0 -0
- /package/client/dist/assets/{m3-tlthQ8Fo.js → m3-D-oSqn_W.js} +0 -0
- /package/client/dist/assets/{markdown-Dbx1LgUZ.js → markdown-Cimd5fb3.js} +0 -0
- /package/client/dist/assets/{mcp-Dcl-XxkR.js → mcp-Czd_iyp02.js} +0 -0
- /package/client/dist/assets/{mcp-XJNRfxVp2.js → mcp-D2pc5GGx.js} +0 -0
- /package/client/dist/assets/{mcp-sRmWE8WJ.js → mcp-D4ZjtIx32.js} +0 -0
- /package/client/dist/assets/{mcp-DtbxuH3V2.js → mcp-DIfBUv2D2.js} +0 -0
- /package/client/dist/assets/{mcp-H-gnQUdN.js → mcp-DsTwmjST.js} +0 -0
- /package/client/dist/assets/{mcp-N1nzp8oL.js → mcp-SMkn8boG.js} +0 -0
- /package/client/dist/assets/{mcp-CjVKXAhD2.js → mcp-fSfiVYAc.js} +0 -0
- /package/client/dist/assets/{mips-D_O3IUz_.js → mips-CIPQ_RoX.js} +0 -0
- /package/client/dist/assets/{msdax-C_ZmCnRt.js → msdax-DauUninz.js} +0 -0
- /package/client/dist/assets/{mysql-C-dCEYSX.js → mysql-SOo6toE5.js} +0 -0
- /package/client/dist/assets/{nav-BElxljIF2.js → nav-BRJBxaGt2.js} +0 -0
- /package/client/dist/assets/{nav-COu2E_-L.js → nav-CAGKv3_F.js} +0 -0
- /package/client/dist/assets/{nav-BXO7PJJG2.js → nav-CNskRJKQ2.js} +0 -0
- /package/client/dist/assets/{nav-CW9pxWfC2.js → nav-DY93irBL.js} +0 -0
- /package/client/dist/assets/{nav-CXHUA4uf.js → nav-DhjvYgqt.js} +0 -0
- /package/client/dist/assets/{nav-DdHF5VWJ.js → nav-Gb-r4wop.js} +0 -0
- /package/client/dist/assets/{nav-C8kOuGh8.js → nav-TvumAFD22.js} +0 -0
- /package/client/dist/assets/{objective-c-DZY9Aqxd.js → objective-c-FvmIjYaQ.js} +0 -0
- /package/client/dist/assets/{pascal-BVHLXoor.js → pascal-DrH0SRf2.js} +0 -0
- /package/client/dist/assets/{pascaligo-XdG_6u3k.js → pascaligo-D-ptJ9y-.js} +0 -0
- /package/client/dist/assets/{perl-b9QV0BcD.js → perl-oz_6vUea.js} +0 -0
- /package/client/dist/assets/{pgsql-Du5FDOgT.js → pgsql-DTj74zXo.js} +0 -0
- /package/client/dist/assets/{php-WJD0z2Z_.js → php-nr791fC2.js} +0 -0
- /package/client/dist/assets/{pla-CbciTf2w.js → pla-CopQ2nXW.js} +0 -0
- /package/client/dist/assets/{postiats-C1QxmvVq.js → postiats-43DmfD33.js} +0 -0
- /package/client/dist/assets/{powerquery-BEYDhPLZ.js → powerquery-D3hlyOfw.js} +0 -0
- /package/client/dist/assets/{powershell-CfaYw4XL.js → powershell-DmHpPYUd.js} +0 -0
- /package/client/dist/assets/{protobuf-pwZEDOYa.js → protobuf-C531GsRP.js} +0 -0
- /package/client/dist/assets/{pug-5ftqSPVe.js → pug-Z5eAx3Zn.js} +0 -0
- /package/client/dist/assets/{qsharp-uI6qZMq4.js → qsharp-DkqhCAOL.js} +0 -0
- /package/client/dist/assets/{r-BK54P1EG.js → r-BwWrilGY.js} +0 -0
- /package/client/dist/assets/{redis-DI0yd7kN.js → redis-ClamHrr6.js} +0 -0
- /package/client/dist/assets/{redshift-Dov26eyI.js → redshift-DT7zqm-g.js} +0 -0
- /package/client/dist/assets/{restructuredtext-QqJ_1dC-.js → restructuredtext-BYgofb2h.js} +0 -0
- /package/client/dist/assets/{ruby-5WjveeVb.js → ruby-DezsRK8O.js} +0 -0
- /package/client/dist/assets/{rust-BzVSfqFH.js → rust-DdL9SqIa.js} +0 -0
- /package/client/dist/assets/{sb-DMIUfT-3.js → sb-CcwsVR0C.js} +0 -0
- /package/client/dist/assets/{scala-CSKA7lqt.js → scala-DHpiXF5c.js} +0 -0
- /package/client/dist/assets/{scheme-CojOP2Qc.js → scheme-BeGwcela.js} +0 -0
- /package/client/dist/assets/{scss-DoBZGJG6.js → scss-gp-XZpBa.js} +0 -0
- /package/client/dist/assets/{settings-1w1O5Og82.js → settings-B9ilOSYO.js} +0 -0
- /package/client/dist/assets/{settings-Jc46vUhd.js → settings-BqlWHRHA.js} +0 -0
- /package/client/dist/assets/{settings-BSihkr0e.js → settings-D8dlqt4d.js} +0 -0
- /package/client/dist/assets/{settings-BiDOB3hV2.js → settings-DAZqjX2a2.js} +0 -0
- /package/client/dist/assets/{settings-DNL68KTE.js → settings-DRh39v1S2.js} +0 -0
- /package/client/dist/assets/{settings-BoubJhDP2.js → settings-Mj1OJbfd2.js} +0 -0
- /package/client/dist/assets/{settings-CB-EZ_W8.js → settings-lRReaSYm.js} +0 -0
- /package/client/dist/assets/{setup-D84y3wx-.js → setup-CGZjy2IF2.js} +0 -0
- /package/client/dist/assets/{setup-BuQNKztt2.js → setup-CXa0Sx16.js} +0 -0
- /package/client/dist/assets/{setup-BwMovM9z2.js → setup-D2ktKKMF2.js} +0 -0
- /package/client/dist/assets/{setup-DU0ULX8q2.js → setup-DG2H7ha5.js} +0 -0
- /package/client/dist/assets/{setup-BXpw07r-.js → setup-DK5bHDUq2.js} +0 -0
- /package/client/dist/assets/{setup-7J8TjG4O.js → setup-DrWlIQTZ.js} +0 -0
- /package/client/dist/assets/{setup-BiduSX9d.js → setup-rzHUVgAu.js} +0 -0
- /package/client/dist/assets/{shell-YOaE4udl.js → shell-CC2rA5mh.js} +0 -0
- /package/client/dist/assets/{solidity-l3jSyVLu.js → solidity-BEEn4gHE.js} +0 -0
- /package/client/dist/assets/{sophia-BhSGTaQJ.js → sophia-CRfGWb83.js} +0 -0
- /package/client/dist/assets/{sparql-qhk6QD9w.js → sparql-D_Lu-MrJ.js} +0 -0
- /package/client/dist/assets/{specs-CWtKcVDC.js → specs-BcyxaCwf2.js} +0 -0
- /package/client/dist/assets/{specs-DTplt2XM.js → specs-C1q9geAi.js} +0 -0
- /package/client/dist/assets/{specs-PozUNhqb.js → specs-CLZKysq-2.js} +0 -0
- /package/client/dist/assets/{specs-C9Sd7BhH2.js → specs-DZMS9o54.js} +0 -0
- /package/client/dist/assets/{specs-D2clrt2c2.js → specs-Dm6zUI-R.js} +0 -0
- /package/client/dist/assets/{specs-CBDvEYKv.js → specs-V3TvhKLX.js} +0 -0
- /package/client/dist/assets/{specs-CIPr9reA2.js → specs-dV3Mq5Mu2.js} +0 -0
- /package/client/dist/assets/{sql-DoO3y21-.js → sql-NEE52Syq.js} +0 -0
- /package/client/dist/assets/{st-ChZAY8oG.js → st-DbInun42.js} +0 -0
- /package/client/dist/assets/{swift-D290Tr7_.js → swift-Bxkupp3x.js} +0 -0
- /package/client/dist/assets/{systemverilog-DsxzgJ-o.js → systemverilog-Bz4Y3fRF.js} +0 -0
- /package/client/dist/assets/{tcl-BKDUHAAR.js → tcl-DISqw1ZD.js} +0 -0
- /package/client/dist/assets/{terminal-De2Brd23.js → terminal-BZiIu3rf.js} +0 -0
- /package/client/dist/assets/{terminal-cqfd3EDV.js → terminal-C2NAd7BW2.js} +0 -0
- /package/client/dist/assets/{terminal-WSAvHXoU.js → terminal-IahE4LW92.js} +0 -0
- /package/client/dist/assets/{terminal-Z7KsDM_n.js → terminal-QpXZU2Bn2.js} +0 -0
- /package/client/dist/assets/{terminal-xGbCZJHg.js → terminal-ScjyXNxQ.js} +0 -0
- /package/client/dist/assets/{terminal-BRm03iFA2.js → terminal-d7GadXbN.js} +0 -0
- /package/client/dist/assets/{terminal-DxQWCqSD2.js → terminal-tITa81iz.js} +0 -0
- /package/client/dist/assets/{twig-UpZQaM8G.js → twig-De2hgUGE.js} +0 -0
- /package/client/dist/assets/{typespec-DzaKvN8i.js → typespec-B8J7ngcE.js} +0 -0
- /package/client/dist/assets/{vb-CZpbb7tk.js → vb-DV3o63ZY.js} +0 -0
- /package/client/dist/assets/{wgsl-CNt3OIvl.js → wgsl-DpFanUEy.js} +0 -0
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PrContinuationIsolationError = void 0;
|
|
36
37
|
exports.launchIsolatedRail = launchIsolatedRail;
|
|
37
38
|
exports.reconcileRailWorktrees = reconcileRailWorktrees;
|
|
38
39
|
/**
|
|
@@ -41,9 +42,10 @@ exports.reconcileRailWorktrees = reconcileRailWorktrees;
|
|
|
41
42
|
* ledger) into a running parallel rail. Gated OFF by default (rail-isolation's
|
|
42
43
|
* opt-in flag), so this path is inert unless explicitly enabled.
|
|
43
44
|
*
|
|
44
|
-
* Flow: allocate one worktree+branch per
|
|
45
|
-
* worktrees (so concurrent AI CLIs never collide) → when ALL runs
|
|
46
|
-
* sequential validated merge-back on the base repo, then clean
|
|
45
|
+
* Flow: allocate one worktree+branch per independent delivery → fan out the loop
|
|
46
|
+
* runs IN those worktrees (so concurrent AI CLIs never collide) → when ALL runs
|
|
47
|
+
* settle, run the sequential validated merge-back on the base repo, then clean
|
|
48
|
+
* up worktrees. Tickets continuing one PR are grouped into its single checkout.
|
|
47
49
|
*
|
|
48
50
|
* The run spawns with cwd = the worktree (and repoDir = the worktree, so
|
|
49
51
|
* writes/git land there). Because `git worktree add` materializes only TRACKED
|
|
@@ -59,6 +61,7 @@ exports.reconcileRailWorktrees = reconcileRailWorktrees;
|
|
|
59
61
|
* via the executors' base env (see workspace-resolution.resolveLoopBaseEnv).
|
|
60
62
|
*/
|
|
61
63
|
const path = __importStar(require("path"));
|
|
64
|
+
const fs = __importStar(require("fs"));
|
|
62
65
|
const artifact_registry_1 = require("./artifact-registry");
|
|
63
66
|
const ids_1 = require("./ids");
|
|
64
67
|
const loop_constants_1 = require("./loop-constants");
|
|
@@ -82,7 +85,18 @@ const file_story_1 = require("./file-story");
|
|
|
82
85
|
const providers_1 = require("./providers");
|
|
83
86
|
const pr_publisher_1 = require("./pr-publisher");
|
|
84
87
|
const active_pr_continuation_1 = require("./active-pr-continuation");
|
|
88
|
+
const pr_lifecycle_1 = require("./pr-lifecycle");
|
|
85
89
|
const rail_worktree_release_1 = require("./rail-worktree-release");
|
|
90
|
+
/** A PR follow-up may only run on the verified PR branch in a dedicated
|
|
91
|
+
* worktree. Routers must surface this error instead of degrading to shared cwd. */
|
|
92
|
+
class PrContinuationIsolationError extends Error {
|
|
93
|
+
code = 'pr_continuation_isolation_required';
|
|
94
|
+
constructor(message) {
|
|
95
|
+
super(message);
|
|
96
|
+
this.name = 'PrContinuationIsolationError';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.PrContinuationIsolationError = PrContinuationIsolationError;
|
|
86
100
|
/**
|
|
87
101
|
* Ticket data for the conventional branch name (pr-naming): title/labels from
|
|
88
102
|
* the ticket-spec reader, the Jira key resolved with the authoritative
|
|
@@ -125,11 +139,86 @@ function commitFailureSummary(result) {
|
|
|
125
139
|
const dirty = result.dirty.length > 0 ? `; dirty=${result.dirty.slice(0, 8).join(', ')}` : '';
|
|
126
140
|
return `${result.error ?? 'worktree still has uncommitted deliverable changes'}${dirty}`;
|
|
127
141
|
}
|
|
142
|
+
const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
143
|
+
/** Prove that the linked checkout is on the expected PR branch and that its
|
|
144
|
+
* HEAD is exactly the commit named by refs/heads/<branch>. The handle's branch
|
|
145
|
+
* string alone is insufficient: createWorktree may reuse a stale mounted path. */
|
|
146
|
+
async function verifyContinuationWorktree(git, repoDir, handle, target) {
|
|
147
|
+
if (handle.branch !== target.branch) {
|
|
148
|
+
throw new PrContinuationIsolationError(`PR branch ${target.branch} resolved to mounted branch ${handle.branch}; free the stale worktree and retry`);
|
|
149
|
+
}
|
|
150
|
+
const checkedOut = await git.run(['rev-parse', '--abbrev-ref', 'HEAD'], handle.worktreePath);
|
|
151
|
+
const actualBranch = checkedOut.code === 0 ? checkedOut.stdout.trim() : '';
|
|
152
|
+
if (actualBranch !== target.branch) {
|
|
153
|
+
throw new PrContinuationIsolationError(`worktree ${handle.worktreePath} is on ${actualBranch || 'an unverifiable ref'}, expected PR branch ${target.branch}; fix the checkout and retry`);
|
|
154
|
+
}
|
|
155
|
+
const [head, branchRef] = await Promise.all([
|
|
156
|
+
git.run(['rev-parse', '--verify', 'HEAD'], handle.worktreePath),
|
|
157
|
+
git.run(['rev-parse', '--verify', `refs/heads/${target.branch}`], repoDir),
|
|
158
|
+
]);
|
|
159
|
+
const headSha = head.code === 0 ? head.stdout.trim() : '';
|
|
160
|
+
const branchSha = branchRef.code === 0 ? branchRef.stdout.trim() : '';
|
|
161
|
+
if (!COMMIT_SHA_RE.test(headSha) || headSha !== branchSha) {
|
|
162
|
+
throw new PrContinuationIsolationError(`worktree HEAD does not match refs/heads/${target.branch}; reconcile the PR branch and retry`);
|
|
163
|
+
}
|
|
164
|
+
return headSha;
|
|
165
|
+
}
|
|
166
|
+
async function readHeadSha(git, cwd) {
|
|
167
|
+
try {
|
|
168
|
+
const result = await git.run(['rev-parse', '--verify', 'HEAD'], cwd);
|
|
169
|
+
const sha = result.code === 0 ? result.stdout.trim() : '';
|
|
170
|
+
return COMMIT_SHA_RE.test(sha) ? sha : null;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function branchHasDelta(git, cwd, baseRef) {
|
|
177
|
+
try {
|
|
178
|
+
const result = await git.run(['diff', '--quiet', `${baseRef}...HEAD`, '--'], cwd);
|
|
179
|
+
if (result.code === 0)
|
|
180
|
+
return false;
|
|
181
|
+
if (result.code === 1)
|
|
182
|
+
return true;
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function errorDetail(err) {
|
|
190
|
+
return err instanceof Error ? err.message : String(err);
|
|
191
|
+
}
|
|
192
|
+
function aggregateImplementationOutcome(results) {
|
|
193
|
+
const succeeded = results.filter((result) => result.implementationOutcome === 'succeeded').length;
|
|
194
|
+
if (succeeded === results.length && results.length > 0)
|
|
195
|
+
return 'succeeded';
|
|
196
|
+
if (succeeded > 0)
|
|
197
|
+
return 'partially_succeeded';
|
|
198
|
+
return results.length > 0 ? 'failed' : 'unknown';
|
|
199
|
+
}
|
|
200
|
+
function branchRecords(results) {
|
|
201
|
+
return results.flatMap((result) => result.run.ticketIds.map((ticketId) => ({
|
|
202
|
+
ticketId,
|
|
203
|
+
branch: result.run.handle.branch,
|
|
204
|
+
// Legacy consumers interpret this as delivery eligibility, not engine truth.
|
|
205
|
+
succeeded: result.deliveryOutcome === 'ready',
|
|
206
|
+
runId: result.run.runId,
|
|
207
|
+
implementationOutcome: result.implementationOutcome,
|
|
208
|
+
deliveryOutcome: result.deliveryOutcome,
|
|
209
|
+
initialSha: result.initialSha,
|
|
210
|
+
finalSha: result.finalSha,
|
|
211
|
+
...(result.changed === undefined ? {} : { changed: result.changed }),
|
|
212
|
+
failureCode: result.failureCode ?? null,
|
|
213
|
+
branchOwnership: result.run.branchOwnership,
|
|
214
|
+
})));
|
|
215
|
+
}
|
|
128
216
|
/**
|
|
129
217
|
* Launch the rail's tickets in isolated worktrees + schedule the merge-back.
|
|
130
|
-
* Returns the loop run ids. Throws if worktree allocation fails
|
|
131
|
-
*
|
|
132
|
-
*
|
|
218
|
+
* Returns the loop run ids. Throws if worktree allocation fails, but only after
|
|
219
|
+
* tearing down owned partial allocation. A fresh launch may degrade to shared
|
|
220
|
+
* cwd in the router; a PR continuation raises PrContinuationIsolationError and
|
|
221
|
+
* must fail closed because shared cwd cannot prove which branch receives work.
|
|
133
222
|
*/
|
|
134
223
|
async function launchIsolatedRail(input, io = {}) {
|
|
135
224
|
const { ctx, railIndex, ticketIds, loopId, loopName, loopGraph, provider, model, effort } = input;
|
|
@@ -151,23 +240,21 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
151
240
|
// every await and late-joining clients hydrate mid-build via GET /rails —
|
|
152
241
|
// and re-broadcast the durable rail.pr_state snapshot on every mutation.
|
|
153
242
|
let prDeliveryId = null;
|
|
154
|
-
|
|
155
|
-
if (!prDeliveryId)
|
|
156
|
-
return;
|
|
157
|
-
const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, prDeliveryId);
|
|
158
|
-
if (row)
|
|
159
|
-
ctx.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(ctx.project.id, (0, rail_pr_store_1.toPrDeliverySnapshot)(row)));
|
|
160
|
-
};
|
|
243
|
+
let supersededDelivery = null;
|
|
161
244
|
// Origin-conversation card sync: when the launch came from an agent-chat
|
|
162
245
|
// conversation, mirror the row's state into its inline decision card —
|
|
163
246
|
// 'post' at launch (the user sees "working in an isolated worktree" from
|
|
164
247
|
// second zero), 'update' on every later transition (settle, alloc-failure).
|
|
165
248
|
// Null-safe: registry empty in tests / disabled builds; update falls back to
|
|
166
249
|
// post when no card exists yet.
|
|
167
|
-
const
|
|
168
|
-
|
|
250
|
+
const broadcastDeliveryState = (deliveryId) => {
|
|
251
|
+
const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, deliveryId);
|
|
252
|
+
if (!row)
|
|
169
253
|
return;
|
|
170
|
-
|
|
254
|
+
ctx.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(ctx.project.id, (0, rail_pr_store_1.toPrDeliverySnapshot)(row)));
|
|
255
|
+
};
|
|
256
|
+
const syncDeliveryCard = (deliveryId, verb) => {
|
|
257
|
+
const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, deliveryId);
|
|
171
258
|
if (!row?.origin_conversation_id)
|
|
172
259
|
return;
|
|
173
260
|
const mgr = (0, agent_chat_registry_1.getAgentChatManager)();
|
|
@@ -179,11 +266,47 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
179
266
|
else
|
|
180
267
|
mgr.updatePrDecisionCard(row.origin_conversation_id, envelope);
|
|
181
268
|
};
|
|
269
|
+
const emitPrDeliveryState = (deliveryId, verb) => {
|
|
270
|
+
broadcastDeliveryState(deliveryId);
|
|
271
|
+
syncDeliveryCard(deliveryId, verb);
|
|
272
|
+
};
|
|
273
|
+
const broadcastPrState = () => {
|
|
274
|
+
if (prDeliveryId)
|
|
275
|
+
broadcastDeliveryState(prDeliveryId);
|
|
276
|
+
};
|
|
277
|
+
const syncOriginCard = (verb) => {
|
|
278
|
+
if (prDeliveryId)
|
|
279
|
+
syncDeliveryCard(prDeliveryId, verb);
|
|
280
|
+
};
|
|
281
|
+
const closeFailedGeneration = (err) => {
|
|
282
|
+
if (!prDeliveryId || (0, rail_pr_store_1.getPrDelivery)(ctx.db, prDeliveryId)?.decision !== 'building')
|
|
283
|
+
return;
|
|
284
|
+
const closed = supersededDelivery
|
|
285
|
+
? (0, rail_pr_store_1.failPrDeliveryAndRestoreSuperseded)(ctx.db, prDeliveryId, supersededDelivery)
|
|
286
|
+
: (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'discarded', {
|
|
287
|
+
implementationOutcome: 'unknown',
|
|
288
|
+
deliveryOutcome: 'blocked',
|
|
289
|
+
statusCode: 'delivery_failed',
|
|
290
|
+
statusDetail: errorDetail(err),
|
|
291
|
+
});
|
|
292
|
+
if (!closed)
|
|
293
|
+
return;
|
|
294
|
+
try {
|
|
295
|
+
emitPrDeliveryState(prDeliveryId, 'update');
|
|
296
|
+
}
|
|
297
|
+
catch { /* durable row is authoritative */ }
|
|
298
|
+
if (supersededDelivery) {
|
|
299
|
+
try {
|
|
300
|
+
emitPrDeliveryState(supersededDelivery.id, 'update');
|
|
301
|
+
}
|
|
302
|
+
catch { /* durable row is authoritative */ }
|
|
303
|
+
}
|
|
304
|
+
};
|
|
182
305
|
// Units of isolation: `all` → ONE unit covering every ticket (one worktree, one
|
|
183
306
|
// run — the batch pipeline handles the tickets internally); `per-ticket` → one
|
|
184
307
|
// unit per ticket. The branch/ledger key is the unit's primary (first) ticket.
|
|
185
308
|
const scope = input.scope ?? 'per-ticket';
|
|
186
|
-
|
|
309
|
+
let units = scope === 'all'
|
|
187
310
|
? [{ ticketId: ticketIds[0], ticketIds: [...ticketIds] }]
|
|
188
311
|
: ticketIds.map((id) => ({ ticketId: id, ticketIds: [id] }));
|
|
189
312
|
// Effective artifact root for the per-worktree overlay: a RELOCATED project's
|
|
@@ -231,187 +354,319 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
231
354
|
let integration;
|
|
232
355
|
let launchContinuation = null;
|
|
233
356
|
const allocated = [];
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
ctx.broadcast({ type: 'rail.fetch_degraded', projectId: ctx.project.id, railIndex, warning: worktreeBaseRef.warning });
|
|
264
|
-
}
|
|
265
|
-
catch { /* non-fatal, mirrors notifyOverlayDegraded's broadcast guard below */ }
|
|
266
|
-
}
|
|
267
|
-
// Active-PR continuation: if a ticket is already parked on_review with a
|
|
268
|
-
// matching OPEN GitHub PR (or a Jira-linked in_progress ticket explicitly
|
|
269
|
-
// matches an open PR, covering unmapped Jira "Review" statuses), run the next
|
|
270
|
-
// pass directly on that PR's head branch. Fresh tickets stay on the normal
|
|
271
|
-
// integration-base path. Keep this deliberately single-target for now: one
|
|
272
|
-
// rail delivery row can represent one PR URL, so multi-PR batches continue to
|
|
273
|
-
// use the existing new-work flow instead of silently mixing PRs.
|
|
274
|
-
const continuationTargets = await (0, active_pr_continuation_1.resolveActivePrContinuationTargets)({
|
|
275
|
-
db: ctx.db,
|
|
276
|
-
git,
|
|
277
|
-
exec,
|
|
278
|
-
repoDir: baseRepo,
|
|
279
|
-
ticketIds: units.map((u) => u.ticketId),
|
|
280
|
-
integrationBranch: integration.branch,
|
|
281
|
-
fetchOk: fetchResult.ok,
|
|
282
|
-
getTicketSpec: (ticketId) => {
|
|
357
|
+
try {
|
|
358
|
+
await (0, repo_lock_1.withRepoLock)(baseRepo, async () => {
|
|
359
|
+
// Bring the repo's remote-tracking refs up to date BEFORE resolving the
|
|
360
|
+
// integration branch or allocating any worktree — otherwise `git worktree
|
|
361
|
+
// add -b <branch> <path> <bare-name>` resolves against whatever (possibly
|
|
362
|
+
// stale) commit the user's LOCAL branch happens to be at. `fetchOrigin` only
|
|
363
|
+
// ever touches `refs/remotes/origin/*`; it never mutates the checked-out
|
|
364
|
+
// branch/working tree. De-duped per repo for a short TTL so a "Launch all"
|
|
365
|
+
// batch (N independent launch requests for the same repo, serialized by
|
|
366
|
+
// this same withRepoLock) performs one real fetch, not one per rail. A
|
|
367
|
+
// failed fetch (no network / no remote / auth error) never blocks the
|
|
368
|
+
// launch — resolveWorktreeBaseRef below degrades to the local ref.
|
|
369
|
+
const fetchResult = await (0, integration_branch_1.fetchOrigin)(git, baseRepo);
|
|
370
|
+
// Resolve the project's designated integration branch ONCE, and branch every
|
|
371
|
+
// ticket's worktree off it (not the ambient HEAD). Empty setting → auto-resolve
|
|
372
|
+
// (repo default → HEAD fallback). See server/integration-branch.ts.
|
|
373
|
+
integration = await (0, integration_branch_1.resolveIntegrationBranch)(git, {
|
|
374
|
+
repoDir: baseRepo,
|
|
375
|
+
projectSetting: (0, db_1.getProjectSettings)(ctx.db).integrationBranch,
|
|
376
|
+
});
|
|
377
|
+
// Prefer the freshly-fetched remote-tracking ref (origin/<branch>) over the
|
|
378
|
+
// bare local name for repo-default/project-setting sources — see
|
|
379
|
+
// resolveWorktreeBaseRef for the exact fallback policy. `explicit` is left
|
|
380
|
+
// completely untouched (rare, launch-time-chosen override).
|
|
381
|
+
const worktreeBaseRef = await (0, integration_branch_1.resolveWorktreeBaseRef)(git, {
|
|
382
|
+
repoDir: baseRepo, integration, fetchOk: fetchResult.ok,
|
|
383
|
+
});
|
|
384
|
+
if (worktreeBaseRef.warning) {
|
|
385
|
+
console.warn(`[rail-isolated] ${worktreeBaseRef.warning} (repo ${baseRepo})`);
|
|
283
386
|
try {
|
|
284
|
-
|
|
387
|
+
ctx.broadcast({ type: 'rail.fetch_degraded', projectId: ctx.project.id, railIndex, warning: worktreeBaseRef.warning });
|
|
285
388
|
}
|
|
286
|
-
catch {
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
const uniqueContinuationKeys = new Set(units
|
|
292
|
-
.map((u) => continuationTargets.get(u.ticketId))
|
|
293
|
-
.filter((t) => !!t)
|
|
294
|
-
.map((t) => `${t.prUrl ?? ''}\n${t.branch}`));
|
|
295
|
-
if (uniqueContinuationKeys.size === 1 && units.every((u) => continuationTargets.has(u.ticketId))) {
|
|
296
|
-
launchContinuation = continuationTargets.get(units[0].ticketId) ?? null;
|
|
297
|
-
}
|
|
298
|
-
if (prMode) {
|
|
299
|
-
prDeliveryId = (0, rail_pr_store_1.createPrDelivery)(ctx.db, {
|
|
300
|
-
railIndex,
|
|
301
|
-
loopId,
|
|
302
|
-
railKey: `${railIndex}-${loopId}`,
|
|
303
|
-
ticketIds: [...ticketIds],
|
|
304
|
-
baseBranch: launchContinuation?.baseBranch ?? integration.branch,
|
|
305
|
-
loopName,
|
|
306
|
-
originSurface: input.originSurface ?? 'dashboard',
|
|
307
|
-
originConversationId: input.originConversationId ?? null,
|
|
308
|
-
}).id;
|
|
309
|
-
input.onPrDeliveryCreated?.(prDeliveryId);
|
|
310
|
-
if (launchContinuation) {
|
|
311
|
-
(0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'building', {
|
|
312
|
-
branch: launchContinuation.branch,
|
|
313
|
-
prUrl: launchContinuation.prUrl,
|
|
314
|
-
prNumber: launchContinuation.prNumber,
|
|
315
|
-
prState: 'pr-created',
|
|
316
|
-
});
|
|
389
|
+
catch { /* non-fatal, mirrors notifyOverlayDegraded's broadcast guard below */ }
|
|
317
390
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const continuation = launchContinuation && continuationTargets.get(ticketId);
|
|
331
|
-
if (continuation) {
|
|
332
|
-
takenBranches.add(continuation.branch);
|
|
333
|
-
return continuation.branch;
|
|
334
|
-
}
|
|
335
|
-
const preferred = (0, pr_naming_1.ticketBranchName)(unitNamingInput(ctx, ticketId));
|
|
336
|
-
const branch = (0, pr_naming_1.resolveCollisionFreeName)(preferred, {
|
|
337
|
-
taken: (name) => takenBranches.has(name) && !(0, rail_worktrees_store_1.railWorktreeBranchExistsForTicket)(ctx.db, ticketId, name),
|
|
338
|
-
reserved: [integration.branch],
|
|
339
|
-
}) ?? (0, worktree_manager_1.worktreeBranch)(slug, ticketId);
|
|
340
|
-
takenBranches.add(branch);
|
|
341
|
-
return branch;
|
|
342
|
-
};
|
|
343
|
-
try {
|
|
344
|
-
for (const unit of units) {
|
|
345
|
-
const continuationTarget = launchContinuation ? continuationTargets.get(unit.ticketId) ?? null : null;
|
|
346
|
-
const handle = await create(git, {
|
|
391
|
+
// Active-PR continuation: if a ticket is already parked on_review with a
|
|
392
|
+
// matching OPEN GitHub PR (or a Jira-linked in_progress ticket explicitly
|
|
393
|
+
// matches an open PR, covering unmapped Jira "Review" statuses), run the next
|
|
394
|
+
// pass directly on that PR's head branch. Fresh tickets stay on the normal
|
|
395
|
+
// integration-base path. Keep this deliberately single-target for now: one
|
|
396
|
+
// rail delivery row can represent one PR URL, so multi-PR batches continue to
|
|
397
|
+
// use the existing new-work flow instead of silently mixing PRs.
|
|
398
|
+
const continuationTargets = prMode
|
|
399
|
+
? await (0, active_pr_continuation_1.resolveActivePrContinuationTargets)({
|
|
400
|
+
db: ctx.db,
|
|
401
|
+
git,
|
|
402
|
+
exec,
|
|
347
403
|
repoDir: baseRepo,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
404
|
+
ticketIds: units.map((u) => u.ticketId),
|
|
405
|
+
integrationBranch: integration.branch,
|
|
406
|
+
fetchOk: fetchResult.ok,
|
|
407
|
+
getTicketSpec: (ticketId) => {
|
|
408
|
+
try {
|
|
409
|
+
return ctx.getTicketSpec(ticketId);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
})
|
|
416
|
+
: new Map();
|
|
417
|
+
const uniqueContinuationKeys = new Set(units
|
|
418
|
+
.map((u) => continuationTargets.get(u.ticketId))
|
|
419
|
+
.filter((t) => !!t)
|
|
420
|
+
.map((t) => `${t.prUrl ?? ''}\n${t.branch}`));
|
|
421
|
+
if (uniqueContinuationKeys.size === 1 && units.every((u) => continuationTargets.has(u.ticketId))) {
|
|
422
|
+
launchContinuation = continuationTargets.get(units[0].ticketId) ?? null;
|
|
423
|
+
}
|
|
424
|
+
let staleRequiredPr = false;
|
|
425
|
+
if (input.requiredPrContinuation && (!launchContinuation ||
|
|
426
|
+
launchContinuation.branch !== input.requiredPrContinuation.branch ||
|
|
427
|
+
launchContinuation.prUrl !== input.requiredPrContinuation.prUrl ||
|
|
428
|
+
launchContinuation.prNumber !== input.requiredPrContinuation.prNumber)) {
|
|
429
|
+
const observed = await (0, pr_lifecycle_1.observePrLifecycle)(exec, baseRepo, input.requiredPrContinuation.prUrl);
|
|
430
|
+
if (observed.ok && (observed.state === 'CLOSED' || observed.state === 'MERGED')) {
|
|
431
|
+
// The router snapshot was stale, but this is not an isolation failure and
|
|
432
|
+
// must not trap the user in a 409 loop. Supersede that generation under
|
|
433
|
+
// the same expected-active CAS, then allocate a genuinely fresh branch.
|
|
434
|
+
staleRequiredPr = true;
|
|
435
|
+
launchContinuation = null;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
const reason = observed.ok
|
|
439
|
+
? `the PR no longer matches its recorded head/base (${observed.state})`
|
|
440
|
+
: `GitHub lifecycle could not be confirmed (${observed.detail})`;
|
|
441
|
+
throw new PrContinuationIsolationError(`cannot safely continue PR branch ${input.requiredPrContinuation.branch}: ${reason}; retry after the PR is verifiably open`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// A git branch can only be checked out by one worktree. When several rail
|
|
445
|
+
// tickets all continue the same PR, make them one atomic batch run in one
|
|
446
|
+
// checkout instead of attempting N worktrees for the same branch (the second
|
|
447
|
+
// `git worktree add` necessarily fails). `ticketIds` preserves the full rail
|
|
448
|
+
// scope for {{spec.ids}} / ticket commands and completion bookkeeping.
|
|
449
|
+
if (launchContinuation && units.length > 1) {
|
|
450
|
+
units = [{
|
|
451
|
+
ticketId: units[0].ticketId,
|
|
452
|
+
ticketIds: units.flatMap((unit) => unit.ticketIds),
|
|
453
|
+
}];
|
|
454
|
+
}
|
|
455
|
+
if (prMode) {
|
|
456
|
+
const generation = (0, rail_pr_store_1.createPrDeliveryGeneration)(ctx.db, {
|
|
457
|
+
railIndex,
|
|
458
|
+
loopId,
|
|
459
|
+
railKey: `${railIndex}-${loopId}`,
|
|
460
|
+
ticketIds: [...ticketIds],
|
|
461
|
+
baseBranch: launchContinuation?.baseBranch ?? integration.branch,
|
|
462
|
+
loopName,
|
|
463
|
+
originSurface: input.originSurface ?? 'dashboard',
|
|
464
|
+
originConversationId: input.originConversationId ?? null,
|
|
465
|
+
isContinuation: Boolean(launchContinuation),
|
|
466
|
+
}, input.requiredPrContinuation
|
|
467
|
+
? { id: input.requiredPrContinuation.deliveryId, decision: input.requiredPrContinuation.decision }
|
|
468
|
+
: null);
|
|
469
|
+
prDeliveryId = generation.delivery.id;
|
|
470
|
+
supersededDelivery = generation.superseded;
|
|
471
|
+
input.onPrDeliveryCreated?.(prDeliveryId);
|
|
472
|
+
if (launchContinuation) {
|
|
473
|
+
(0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'building', {
|
|
474
|
+
branch: launchContinuation.branch,
|
|
475
|
+
prUrl: launchContinuation.prUrl,
|
|
476
|
+
prNumber: launchContinuation.prNumber,
|
|
477
|
+
prState: 'pr-created',
|
|
478
|
+
isContinuation: true,
|
|
479
|
+
supersedesDeliveryId: supersededDelivery?.id ?? null,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
else if (staleRequiredPr) {
|
|
483
|
+
// This replacement deliberately starts a new PR lineage: the stale PR
|
|
484
|
+
// remains historical and its head is never borrowed. Ownership was
|
|
485
|
+
// already persisted atomically by createPrDeliveryGeneration; this
|
|
486
|
+
// transition only adds the user-facing explanation.
|
|
487
|
+
(0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'building', {
|
|
488
|
+
supersedesDeliveryId: supersededDelivery?.id ?? null,
|
|
489
|
+
statusDetail: 'The previous PR was no longer open; this implementation will use a fresh branch and PR.',
|
|
364
490
|
});
|
|
365
|
-
overlayExcludes = res.createdPaths;
|
|
366
|
-
if (res.warnings.length > 0)
|
|
367
|
-
notifyOverlayDegraded(unit.ticketId, res.warnings);
|
|
368
491
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
492
|
+
if (supersededDelivery) {
|
|
493
|
+
try {
|
|
494
|
+
emitPrDeliveryState(supersededDelivery.id, 'update');
|
|
495
|
+
}
|
|
496
|
+
catch { /* durable supersession is authoritative */ }
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
broadcastPrState();
|
|
500
|
+
}
|
|
501
|
+
catch { /* hydration can recover */ }
|
|
502
|
+
try {
|
|
503
|
+
syncOriginCard('post');
|
|
373
504
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
505
|
+
catch { /* hydration can recover */ }
|
|
506
|
+
}
|
|
507
|
+
// Conventional branch naming (pr-naming): `<type>/<ref>-<kebab-title>`, the
|
|
508
|
+
// Jira key prevailing over the local id when linked. Collisions with foreign
|
|
509
|
+
// branches suffix `-2`… (bounded); a branch a PRIOR rail run allocated for
|
|
510
|
+
// the SAME ticket is ours to resume, so it is NOT treated as a collision.
|
|
511
|
+
// The integration branch is never used; exhaustion falls back to the legacy
|
|
512
|
+
// `sr/<slug>/ticket-<id>` name. Snapshot under the lock so concurrent
|
|
513
|
+
// launches see each other's just-created branches.
|
|
514
|
+
const takenBranches = await (0, worktree_manager_1.listLocalBranches)(git, baseRepo);
|
|
515
|
+
const preexistingBranches = new Set(takenBranches);
|
|
516
|
+
const unitBranchName = (ticketId) => {
|
|
517
|
+
const continuation = launchContinuation && continuationTargets.get(ticketId);
|
|
518
|
+
if (continuation) {
|
|
519
|
+
takenBranches.add(continuation.branch);
|
|
520
|
+
return { branch: continuation.branch, ownership: 'borrowed-pr' };
|
|
521
|
+
}
|
|
522
|
+
const preferred = (0, pr_naming_1.ticketBranchName)(unitNamingInput(ctx, ticketId));
|
|
523
|
+
const branch = (0, pr_naming_1.resolveCollisionFreeName)(preferred, {
|
|
524
|
+
taken: (name) => takenBranches.has(name) && !(0, rail_worktrees_store_1.railWorktreeBranchExistsForTicket)(ctx.db, ticketId, name),
|
|
525
|
+
reserved: [integration.branch],
|
|
526
|
+
}) ?? (0, worktree_manager_1.worktreeBranch)(slug, ticketId);
|
|
527
|
+
takenBranches.add(branch);
|
|
528
|
+
return { branch, ownership: preexistingBranches.has(branch) ? 'preexisting' : 'created' };
|
|
529
|
+
};
|
|
530
|
+
try {
|
|
531
|
+
for (const unit of units) {
|
|
532
|
+
const continuationTarget = launchContinuation ? continuationTargets.get(unit.ticketId) ?? null : null;
|
|
533
|
+
const branchPlan = unitBranchName(unit.ticketId);
|
|
534
|
+
let handle = null;
|
|
535
|
+
let branchOwnership = branchPlan.ownership;
|
|
536
|
+
let worktreeOwnership = 'created';
|
|
537
|
+
let initialSha = null;
|
|
380
538
|
try {
|
|
381
|
-
|
|
539
|
+
handle = await create(git, {
|
|
540
|
+
repoDir: baseRepo,
|
|
541
|
+
worktreesRoot,
|
|
542
|
+
slug,
|
|
543
|
+
ticketId: unit.ticketId,
|
|
544
|
+
baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
|
|
545
|
+
branch: branchPlan.branch,
|
|
546
|
+
refreshFromBaseRef: Boolean(continuationTarget?.baseRef),
|
|
547
|
+
});
|
|
548
|
+
worktreeOwnership = handle.worktreeCreated === false ? 'preexisting' : 'created';
|
|
549
|
+
if (!continuationTarget && (handle.branch !== branchPlan.branch || handle.branchCreated === false)) {
|
|
550
|
+
branchOwnership = 'preexisting';
|
|
551
|
+
}
|
|
552
|
+
if (continuationTarget) {
|
|
553
|
+
initialSha = await verifyContinuationWorktree(git, baseRepo, handle, continuationTarget);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
initialSha = await readHeadSha(git, handle.worktreePath);
|
|
557
|
+
}
|
|
558
|
+
// Per-run overlay: merge-link the framework surface the checkout didn't
|
|
559
|
+
// bring into the worktree (idempotent; resume-safe via its manifest).
|
|
560
|
+
let overlayExcludes = [];
|
|
561
|
+
try {
|
|
562
|
+
const res = overlay({
|
|
563
|
+
worktreePath: handle.worktreePath,
|
|
564
|
+
sourceRoot: overlaySourceRoot,
|
|
565
|
+
providerDir: overlayProviderDir,
|
|
566
|
+
instructionsFilename: overlayInstructions,
|
|
567
|
+
});
|
|
568
|
+
overlayExcludes = res.createdPaths;
|
|
569
|
+
if (res.warnings.length > 0)
|
|
570
|
+
notifyOverlayDegraded(unit.ticketId, res.warnings);
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
// applyWorktreeOverlay never throws; this guards injected test doubles
|
|
574
|
+
// and future edits — the spawn proceeds regardless.
|
|
575
|
+
notifyOverlayDegraded(unit.ticketId, [err instanceof Error ? err.message : String(err)]);
|
|
576
|
+
}
|
|
577
|
+
// Code-Explorer provenance: freeze the worktree's pre-run state (HEAD sha
|
|
578
|
+
// + the overlay's untracked set) so the settle diff attributes exactly
|
|
579
|
+
// the run's own writes. Best-effort — a snapshot failure only loses
|
|
580
|
+
// provenance, never the run.
|
|
581
|
+
let provenanceSnapshot = null;
|
|
582
|
+
if ((0, feature_flags_1.isCodeExplorerEnabled)()) {
|
|
583
|
+
try {
|
|
584
|
+
provenanceSnapshot = (io.snapshot ?? file_provenance_1.snapshotWorkingTree)(handle.worktreePath);
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
console.warn(`[rail-isolated] provenance snapshot failed: ${err.message}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const runId = (0, ids_1.newId)();
|
|
591
|
+
const ledgerId = (0, ids_1.newId)();
|
|
592
|
+
(0, rail_worktrees_store_1.createRailWorktree)(ctx.db, {
|
|
593
|
+
id: ledgerId, railIndex, ticketId: unit.ticketId, runId,
|
|
594
|
+
branch: handle.branch, worktreePath: handle.worktreePath,
|
|
595
|
+
});
|
|
596
|
+
allocated.push({
|
|
597
|
+
ticketId: unit.ticketId,
|
|
598
|
+
ticketIds: unit.ticketIds,
|
|
599
|
+
runId,
|
|
600
|
+
ledgerId,
|
|
601
|
+
handle,
|
|
602
|
+
overlayExcludes,
|
|
603
|
+
provenanceSnapshot,
|
|
604
|
+
continuationTarget,
|
|
605
|
+
baseRef: continuationTarget?.baseRef ?? worktreeBaseRef.baseRef,
|
|
606
|
+
initialSha,
|
|
607
|
+
branchOwnership,
|
|
608
|
+
worktreeOwnership,
|
|
609
|
+
});
|
|
382
610
|
}
|
|
383
611
|
catch (err) {
|
|
384
|
-
|
|
612
|
+
// A handle that never reached `allocated` is still our responsibility,
|
|
613
|
+
// but only unmount/delete resources this call actually created.
|
|
614
|
+
if (handle && worktreeOwnership === 'created') {
|
|
615
|
+
await remove(git, {
|
|
616
|
+
repoDir: baseRepo,
|
|
617
|
+
worktreePath: handle.worktreePath,
|
|
618
|
+
branch: handle.branch,
|
|
619
|
+
deleteBranch: branchOwnership === 'created',
|
|
620
|
+
}).catch(() => { });
|
|
621
|
+
}
|
|
622
|
+
throw err;
|
|
385
623
|
}
|
|
386
624
|
}
|
|
387
|
-
const runId = (0, ids_1.newId)();
|
|
388
|
-
const ledgerId = (0, ids_1.newId)();
|
|
389
|
-
(0, rail_worktrees_store_1.createRailWorktree)(ctx.db, {
|
|
390
|
-
id: ledgerId, railIndex, ticketId: unit.ticketId, runId,
|
|
391
|
-
branch: handle.branch, worktreePath: handle.worktreePath,
|
|
392
|
-
});
|
|
393
|
-
allocated.push({ ticketId: unit.ticketId, ticketIds: unit.ticketIds, runId, ledgerId, handle, overlayExcludes, provenanceSnapshot, continuationTarget });
|
|
394
625
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
626
|
+
catch (err) {
|
|
627
|
+
for (const a of allocated) {
|
|
628
|
+
if (a.worktreeOwnership === 'created') {
|
|
629
|
+
await remove(git, {
|
|
630
|
+
repoDir: baseRepo,
|
|
631
|
+
worktreePath: a.handle.worktreePath,
|
|
632
|
+
branch: a.handle.branch,
|
|
633
|
+
deleteBranch: a.branchOwnership === 'created',
|
|
634
|
+
}).catch(() => { });
|
|
635
|
+
}
|
|
636
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, 'failed');
|
|
637
|
+
}
|
|
638
|
+
// Close the failed generation. A continuation atomically restores the exact
|
|
639
|
+
// predecessor it superseded; a stale concurrent generation wins instead of
|
|
640
|
+
// letting this catch overwrite it.
|
|
641
|
+
closeFailedGeneration(err);
|
|
642
|
+
if (launchContinuation) {
|
|
643
|
+
if (err instanceof PrContinuationIsolationError)
|
|
644
|
+
throw err;
|
|
645
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
646
|
+
throw new PrContinuationIsolationError(`cannot allocate a verified worktree for PR branch ${launchContinuation.branch}: ${detail}; free the branch checkout and retry`);
|
|
647
|
+
}
|
|
648
|
+
throw err;
|
|
408
649
|
}
|
|
409
|
-
|
|
650
|
+
}); // withRepoLock — allocation done; the fan-out below runs in parallel.
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
closeFailedGeneration(err);
|
|
654
|
+
// Auto-detected GitHub continuations do not have a router-side delivery
|
|
655
|
+
// contract yet. Preserve the fail-closed signal for errors that happen
|
|
656
|
+
// after detection but before the allocation catch (for example DB setup).
|
|
657
|
+
// TypeScript does not observe assignments performed inside withRepoLock's
|
|
658
|
+
// async callback, so retain the explicit runtime type at this boundary.
|
|
659
|
+
const detectedContinuation = launchContinuation;
|
|
660
|
+
if (detectedContinuation && !(err instanceof PrContinuationIsolationError)) {
|
|
661
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
662
|
+
throw new PrContinuationIsolationError(`cannot prepare a verified worktree for PR branch ${detectedContinuation.branch}: ${detail}`);
|
|
410
663
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
//
|
|
414
|
-
//
|
|
664
|
+
throw err;
|
|
665
|
+
}
|
|
666
|
+
// 2. Fan out: one loop run per independent worktree. Same-PR tickets form one
|
|
667
|
+
// atomic batch run because git cannot mount one branch in multiple linked
|
|
668
|
+
// worktrees. In PR mode a COMPLETED run's tickets park at on_review (the
|
|
669
|
+
// user decides done vs discard via the PR flow); failures ignore the field.
|
|
415
670
|
const runFinishedOpts = { ticketCompletionStatus: prMode ? 'on_review' : 'done' };
|
|
416
671
|
// Code-Explorer provenance at settle: diff the worktree against its pre-run
|
|
417
672
|
// snapshot and record file_provenance/story rows (keyed by runId), exactly
|
|
@@ -427,58 +682,198 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
427
682
|
if (provenanceRecorded.has(a.runId))
|
|
428
683
|
return;
|
|
429
684
|
provenanceRecorded.add(a.runId);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
685
|
+
try {
|
|
686
|
+
recordProvenance({
|
|
687
|
+
db: ctx.db,
|
|
688
|
+
projectId: ctx.project.id,
|
|
689
|
+
runId: a.runId,
|
|
690
|
+
ticketId: a.ticketId,
|
|
691
|
+
repoDir: a.handle.worktreePath,
|
|
692
|
+
snapshot: a.provenanceSnapshot,
|
|
693
|
+
broadcast: (msg) => ctx.broadcast(msg),
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
catch (err) {
|
|
697
|
+
console.warn(`[rail-isolated] provenance recording failed for ${a.runId}: ${errorDetail(err)}`);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
const markWorktree = (a, state) => {
|
|
701
|
+
try {
|
|
702
|
+
return (0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, state)
|
|
703
|
+
? null
|
|
704
|
+
: `worktree ledger ${a.ledgerId} no longer exists`;
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
return errorDetail(err);
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
/** This function resolves for every allocated unit. Engine truth is captured
|
|
711
|
+
* before any delivery effect and is the only value sent to the deferred
|
|
712
|
+
* terminal callback. Delivery failures become blocked evidence instead. */
|
|
713
|
+
const settleAllocatedRun = async (a, enginePromise) => {
|
|
714
|
+
let actualOutcome = 'failed';
|
|
715
|
+
let engineFailure;
|
|
716
|
+
try {
|
|
717
|
+
const result = await enginePromise;
|
|
718
|
+
actualOutcome = result.outcome;
|
|
719
|
+
}
|
|
720
|
+
catch (err) {
|
|
721
|
+
engineFailure = errorDetail(err);
|
|
722
|
+
console.error(`[rail-isolated] loop run ${a.runId} rejected: ${engineFailure}`);
|
|
723
|
+
}
|
|
724
|
+
const implementationOutcome = actualOutcome === 'success' ? 'succeeded' : 'failed';
|
|
725
|
+
recordRunProvenance(a);
|
|
726
|
+
let callbackFailure;
|
|
727
|
+
try {
|
|
728
|
+
ctx.onLoopRunFinished(a.runId, actualOutcome, runFinishedOpts);
|
|
729
|
+
}
|
|
730
|
+
catch (err) {
|
|
731
|
+
callbackFailure = errorDetail(err);
|
|
732
|
+
console.error(`[rail-isolated] terminal callback failed for ${a.runId}: ${callbackFailure}`);
|
|
733
|
+
}
|
|
734
|
+
let commit;
|
|
735
|
+
try {
|
|
736
|
+
commit = await (0, worktree_manager_1.commitWorktreeAndVerify)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId, implementationOutcome === 'failed'), a.overlayExcludes);
|
|
737
|
+
}
|
|
738
|
+
catch (err) {
|
|
739
|
+
commit = { staged: false, committed: false, clean: false, dirty: [], error: errorDetail(err) };
|
|
740
|
+
}
|
|
741
|
+
if (!commit.clean) {
|
|
742
|
+
const detail = commitFailureSummary(commit);
|
|
743
|
+
console.error(`[rail-isolated] run ${a.runId} left an unsafe worktree: ${detail}`);
|
|
744
|
+
markWorktree(a, 'needs-review');
|
|
745
|
+
return {
|
|
746
|
+
run: a,
|
|
747
|
+
implementationOutcome,
|
|
748
|
+
deliveryOutcome: 'blocked',
|
|
749
|
+
initialSha: a.initialSha,
|
|
750
|
+
finalSha: await readHeadSha(git, a.handle.worktreePath),
|
|
751
|
+
failureCode: 'commit_failed',
|
|
752
|
+
failureDetail: detail,
|
|
753
|
+
safeToRelease: false,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
let finalSha = null;
|
|
757
|
+
let verificationFailure;
|
|
758
|
+
if (a.continuationTarget) {
|
|
759
|
+
try {
|
|
760
|
+
finalSha = await verifyContinuationWorktree(git, baseRepo, a.handle, a.continuationTarget);
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
verificationFailure = errorDetail(err);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
finalSha = await readHeadSha(git, a.handle.worktreePath);
|
|
768
|
+
}
|
|
769
|
+
const changed = commit.committed || Boolean(a.initialSha && finalSha && a.initialSha !== finalSha);
|
|
770
|
+
// A successful commit without a provable object is not retryable or safe to
|
|
771
|
+
// detach. A continuation additionally requires HEAD == refs/heads/<branch>.
|
|
772
|
+
if (verificationFailure || !finalSha || (!commit.committed && a.branchOwnership === 'created' && !a.initialSha)) {
|
|
773
|
+
const detail = verificationFailure
|
|
774
|
+
?? (!finalSha
|
|
775
|
+
? 'the final HEAD object could not be verified'
|
|
776
|
+
: 'the initial HEAD object was not captured, so a no-change result cannot be proven');
|
|
777
|
+
console.error(`[rail-isolated] run ${a.runId} finished on an unverified ref: ${detail}`);
|
|
778
|
+
markWorktree(a, 'needs-review');
|
|
779
|
+
return {
|
|
780
|
+
run: a,
|
|
781
|
+
implementationOutcome,
|
|
782
|
+
deliveryOutcome: 'blocked',
|
|
783
|
+
initialSha: a.initialSha,
|
|
784
|
+
finalSha,
|
|
785
|
+
changed,
|
|
786
|
+
failureCode: 'branch_verification_failed',
|
|
787
|
+
failureDetail: detail,
|
|
788
|
+
safeToRelease: false,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
// Failed clean units remain `building` until aggregate cleanup removes the
|
|
792
|
+
// mount and atomically terminalizes the ledger as failed. Marking them
|
|
793
|
+
// terminal first would make idempotent cleanup skip a still-mounted path.
|
|
794
|
+
const ledgerFailure = implementationOutcome === 'succeeded'
|
|
795
|
+
? (markWorktree(a, 'built') ?? undefined)
|
|
796
|
+
: undefined;
|
|
797
|
+
const settlementFailure = callbackFailure ?? ledgerFailure;
|
|
798
|
+
if (settlementFailure) {
|
|
799
|
+
markWorktree(a, 'needs-review');
|
|
800
|
+
return {
|
|
801
|
+
run: a,
|
|
802
|
+
implementationOutcome,
|
|
803
|
+
deliveryOutcome: 'blocked',
|
|
804
|
+
initialSha: a.initialSha,
|
|
805
|
+
finalSha,
|
|
806
|
+
changed,
|
|
807
|
+
failureCode: 'settlement_interrupted',
|
|
808
|
+
failureDetail: settlementFailure,
|
|
809
|
+
safeToRelease: false,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
if (implementationOutcome === 'failed') {
|
|
813
|
+
return {
|
|
814
|
+
run: a,
|
|
815
|
+
implementationOutcome,
|
|
816
|
+
deliveryOutcome: 'not_started',
|
|
817
|
+
initialSha: a.initialSha,
|
|
818
|
+
finalSha,
|
|
819
|
+
changed,
|
|
820
|
+
...(engineFailure ? { failureDetail: engineFailure } : {}),
|
|
821
|
+
safeToRelease: true,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
// A resumed non-PR branch may already contain reviewable commits from an
|
|
825
|
+
// earlier attempt, even when this iteration itself did not add one.
|
|
826
|
+
let resumedBranchDelta = null;
|
|
827
|
+
if (!changed && a.continuationTarget === null && a.branchOwnership === 'preexisting') {
|
|
828
|
+
resumedBranchDelta = await branchHasDelta(git, a.handle.worktreePath, a.baseRef);
|
|
829
|
+
if (resumedBranchDelta === null) {
|
|
830
|
+
const detail = `cannot prove whether resumed branch ${a.handle.branch} has commits ahead of ${a.baseRef}`;
|
|
831
|
+
markWorktree(a, 'needs-review');
|
|
832
|
+
return {
|
|
833
|
+
run: a,
|
|
834
|
+
implementationOutcome,
|
|
835
|
+
deliveryOutcome: 'blocked',
|
|
836
|
+
initialSha: a.initialSha,
|
|
837
|
+
finalSha,
|
|
838
|
+
changed,
|
|
839
|
+
failureCode: 'branch_verification_failed',
|
|
840
|
+
failureDetail: detail,
|
|
841
|
+
safeToRelease: false,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const noChanges = !changed && Boolean(a.initialSha && finalSha && a.initialSha === finalSha && (a.continuationTarget !== null ||
|
|
846
|
+
a.branchOwnership === 'created' ||
|
|
847
|
+
resumedBranchDelta === false));
|
|
848
|
+
return {
|
|
849
|
+
run: a,
|
|
850
|
+
implementationOutcome,
|
|
851
|
+
deliveryOutcome: noChanges ? 'no_changes' : 'ready',
|
|
852
|
+
initialSha: a.initialSha,
|
|
853
|
+
finalSha,
|
|
854
|
+
changed,
|
|
855
|
+
safeToRelease: true,
|
|
856
|
+
};
|
|
439
857
|
};
|
|
440
858
|
const runPromises = [];
|
|
441
859
|
for (const a of allocated) {
|
|
442
|
-
ctx.railLoopRuns.set(a.runId, {
|
|
860
|
+
ctx.railLoopRuns.set(a.runId, {
|
|
861
|
+
railIndex,
|
|
862
|
+
ticketIds: a.ticketIds,
|
|
863
|
+
requiresTerminalIntent: true,
|
|
864
|
+
});
|
|
443
865
|
const spec = ctx.getTicketSpec(a.ticketId);
|
|
444
|
-
const
|
|
445
|
-
.run({
|
|
866
|
+
const enginePromise = ctx.loopRunManager.run({
|
|
446
867
|
runId: a.runId, loopId, loopName, graph: loopGraph, projectId: ctx.project.id,
|
|
447
868
|
cwd: a.handle.worktreePath, repoDir: a.handle.worktreePath,
|
|
448
869
|
isolation: { branch: a.handle.branch, worktreePath: a.handle.worktreePath },
|
|
449
870
|
railIndex, ticketId: a.ticketId,
|
|
450
871
|
spec: spec ? { ...spec, ticketIds: a.ticketIds } : { ticketIds: a.ticketIds },
|
|
872
|
+
ticketCompletionStatus: runFinishedOpts.ticketCompletionStatus,
|
|
873
|
+
deferTerminalOutcome: true,
|
|
451
874
|
constants, provider, model, effort,
|
|
452
|
-
})
|
|
453
|
-
.then(async (r) => {
|
|
454
|
-
const succeeded = r.outcome === 'success';
|
|
455
|
-
recordRunProvenance(a);
|
|
456
|
-
// Commit the run's work to its branch so PR creation/merge-back can
|
|
457
|
-
// trust the branch as the durable source of truth. A "successful" loop
|
|
458
|
-
// with deliverable local changes still dirty is treated as failed
|
|
459
|
-
// instead of surfacing a Create PR card that cannot ship the work.
|
|
460
|
-
const commit = await (0, worktree_manager_1.commitWorktreeAndVerify)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId), a.overlayExcludes);
|
|
461
|
-
const deliverable = succeeded && commit.clean;
|
|
462
|
-
if (succeeded && !commit.clean) {
|
|
463
|
-
console.error(`[rail-isolated] run ${a.runId} finished success but commit verification failed: ${commitFailureSummary(commit)}`);
|
|
464
|
-
}
|
|
465
|
-
ctx.onLoopRunFinished(r.runId, deliverable ? 'success' : 'failed', runFinishedOpts);
|
|
466
|
-
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, deliverable ? 'built' : 'failed');
|
|
467
|
-
return { run: a, succeeded: deliverable };
|
|
468
|
-
})
|
|
469
|
-
.catch(async (err) => {
|
|
470
|
-
console.error('[rail-isolated] loop run failed:', err);
|
|
471
|
-
ctx.onLoopRunFinished(a.runId, 'failed', runFinishedOpts);
|
|
472
|
-
recordRunProvenance(a);
|
|
473
|
-
// Commit partial work too → durable in git → resumable on re-launch.
|
|
474
|
-
const commit = await (0, worktree_manager_1.commitWorktreeAndVerify)(git, a.handle.worktreePath, worktreeCommitMessage(ctx, a.ticketId, a.runId, true), a.overlayExcludes);
|
|
475
|
-
if (!commit.clean) {
|
|
476
|
-
console.error(`[rail-isolated] failed run ${a.runId} left uncommitted deliverable changes: ${commitFailureSummary(commit)}`);
|
|
477
|
-
}
|
|
478
|
-
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, 'failed');
|
|
479
|
-
return { run: a, succeeded: false };
|
|
480
875
|
});
|
|
481
|
-
runPromises.push(
|
|
876
|
+
runPromises.push(settleAllocatedRun(a, enginePromise));
|
|
482
877
|
try {
|
|
483
878
|
ctx.jiraSyncManager.onRailLaunch(a.ticketIds, a.runId);
|
|
484
879
|
}
|
|
@@ -500,11 +895,28 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
500
895
|
// work isolated on its branches and mark the decision row on_review —
|
|
501
896
|
// the draft PR is only created when the user clicks [Create PR] on the
|
|
502
897
|
// pr-decision endpoint (ask-first, safe-pr-review-flow).
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
898
|
+
const guardedRunPromises = runPromises.map((promise, index) => promise.catch(async (err) => {
|
|
899
|
+
const run = allocated[index];
|
|
900
|
+
const detail = errorDetail(err);
|
|
901
|
+
console.error(`[rail-isolated] unexpected settlement rejection for ${run.runId}: ${detail}`);
|
|
902
|
+
const durable = ctx.db.prepare('SELECT final_outcome FROM loop_runs WHERE id = ?').get(run.runId);
|
|
903
|
+
const implementationOutcome = durable?.final_outcome === 'success' ? 'succeeded' : 'failed';
|
|
904
|
+
markWorktree(run, 'needs-review');
|
|
905
|
+
return {
|
|
906
|
+
run,
|
|
907
|
+
implementationOutcome,
|
|
908
|
+
deliveryOutcome: 'blocked',
|
|
909
|
+
initialSha: run.initialSha,
|
|
910
|
+
finalSha: await readHeadSha(git, run.handle.worktreePath),
|
|
911
|
+
failureCode: 'settlement_interrupted',
|
|
912
|
+
failureDetail: detail,
|
|
913
|
+
safeToRelease: false,
|
|
914
|
+
};
|
|
915
|
+
}));
|
|
916
|
+
void Promise.all(guardedRunPromises).then(async (settledResults) => {
|
|
917
|
+
// Cardinality is invariant: one structured result per allocated unit. The
|
|
918
|
+
// guarded promises above synthesize a blocked record for any rejection.
|
|
919
|
+
let results = settledResults;
|
|
508
920
|
if (prMode) {
|
|
509
921
|
// Build-settle: persist the per-unit branch outcomes + this launch's
|
|
510
922
|
// worktree ledger ids on the row (the deferred create-pr / discard actions
|
|
@@ -513,71 +925,301 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
513
925
|
// keep the implementation card visible as a failed job state with run-log
|
|
514
926
|
// chips instead of silently auto-discarding or leaving it at "building".
|
|
515
927
|
const worktreeIds = allocated.map((a) => a.ledgerId);
|
|
516
|
-
|
|
517
|
-
const settledContinuation = anySucceeded ? launchContinuation : null;
|
|
928
|
+
let deliverySha = null;
|
|
518
929
|
let continuationPushFailed = false;
|
|
519
930
|
let continuationPushFailureReason = null;
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
931
|
+
let continuationNeedsNewPr = false;
|
|
932
|
+
let continuationExactPushCompleted = false;
|
|
933
|
+
let continuationRemoteIsDraft = launchContinuation?.isDraft ?? null;
|
|
934
|
+
let continuationMergedWithSha = false;
|
|
935
|
+
let continuationClosedWithSha = false;
|
|
936
|
+
let continuationLifecycleBlocked = false;
|
|
937
|
+
const unchangedContinuation = launchContinuation
|
|
938
|
+
? results.find((result) => result.run.continuationTarget && result.deliveryOutcome === 'no_changes')
|
|
939
|
+
: undefined;
|
|
940
|
+
if (unchangedContinuation?.finalSha)
|
|
941
|
+
deliverySha = unchangedContinuation.finalSha;
|
|
942
|
+
// Revalidate immediately before an existing-PR push. Settlement's SHA is
|
|
943
|
+
// immutable delivery evidence; a moved ref blocks instead of substituting
|
|
944
|
+
// a newer object behind the user's back.
|
|
945
|
+
if (launchContinuation) {
|
|
946
|
+
const candidateIndex = results.findIndex((result) => result.deliveryOutcome === 'ready' && result.run.continuationTarget !== null);
|
|
947
|
+
if (candidateIndex >= 0) {
|
|
948
|
+
const candidate = results[candidateIndex];
|
|
949
|
+
try {
|
|
950
|
+
const verified = await verifyContinuationWorktree(git, baseRepo, candidate.run.handle, candidate.run.continuationTarget);
|
|
951
|
+
if (!candidate.finalSha || verified !== candidate.finalSha) {
|
|
952
|
+
throw new PrContinuationIsolationError('PR branch moved after settlement; refusing to push an unverified object');
|
|
953
|
+
}
|
|
954
|
+
deliverySha = candidate.finalSha;
|
|
955
|
+
}
|
|
956
|
+
catch (err) {
|
|
957
|
+
const detail = errorDetail(err);
|
|
958
|
+
markWorktree(candidate.run, 'needs-review');
|
|
959
|
+
results = results.map((result, resultIndex) => resultIndex === candidateIndex
|
|
960
|
+
? {
|
|
961
|
+
...result,
|
|
962
|
+
deliveryOutcome: 'blocked',
|
|
963
|
+
failureCode: 'branch_verification_failed',
|
|
964
|
+
failureDetail: detail,
|
|
965
|
+
safeToRelease: false,
|
|
966
|
+
}
|
|
967
|
+
: result);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
const implementationOutcome = aggregateImplementationOutcome(results);
|
|
972
|
+
let ready = results.filter((result) => result.deliveryOutcome === 'ready');
|
|
973
|
+
let blocked = results.filter((result) => result.deliveryOutcome === 'blocked');
|
|
974
|
+
let noChanges = results.filter((result) => result.deliveryOutcome === 'no_changes');
|
|
975
|
+
const continuationHadReadyWork = ready.length > 0;
|
|
976
|
+
if (launchContinuation && deliverySha && (continuationHadReadyWork || noChanges.length > 0)) {
|
|
977
|
+
const beforePush = await (0, pr_lifecycle_1.observePrLifecycle)(exec, baseRepo, launchContinuation.prUrl, deliverySha);
|
|
978
|
+
if (!beforePush.ok) {
|
|
527
979
|
continuationPushFailed = true;
|
|
528
|
-
continuationPushFailureReason =
|
|
529
|
-
|
|
980
|
+
continuationPushFailureReason = `could not confirm the existing PR is open before push: ${beforePush.detail}`;
|
|
981
|
+
}
|
|
982
|
+
else if (!(0, pr_lifecycle_1.isExactOpenPr)(beforePush, launchContinuation.branch, launchContinuation.baseBranch)) {
|
|
983
|
+
if (beforePush.state === 'MERGED' && beforePush.includesExpectedSha === true) {
|
|
984
|
+
// Another actor delivered the exact object before our push. Keep the
|
|
985
|
+
// old PR attached so poll-merge can apply the terminal ticket effect.
|
|
986
|
+
continuationRemoteIsDraft = false;
|
|
987
|
+
continuationMergedWithSha = true;
|
|
988
|
+
}
|
|
989
|
+
else if (beforePush.state === 'CLOSED' && beforePush.includesExpectedSha === true && !continuationHadReadyWork) {
|
|
990
|
+
// An unchanged iteration may safely offer Reopen when the closed PR
|
|
991
|
+
// already contains its exact head. Changed work is never assumed to
|
|
992
|
+
// belong to a closed PR before we push it.
|
|
993
|
+
continuationClosedWithSha = true;
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
continuationNeedsNewPr = true;
|
|
997
|
+
continuationPushFailureReason = beforePush.includesExpectedSha === null
|
|
998
|
+
? `the previous PR is ${beforePush.state.toLowerCase()} and GitHub could not prove it included this implementation; create a new draft PR from the preserved commit`
|
|
999
|
+
: `the previous PR is ${beforePush.state.toLowerCase()} without this implementation; create a new draft PR from the preserved commit`;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
else if (!continuationHadReadyWork) {
|
|
1003
|
+
// No-change still needs lifecycle truth: the PR may have merged while
|
|
1004
|
+
// the run was active, and an advanced remote head must not be silently
|
|
1005
|
+
// dismissed as though it were the verified unchanged object.
|
|
1006
|
+
if (beforePush.headRefOid?.toLowerCase() !== deliverySha.toLowerCase()) {
|
|
1007
|
+
continuationLifecycleBlocked = true;
|
|
1008
|
+
continuationPushFailureReason = 'the open PR head no longer exposes the exact verified commit from this unchanged iteration';
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
continuationRemoteIsDraft = beforePush.isDraft;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
continuationRemoteIsDraft = beforePush.isDraft;
|
|
1016
|
+
try {
|
|
1017
|
+
const pushed = await (0, pr_publisher_1.pushBranch)(exec, {
|
|
1018
|
+
repoDir: baseRepo,
|
|
1019
|
+
branch: launchContinuation.branch,
|
|
1020
|
+
baseBranch: launchContinuation.baseBranch,
|
|
1021
|
+
sourceSha: deliverySha,
|
|
1022
|
+
});
|
|
1023
|
+
if (pushed.state === 'local-only') {
|
|
1024
|
+
continuationPushFailed = true;
|
|
1025
|
+
continuationPushFailureReason = pushed.reason;
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
continuationExactPushCompleted = true;
|
|
1029
|
+
const afterPush = await (0, pr_lifecycle_1.observePrLifecycle)(exec, baseRepo, launchContinuation.prUrl, deliverySha);
|
|
1030
|
+
if (!afterPush.ok) {
|
|
1031
|
+
continuationPushFailed = true;
|
|
1032
|
+
continuationPushFailureReason = `exact commit was pushed, but the PR lifecycle could not be confirmed: ${afterPush.detail}`;
|
|
1033
|
+
}
|
|
1034
|
+
else if ((0, pr_lifecycle_1.isExactOpenPr)(afterPush, launchContinuation.branch, launchContinuation.baseBranch)) {
|
|
1035
|
+
if (afterPush.headRefOid?.toLowerCase() === deliverySha.toLowerCase()) {
|
|
1036
|
+
continuationRemoteIsDraft = afterPush.isDraft;
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
// The object push returned success but another writer moved the
|
|
1040
|
+
// remote head before observation. Preserve local evidence and
|
|
1041
|
+
// block; creating a PR from the mutable branch would be unsafe.
|
|
1042
|
+
continuationLifecycleBlocked = true;
|
|
1043
|
+
continuationPushFailureReason = 'the PR head moved after the exact push; the verified commit is preserved but no longer proven as the remote head';
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
else if (afterPush.state === 'MERGED' && afterPush.includesExpectedSha === true) {
|
|
1047
|
+
// The PR won the race but demonstrably included this exact SHA.
|
|
1048
|
+
continuationRemoteIsDraft = false;
|
|
1049
|
+
continuationMergedWithSha = true;
|
|
1050
|
+
}
|
|
1051
|
+
else if (afterPush.state === 'MERGED' || afterPush.state === 'CLOSED') {
|
|
1052
|
+
continuationNeedsNewPr = true;
|
|
1053
|
+
continuationPushFailureReason = afterPush.includesExpectedSha === null
|
|
1054
|
+
? `the previous PR became ${afterPush.state.toLowerCase()} and GitHub could not prove it included this implementation; create a new draft PR from the preserved commit`
|
|
1055
|
+
: `the previous PR became ${afterPush.state.toLowerCase()} without this implementation; create a new draft PR from the preserved commit`;
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
continuationLifecycleBlocked = true;
|
|
1059
|
+
continuationPushFailureReason = 'the existing PR no longer matches its recorded head/base after the exact push';
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
catch (err) {
|
|
1064
|
+
continuationPushFailed = true;
|
|
1065
|
+
continuationPushFailureReason = errorDetail(err);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (continuationPushFailed) {
|
|
1069
|
+
console.warn(`[rail-isolated] existing PR follow-up push failed for ${launchContinuation.branch}: ${continuationPushFailureReason}`);
|
|
530
1070
|
}
|
|
531
1071
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
: '
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1072
|
+
if (continuationNeedsNewPr && noChanges.length > 0) {
|
|
1073
|
+
// The iteration itself made no new commit, but the old PR did not deliver
|
|
1074
|
+
// its verified head. That head is therefore real work for a fresh PR.
|
|
1075
|
+
const noChangeRuns = new Set(noChanges.map((result) => result.run.runId));
|
|
1076
|
+
results = results.map((result) => noChangeRuns.has(result.run.runId)
|
|
1077
|
+
? { ...result, deliveryOutcome: 'ready' }
|
|
1078
|
+
: result);
|
|
1079
|
+
}
|
|
1080
|
+
if (continuationLifecycleBlocked) {
|
|
1081
|
+
const affectedRuns = new Set(results
|
|
1082
|
+
.filter((result) => result.run.continuationTarget !== null &&
|
|
1083
|
+
(result.deliveryOutcome === 'ready' || result.deliveryOutcome === 'no_changes'))
|
|
1084
|
+
.map((result) => result.run.runId));
|
|
1085
|
+
for (const result of results) {
|
|
1086
|
+
if (affectedRuns.has(result.run.runId))
|
|
1087
|
+
markWorktree(result.run, 'needs-review');
|
|
547
1088
|
}
|
|
548
|
-
|
|
1089
|
+
results = results.map((result) => affectedRuns.has(result.run.runId)
|
|
1090
|
+
? {
|
|
1091
|
+
...result,
|
|
1092
|
+
deliveryOutcome: 'blocked',
|
|
1093
|
+
failureCode: 'branch_verification_failed',
|
|
1094
|
+
failureDetail: continuationPushFailureReason ?? 'PR lifecycle verification failed',
|
|
1095
|
+
safeToRelease: false,
|
|
1096
|
+
}
|
|
1097
|
+
: result);
|
|
1098
|
+
}
|
|
1099
|
+
ready = results.filter((result) => result.deliveryOutcome === 'ready');
|
|
1100
|
+
blocked = results.filter((result) => result.deliveryOutcome === 'blocked');
|
|
1101
|
+
noChanges = results.filter((result) => result.deliveryOutcome === 'no_changes');
|
|
1102
|
+
let deliveryOutcome;
|
|
1103
|
+
if (implementationOutcome === 'failed') {
|
|
1104
|
+
deliveryOutcome = blocked.length > 0 ? 'blocked' : 'not_started';
|
|
1105
|
+
}
|
|
1106
|
+
else if (continuationLifecycleBlocked) {
|
|
1107
|
+
deliveryOutcome = 'blocked';
|
|
1108
|
+
}
|
|
1109
|
+
else if (continuationMergedWithSha || continuationClosedWithSha) {
|
|
1110
|
+
deliveryOutcome = 'delivered';
|
|
1111
|
+
}
|
|
1112
|
+
else if (launchContinuation && ready.length > 0) {
|
|
1113
|
+
deliveryOutcome = continuationNeedsNewPr
|
|
1114
|
+
? (implementationOutcome === 'partially_succeeded' ? 'partial' : 'ready')
|
|
1115
|
+
: continuationPushFailed ? 'retryable_failure' : 'delivered';
|
|
1116
|
+
}
|
|
1117
|
+
else if (ready.length > 0) {
|
|
1118
|
+
deliveryOutcome = implementationOutcome === 'partially_succeeded' || blocked.length > 0 ? 'partial' : 'ready';
|
|
1119
|
+
}
|
|
1120
|
+
else if (noChanges.length === results.length && implementationOutcome === 'succeeded') {
|
|
1121
|
+
deliveryOutcome = 'no_changes';
|
|
1122
|
+
}
|
|
1123
|
+
else if (implementationOutcome === 'partially_succeeded') {
|
|
1124
|
+
deliveryOutcome = 'partial';
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
deliveryOutcome = 'blocked';
|
|
1128
|
+
}
|
|
1129
|
+
const next = implementationOutcome === 'failed'
|
|
1130
|
+
? 'implementation_failed'
|
|
1131
|
+
: continuationLifecycleBlocked
|
|
1132
|
+
? 'pr_failed'
|
|
1133
|
+
: continuationMergedWithSha
|
|
1134
|
+
? 'pr_ready'
|
|
1135
|
+
: continuationClosedWithSha
|
|
1136
|
+
? 'pr_closed'
|
|
1137
|
+
: ready.length > 0
|
|
1138
|
+
? (launchContinuation
|
|
1139
|
+
? (continuationNeedsNewPr
|
|
1140
|
+
? 'on_review'
|
|
1141
|
+
: continuationPushFailed
|
|
1142
|
+
? 'pr_failed'
|
|
1143
|
+
: (continuationRemoteIsDraft === false ? 'pr_ready' : 'pr_draft'))
|
|
1144
|
+
: 'on_review')
|
|
1145
|
+
: deliveryOutcome === 'no_changes'
|
|
1146
|
+
? 'no_changes'
|
|
1147
|
+
: 'pr_failed';
|
|
1148
|
+
const firstFailure = results.find((result) => result.failureCode);
|
|
1149
|
+
const statusCode = implementationOutcome === 'failed'
|
|
1150
|
+
? 'implementation_failed'
|
|
1151
|
+
: continuationLifecycleBlocked
|
|
1152
|
+
? 'branch_verification_failed'
|
|
1153
|
+
: continuationMergedWithSha
|
|
1154
|
+
? 'pr_ready'
|
|
1155
|
+
: continuationClosedWithSha
|
|
1156
|
+
? 'pr_closed'
|
|
1157
|
+
: continuationNeedsNewPr
|
|
1158
|
+
? 'ready_for_review'
|
|
1159
|
+
: continuationPushFailed
|
|
1160
|
+
? 'push_failed'
|
|
1161
|
+
: deliveryOutcome === 'no_changes'
|
|
1162
|
+
? 'no_changes'
|
|
1163
|
+
: implementationOutcome === 'partially_succeeded'
|
|
1164
|
+
? 'partial_success'
|
|
1165
|
+
: firstFailure?.failureCode
|
|
1166
|
+
?? (launchContinuation && ready.length > 0 ? 'existing_pr_updated' : 'ready_for_review');
|
|
1167
|
+
const statusDetail = continuationPushFailureReason ?? firstFailure?.failureDetail ?? null;
|
|
1168
|
+
// Only proven-clean, durably referenced results are automatically
|
|
1169
|
+
// detached. Fresh reviewable worktrees stay mounted for the later PR
|
|
1170
|
+
// decision; dirty/unknown/ref-mismatched work is never passed to --force.
|
|
1171
|
+
const failedSafeIds = results
|
|
1172
|
+
.filter((result) => result.implementationOutcome === 'failed' && result.safeToRelease)
|
|
1173
|
+
.map((result) => result.run.ledgerId);
|
|
1174
|
+
const completedSafeIds = results
|
|
1175
|
+
.filter((result) => result.safeToRelease && (result.deliveryOutcome === 'no_changes' ||
|
|
1176
|
+
(launchContinuation !== null && result.deliveryOutcome === 'ready')))
|
|
1177
|
+
.map((result) => result.run.ledgerId);
|
|
1178
|
+
const patch = {
|
|
1179
|
+
branches: branchRecords(results),
|
|
1180
|
+
worktreeIds,
|
|
1181
|
+
implementationOutcome,
|
|
1182
|
+
deliveryOutcome,
|
|
1183
|
+
statusCode,
|
|
1184
|
+
statusDetail,
|
|
1185
|
+
deliverySha,
|
|
1186
|
+
...(launchContinuation ? {
|
|
1187
|
+
branch: launchContinuation.branch,
|
|
1188
|
+
prUrl: continuationNeedsNewPr ? null : launchContinuation.prUrl,
|
|
1189
|
+
prNumber: continuationNeedsNewPr ? null : launchContinuation.prNumber,
|
|
1190
|
+
// A stale CLOSED/MERGED PR is detached from this generation. The exact
|
|
1191
|
+
// object remains locally or on its recorded branch, ready for Create PR.
|
|
1192
|
+
prState: continuationNeedsNewPr
|
|
1193
|
+
? (continuationExactPushCompleted ? 'pushed' : 'local-only')
|
|
1194
|
+
: 'pr-created',
|
|
1195
|
+
// Once detached, this generation owns the lifecycle of the new PR,
|
|
1196
|
+
// while per-unit branchOwnership still protects the borrowed branch.
|
|
1197
|
+
isContinuation: !continuationNeedsNewPr,
|
|
1198
|
+
} : {}),
|
|
1199
|
+
};
|
|
549
1200
|
if (prDeliveryId && (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', next, patch)) {
|
|
550
|
-
|
|
551
|
-
|
|
1201
|
+
// Persist truthful settlement before cleanup. A crash can leave a
|
|
1202
|
+
// safely-recoverable mount, but can no longer lose no-change/partial/
|
|
1203
|
+
// exact-SHA evidence and regress the card back to `building`.
|
|
1204
|
+
const cleanupWarnings = [
|
|
1205
|
+
...await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: ctx.db, git, repoDir: baseRepo, worktreeIds: failedSafeIds, state: 'failed', remove }),
|
|
1206
|
+
...await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: ctx.db, git, repoDir: baseRepo, worktreeIds: completedSafeIds, remove }),
|
|
1207
|
+
];
|
|
1208
|
+
if (cleanupWarnings.length > 0) {
|
|
1209
|
+
(0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, next, next, { cleanupWarnings });
|
|
552
1210
|
}
|
|
553
1211
|
broadcastPrState();
|
|
554
1212
|
// Completion driver: refresh the origin conversation's card in place —
|
|
555
1213
|
// on_review asks the question, discarded informs the outcome.
|
|
556
1214
|
syncOriginCard('update');
|
|
557
1215
|
}
|
|
558
|
-
if (!anySucceeded) {
|
|
559
|
-
// FAILED-IMPLEMENTATION CLEANUP (0 succeeded): unmount every worktree NOW rather
|
|
560
|
-
// than leaving it for a restart's reconcile sweep — a still-mounted
|
|
561
|
-
// worktree POISONS the next run of the same ticket: the worktree path is
|
|
562
|
-
// keyed by ticketId, so the next allocation silently reuses this
|
|
563
|
-
// checkout and its stale branch. BRANCHES are kept: per-run settle
|
|
564
|
-
// committed partial work to them and a re-launch resumes a kept branch
|
|
565
|
-
// by name (createWorktree's resume path); user-discard remains the one
|
|
566
|
-
// explicit branch-deleting action. Ledger rows are already terminal
|
|
567
|
-
// ('failed') from per-run settle — ensured defensively here.
|
|
568
|
-
for (const a of allocated) {
|
|
569
|
-
await remove(git, {
|
|
570
|
-
repoDir: baseRepo, worktreePath: a.handle.worktreePath, branch: a.handle.branch, deleteBranch: false,
|
|
571
|
-
}).catch((err) => {
|
|
572
|
-
console.warn(`[rail-isolated] failed to remove failed worktree ${a.handle.worktreePath}: ${err.message}`);
|
|
573
|
-
});
|
|
574
|
-
const wt = (0, rail_worktrees_store_1.getRailWorktree)(ctx.db, a.ledgerId);
|
|
575
|
-
if (wt && !(0, rail_worktrees_store_1.isTerminalMergeState)(wt.merge_state))
|
|
576
|
-
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, a.ledgerId, 'failed');
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
1216
|
return; // legacy merge-back never runs in PR mode
|
|
580
1217
|
}
|
|
1218
|
+
const branches = results.flatMap((result) => result.run.ticketIds.map((ticketId) => ({
|
|
1219
|
+
ticketId,
|
|
1220
|
+
branch: result.run.handle.branch,
|
|
1221
|
+
succeeded: result.deliveryOutcome === 'ready',
|
|
1222
|
+
})));
|
|
581
1223
|
try {
|
|
582
1224
|
const outcomes = await (0, rail_merge_orchestrator_1.runMergeBack)({
|
|
583
1225
|
git, executor: (0, loop_executors_1.createLoopExecutors)(), baseDir: baseRepo,
|
|
@@ -605,33 +1247,238 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
605
1247
|
const r = results.find((x) => x.run.ticketId === o.ticketId);
|
|
606
1248
|
if (!r)
|
|
607
1249
|
continue;
|
|
1250
|
+
if (!r.safeToRelease) {
|
|
1251
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, r.run.ledgerId, 'needs-review');
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
608
1254
|
// Merged → drop the branch; needs-review / skipped → keep the branch for the human.
|
|
609
1255
|
await remove(git, {
|
|
610
1256
|
repoDir: baseRepo, worktreePath: r.run.handle.worktreePath, branch: o.branch,
|
|
611
1257
|
deleteBranch: o.state === 'merged',
|
|
1258
|
+
}).then(() => {
|
|
1259
|
+
if (r.implementationOutcome === 'failed')
|
|
1260
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(ctx.db, r.run.ledgerId, 'failed');
|
|
612
1261
|
}).catch(() => { });
|
|
613
1262
|
}
|
|
614
1263
|
}
|
|
615
1264
|
catch (err) {
|
|
616
1265
|
console.error('[rail-isolated] merge-back failed:', err);
|
|
617
1266
|
}
|
|
1267
|
+
}).catch(async (err) => {
|
|
1268
|
+
const detail = errorDetail(err);
|
|
1269
|
+
console.error(`[rail-isolated] aggregate settlement failed: ${detail}`);
|
|
1270
|
+
if (!prMode || !prDeliveryId || (0, rail_pr_store_1.getPrDelivery)(ctx.db, prDeliveryId)?.decision !== 'building')
|
|
1271
|
+
return;
|
|
1272
|
+
const retained = await Promise.all(guardedRunPromises);
|
|
1273
|
+
for (const result of retained)
|
|
1274
|
+
markWorktree(result.run, 'needs-review');
|
|
1275
|
+
if ((0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', 'pr_failed', {
|
|
1276
|
+
branches: branchRecords(retained.map((result) => ({
|
|
1277
|
+
...result,
|
|
1278
|
+
deliveryOutcome: 'blocked',
|
|
1279
|
+
failureCode: result.failureCode ?? 'settlement_interrupted',
|
|
1280
|
+
failureDetail: result.failureDetail ?? detail,
|
|
1281
|
+
safeToRelease: false,
|
|
1282
|
+
}))),
|
|
1283
|
+
worktreeIds: allocated.map((run) => run.ledgerId),
|
|
1284
|
+
implementationOutcome: aggregateImplementationOutcome(retained),
|
|
1285
|
+
deliveryOutcome: 'blocked',
|
|
1286
|
+
statusCode: 'settlement_interrupted',
|
|
1287
|
+
statusDetail: detail,
|
|
1288
|
+
})) {
|
|
1289
|
+
broadcastPrState();
|
|
1290
|
+
syncOriginCard('update');
|
|
1291
|
+
}
|
|
618
1292
|
});
|
|
619
1293
|
return allocated.map((a) => a.runId);
|
|
620
1294
|
}
|
|
1295
|
+
function recoveryOverlayExcludes(worktreePath) {
|
|
1296
|
+
try {
|
|
1297
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(worktreePath, worktree_overlay_1.OVERLAY_MANIFEST), 'utf8'));
|
|
1298
|
+
const values = Array.isArray(parsed) ? parsed : parsed?.paths;
|
|
1299
|
+
if (!Array.isArray(values))
|
|
1300
|
+
return [];
|
|
1301
|
+
return values.filter((value) => {
|
|
1302
|
+
if (typeof value !== 'string' || value.length === 0 || path.isAbsolute(value))
|
|
1303
|
+
return false;
|
|
1304
|
+
if (value.includes('\\') || value.split('/').includes('..') || /[\0:*?\[\]]/.test(value))
|
|
1305
|
+
return false;
|
|
1306
|
+
if (value === worktree_overlay_1.OVERLAY_MANIFEST)
|
|
1307
|
+
return true;
|
|
1308
|
+
if (!/^\.(?:claude|codex|gemini)\/[^/]+(?:\/[^/]+)*$/.test(value))
|
|
1309
|
+
return false;
|
|
1310
|
+
try {
|
|
1311
|
+
// Manifest contents are worktree-writable and therefore not authority.
|
|
1312
|
+
// Ignore only links that still prove they are overlay scaffolding;
|
|
1313
|
+
// copied/modified files remain recoverable dirty data.
|
|
1314
|
+
return fs.lstatSync(path.join(worktreePath, value)).isSymbolicLink();
|
|
1315
|
+
}
|
|
1316
|
+
catch {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
catch {
|
|
1322
|
+
return [];
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
async function inspectRecoveryWorktree(git, repoDir, row) {
|
|
1326
|
+
const excludes = [...worktree_manager_1.PR_NEVER_STAGE_PATHS, ...recoveryOverlayExcludes(row.worktree_path)];
|
|
1327
|
+
try {
|
|
1328
|
+
const [status, actualBranch, head, branchRef] = await Promise.all([
|
|
1329
|
+
git.run([
|
|
1330
|
+
'status', '--porcelain', '--untracked-files=all', '--', '.',
|
|
1331
|
+
...excludes.map((entry) => `:(exclude)${entry}`),
|
|
1332
|
+
], row.worktree_path),
|
|
1333
|
+
git.run(['rev-parse', '--abbrev-ref', 'HEAD'], row.worktree_path),
|
|
1334
|
+
git.run(['rev-parse', '--verify', 'HEAD'], row.worktree_path),
|
|
1335
|
+
git.run(['rev-parse', '--verify', `refs/heads/${row.branch}`], repoDir),
|
|
1336
|
+
]);
|
|
1337
|
+
const sha = head.code === 0 && COMMIT_SHA_RE.test(head.stdout.trim()) ? head.stdout.trim() : null;
|
|
1338
|
+
const refSha = branchRef.code === 0 ? branchRef.stdout.trim() : '';
|
|
1339
|
+
if (status.code !== 0)
|
|
1340
|
+
return { safe: false, sha, detail: 'git status could not verify worktree cleanliness' };
|
|
1341
|
+
if (status.stdout.trim().length > 0)
|
|
1342
|
+
return { safe: false, sha, detail: 'the interrupted worktree contains uncommitted changes' };
|
|
1343
|
+
if (actualBranch.code !== 0 || actualBranch.stdout.trim() !== row.branch) {
|
|
1344
|
+
return { safe: false, sha, detail: `worktree is not on expected branch ${row.branch}` };
|
|
1345
|
+
}
|
|
1346
|
+
if (!sha || refSha !== sha) {
|
|
1347
|
+
return { safe: false, sha, detail: `HEAD does not match refs/heads/${row.branch}` };
|
|
1348
|
+
}
|
|
1349
|
+
return { safe: true, sha };
|
|
1350
|
+
}
|
|
1351
|
+
catch (err) {
|
|
1352
|
+
return { safe: false, sha: null, detail: errorDetail(err) };
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
621
1355
|
/**
|
|
622
|
-
* Startup reconciliation
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
* isolation. Returns how many rows were reconciled.
|
|
1356
|
+
* Startup reconciliation is serialized with launch allocation. It proves each
|
|
1357
|
+
* orphan clean and durably referenced before removal; successful, dirty,
|
|
1358
|
+
* unknown, or ref-mismatched work remains mounted as needs-review. Building
|
|
1359
|
+
* delivery generations are then recovered from the durable engine outcomes.
|
|
627
1360
|
*/
|
|
628
1361
|
async function reconcileRailWorktrees(db, repoDir, io = {}) {
|
|
629
1362
|
const git = io.git ?? worktree_manager_1.defaultGitRunner;
|
|
630
1363
|
const remove = io.remove ?? worktree_manager_1.removeWorktree;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
1364
|
+
return (0, repo_lock_1.withRepoLock)(repoDir, async () => {
|
|
1365
|
+
const stuck = (0, rail_worktrees_store_1.listNonTerminalRailWorktrees)(db);
|
|
1366
|
+
const autoReleaseWorktreeIds = new Set();
|
|
1367
|
+
const settledDeliveries = db.prepare(`
|
|
1368
|
+
SELECT decision, delivery_outcome, worktree_ids FROM rail_pr_deliveries
|
|
1369
|
+
WHERE decision <> 'building'
|
|
1370
|
+
AND delivery_outcome IN ('delivered', 'no_changes', 'retryable_failure')
|
|
1371
|
+
`).all();
|
|
1372
|
+
for (const delivery of settledDeliveries) {
|
|
1373
|
+
try {
|
|
1374
|
+
const ids = JSON.parse(delivery.worktree_ids);
|
|
1375
|
+
if (Array.isArray(ids)) {
|
|
1376
|
+
for (const id of ids)
|
|
1377
|
+
if (typeof id === 'string')
|
|
1378
|
+
autoReleaseWorktreeIds.add(id);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
catch { /* malformed history never authorizes cleanup */ }
|
|
1382
|
+
}
|
|
1383
|
+
const verifiedShaByRun = new Map();
|
|
1384
|
+
for (const row of stuck) {
|
|
1385
|
+
const run = row.run_id
|
|
1386
|
+
? db.prepare('SELECT status, final_outcome FROM loop_runs WHERE id = ?').get(row.run_id)
|
|
1387
|
+
: undefined;
|
|
1388
|
+
const inspection = await inspectRecoveryWorktree(git, repoDir, row);
|
|
1389
|
+
if (run?.status === 'completed' && run.final_outcome === 'success') {
|
|
1390
|
+
if (inspection.safe && inspection.sha) {
|
|
1391
|
+
if (row.run_id)
|
|
1392
|
+
verifiedShaByRun.set(row.run_id, inspection.sha);
|
|
1393
|
+
if (autoReleaseWorktreeIds.has(row.id)) {
|
|
1394
|
+
try {
|
|
1395
|
+
await remove(git, {
|
|
1396
|
+
repoDir,
|
|
1397
|
+
worktreePath: row.worktree_path,
|
|
1398
|
+
branch: row.branch,
|
|
1399
|
+
deleteBranch: false,
|
|
1400
|
+
});
|
|
1401
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'released');
|
|
1402
|
+
}
|
|
1403
|
+
catch {
|
|
1404
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'built');
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
else {
|
|
1408
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'built');
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
else {
|
|
1412
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'needs-review');
|
|
1413
|
+
}
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
if (run?.status === 'completed' && inspection.safe) {
|
|
1417
|
+
try {
|
|
1418
|
+
await remove(git, {
|
|
1419
|
+
repoDir,
|
|
1420
|
+
worktreePath: row.worktree_path,
|
|
1421
|
+
branch: row.branch,
|
|
1422
|
+
deleteBranch: false,
|
|
1423
|
+
});
|
|
1424
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'failed');
|
|
1425
|
+
}
|
|
1426
|
+
catch {
|
|
1427
|
+
// A failed --force leaves mount state uncertain; retain the ledger as
|
|
1428
|
+
// needs-review instead of claiming cleanup succeeded.
|
|
1429
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'needs-review');
|
|
1430
|
+
}
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
// Missing/incomplete run evidence, dirty state, status failure, and ref
|
|
1434
|
+
// mismatch are all recoverable uncertainty. Never force-remove them.
|
|
1435
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(db, row.id, 'needs-review');
|
|
1436
|
+
}
|
|
1437
|
+
const recovered = (0, rail_pr_store_1.reconcileFailedBuildingPrDeliveries)(db, { startup: true });
|
|
1438
|
+
for (const delivery of recovered) {
|
|
1439
|
+
if (delivery.is_continuation !== 1 || !['succeeded', 'partially_succeeded'].includes(delivery.implementation_outcome))
|
|
1440
|
+
continue;
|
|
1441
|
+
let runIds = [];
|
|
1442
|
+
try {
|
|
1443
|
+
const parsed = JSON.parse(delivery.run_ids);
|
|
1444
|
+
if (Array.isArray(parsed))
|
|
1445
|
+
runIds = parsed.filter((value) => typeof value === 'string');
|
|
1446
|
+
}
|
|
1447
|
+
catch { /* malformed legacy row remains blocked without a retry SHA */ }
|
|
1448
|
+
for (const runId of runIds) {
|
|
1449
|
+
if (verifiedShaByRun.has(runId))
|
|
1450
|
+
continue;
|
|
1451
|
+
const released = db.prepare(`
|
|
1452
|
+
SELECT branch, merge_state FROM rail_worktrees
|
|
1453
|
+
WHERE run_id = ? AND merge_state = 'released'
|
|
1454
|
+
ORDER BY created_at DESC, rowid DESC LIMIT 1
|
|
1455
|
+
`).get(runId);
|
|
1456
|
+
if (!released)
|
|
1457
|
+
continue;
|
|
1458
|
+
try {
|
|
1459
|
+
const ref = await git.run(['rev-parse', '--verify', `refs/heads/${released.branch}`], repoDir);
|
|
1460
|
+
const sha = ref.code === 0 ? ref.stdout.trim() : '';
|
|
1461
|
+
if (COMMIT_SHA_RE.test(sha))
|
|
1462
|
+
verifiedShaByRun.set(runId, sha);
|
|
1463
|
+
}
|
|
1464
|
+
catch { /* exact retry remains blocked */ }
|
|
1465
|
+
}
|
|
1466
|
+
const shas = [...new Set(runIds.map((runId) => verifiedShaByRun.get(runId)).filter((sha) => !!sha))];
|
|
1467
|
+
if (shas.length !== 1)
|
|
1468
|
+
continue;
|
|
1469
|
+
(0, rail_pr_store_1.transitionDecision)(db, delivery.id, delivery.decision, delivery.decision, {
|
|
1470
|
+
deliveryOutcome: 'retryable_failure',
|
|
1471
|
+
statusCode: 'settlement_interrupted',
|
|
1472
|
+
statusDetail: 'Recovered a clean exact continuation commit; delivery can be retried safely.',
|
|
1473
|
+
deliverySha: shas[0],
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
for (const delivery of recovered) {
|
|
1477
|
+
try {
|
|
1478
|
+
io.onDeliveryRecovered?.(delivery.id);
|
|
1479
|
+
}
|
|
1480
|
+
catch { /* durable state wins over advisory surfaces */ }
|
|
1481
|
+
}
|
|
1482
|
+
return stuck.length;
|
|
1483
|
+
});
|
|
637
1484
|
}
|