specrails-desktop 2.29.0 → 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-UjXbEWhN.js → dist-js-CcBCbb32.js} +1 -1
- package/client/dist/assets/{dist-js-BTAcxhPb.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/{settings-Bp24JHlv2.js → settings-B9ilOSYO.js} +1 -1
- package/client/dist/assets/{settings-Bx2ObkTR2.js → settings-BqlWHRHA.js} +1 -1
- package/client/dist/assets/{settings-C_T0-j9d.js → settings-D8dlqt4d.js} +1 -1
- package/client/dist/assets/{settings-DhaFKpcw2.js → settings-DAZqjX2a2.js} +1 -1
- package/client/dist/assets/{settings-B_v3zgJf.js → settings-DRh39v1S2.js} +1 -1
- package/client/dist/assets/{settings-BemR_e3F.js → settings-Mj1OJbfd2.js} +1 -1
- package/client/dist/assets/{settings-BvIwQ3A1.js → settings-lRReaSYm.js} +1 -1
- 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 +1141 -280
- package/server/dist/rail-isolation.js +4 -2
- package/server/dist/rail-pr-decision.js +756 -270
- 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 +28 -6
- package/server/dist/rails-router.js +149 -112
- 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 +70 -21
- package/client/dist/assets/ActivityFeedPage-Be-6L0Ld.js +0 -1
- package/client/dist/assets/AgentBrowserCapture-CpZ-dd8O.js +0 -1
- package/client/dist/assets/AgentIntegrationsModal-DO1pN_Ez.js +0 -1
- package/client/dist/assets/AgentModeCodePane-DLltTOqp.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-Dp5YvmW0.js +0 -1
- package/client/dist/assets/AgentsPage-ehHuVUds.js +0 -86
- package/client/dist/assets/AnalyticsPage-C4Ik95FX.js +0 -1
- package/client/dist/assets/BarChart-DVZgrhJd.js +0 -33
- package/client/dist/assets/CodePage-D4OCMfxL.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-2Xf-52x2.js +0 -1
- package/client/dist/assets/DocsDialog-CHihLjWd.js +0 -11
- package/client/dist/assets/DocsPage-1vEk7XbH.js +0 -11
- package/client/dist/assets/ExportDropdown-DLHFQxVn.js +0 -1
- package/client/dist/assets/IntegrationsPage-CCb2YuyJ.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-CxJJEnjw.js +0 -19
- package/client/dist/assets/JobDetailModal-CJI7qRkm.js +0 -1
- package/client/dist/assets/JobDetailPage-DwA9bbwx.js +0 -1
- package/client/dist/assets/JobsPage-BDIyFfd5.js +0 -1
- package/client/dist/assets/LoopBuilderPage-CpBaW59t.js +0 -7
- package/client/dist/assets/LoopPreviewModal-CwpKcv0Y.js +0 -1
- package/client/dist/assets/LoopsPage-BO0Pa8oz.js +0 -1
- package/client/dist/assets/PluginsPage-BBGTBpGh.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-M9zGQo0l.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-eBI0zL5P.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-BEXYxDbs.js +0 -1
- package/client/dist/assets/agent-BoXSzaBk2.js +0 -1
- package/client/dist/assets/agent-C2-rROL52.js +0 -1
- package/client/dist/assets/agent-CeRTdGT12.js +0 -1
- package/client/dist/assets/agent-DZ8zTrQT.js +0 -1
- package/client/dist/assets/agent-DqyzT33X.js +0 -1
- package/client/dist/assets/agent-fQI_6tsA.js +0 -1
- package/client/dist/assets/agent-tJdAobaM.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-DecvjjPU.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-BfQD173j.js +0 -1
- package/client/dist/assets/common-CF8iGXrG.js +0 -1
- package/client/dist/assets/common-Cfmwcrl52.js +0 -1
- package/client/dist/assets/common-D2cimsul.js +0 -1
- package/client/dist/assets/common-DH7fjyL1.js +0 -1
- package/client/dist/assets/common-DXrmj6WD.js +0 -1
- package/client/dist/assets/common-DZbXlmq72.js +0 -1
- package/client/dist/assets/common-DtXmzX2o2.js +0 -1
- package/client/dist/assets/dashboard-5C2FOZQX.js +0 -1
- package/client/dist/assets/dashboard-B6iQ-IL_.js +0 -1
- package/client/dist/assets/dashboard-BFfnCtq52.js +0 -1
- package/client/dist/assets/dashboard-BIC8KtRf2.js +0 -1
- package/client/dist/assets/dashboard-CEgg9NAr.js +0 -1
- package/client/dist/assets/dashboard-CiOyDh9L.js +0 -1
- package/client/dist/assets/dashboard-D6x5OnNj2.js +0 -1
- package/client/dist/assets/dashboard-DBuLfWo9.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-OBDI7bHm.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-BFIdeDTw.js +0 -1
- package/client/dist/assets/jobs-BJ-Gcao22.js +0 -1
- package/client/dist/assets/jobs-DAGiPVIS2.js +0 -1
- package/client/dist/assets/jobs-DAU8C_tZ.js +0 -1
- package/client/dist/assets/jobs-Da0GaaSc.js +0 -1
- package/client/dist/assets/jobs-DrYmsKy5.js +0 -1
- package/client/dist/assets/jobs-P3unxtxo2.js +0 -1
- package/client/dist/assets/jobs-p360zdf1.js +0 -1
- package/client/dist/assets/json.worker-DzV-CpCQ.js +0 -58
- package/client/dist/assets/lib-C5ZxrPSR.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-BYCMtxOz.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-BHv3ewvq.js +0 -1
- package/client/dist/assets/tickets-BIxU86j12.js +0 -1
- package/client/dist/assets/tickets-C2BhwzwR.js +0 -1
- package/client/dist/assets/tickets-D1IeX2EW.js +0 -1
- package/client/dist/assets/tickets-DZut8Ryt.js +0 -1
- package/client/dist/assets/tickets-LfK6LzKl2.js +0 -1
- package/client/dist/assets/tickets-dQfsc7Ph.js +0 -1
- package/client/dist/assets/tickets-sVun0I6v.js +0 -1
- package/client/dist/assets/upload-lgjKJgrl.js +0 -1
- package/client/dist/assets/value-Bc0_jlJA.js +0 -1
- /package/client/dist/assets/{abap-D0E4FfVW.js → abap-08VXUWAP.js} +0 -0
- /package/client/dist/assets/{activity-BbWiDfn-2.js → activity-B-rp4h_v.js} +0 -0
- /package/client/dist/assets/{activity-DvECTYGt.js → activity-C6kJpm9q2.js} +0 -0
- /package/client/dist/assets/{activity-D3Ic-HTS.js → activity-CGx4kb_j.js} +0 -0
- /package/client/dist/assets/{activity-BZZlEG3a.js → activity-CnoX2bH7.js} +0 -0
- /package/client/dist/assets/{activity-DtzRy92x.js → activity-DAyekdlt2.js} +0 -0
- /package/client/dist/assets/{activity-FleOlK0l2.js → activity-DCxtpT7j.js} +0 -0
- /package/client/dist/assets/{activity-DFbvNqhE2.js → activity-JzwRaWPD.js} +0 -0
- /package/client/dist/assets/{addspec-CFxGvmJR2.js → addspec-BXjvkh5q.js} +0 -0
- /package/client/dist/assets/{addspec-NBbcTbLc.js → addspec-C4bwrmpi.js} +0 -0
- /package/client/dist/assets/{addspec-BKrH2Ucz.js → addspec-CArHXUDM.js} +0 -0
- /package/client/dist/assets/{addspec-CR8MB5Fr2.js → addspec-DTKJcXiR.js} +0 -0
- /package/client/dist/assets/{addspec-B_LQMHW3.js → addspec-DbNBbrha2.js} +0 -0
- /package/client/dist/assets/{addspec-DaA-CbTt.js → addspec-htHGZHOe2.js} +0 -0
- /package/client/dist/assets/{addspec-Bw_YQwo62.js → addspec-lr_LP6jk.js} +0 -0
- /package/client/dist/assets/{agents-BHgn--3j.js → agents-6ZOJXWVk2.js} +0 -0
- /package/client/dist/assets/{agents-VsyFhGOS2.js → agents-BZ0XQKjg.js} +0 -0
- /package/client/dist/assets/{agents-HFCqQH0Z.js → agents-BqWklnHR.js} +0 -0
- /package/client/dist/assets/{agents-B_qGaB0F.js → agents-Bz-thHkB2.js} +0 -0
- /package/client/dist/assets/{agents-CP8_ezYG.js → agents-CJclElx6.js} +0 -0
- /package/client/dist/assets/{agents-B9pFSJQI2.js → agents-D_l9L0VA.js} +0 -0
- /package/client/dist/assets/{agents-CV-_xBvX2.js → agents-u_C0jPB5.js} +0 -0
- /package/client/dist/assets/{agentstudio-BEp7Sb46.js → agentstudio-BF19hbXj.js} +0 -0
- /package/client/dist/assets/{agentstudio-Br3WD7Xs.js → agentstudio-BLGvxWBx2.js} +0 -0
- /package/client/dist/assets/{agentstudio-BOjG6tgJ.js → agentstudio-D9UKiWTj2.js} +0 -0
- /package/client/dist/assets/{agentstudio-D2A5peWV2.js → agentstudio-Drs37nIM.js} +0 -0
- /package/client/dist/assets/{agentstudio-CuTt3ZGe2.js → agentstudio-nEnKkcXM.js} +0 -0
- /package/client/dist/assets/{agentstudio-RsxnZtUA.js → agentstudio-pyFcSBKy.js} +0 -0
- /package/client/dist/assets/{agentstudio-RfZavULt2.js → agentstudio-zfTzQh3U.js} +0 -0
- /package/client/dist/assets/{aiedit-DnBzjlVz2.js → aiedit-B0KUbpB6.js} +0 -0
- /package/client/dist/assets/{aiedit-D0Syl-mC.js → aiedit-BoG24nlg2.js} +0 -0
- /package/client/dist/assets/{aiedit-CcugGPbS.js → aiedit-CjlSRQVP.js} +0 -0
- /package/client/dist/assets/{aiedit-DrIjSojo.js → aiedit-DCjA7jLJ.js} +0 -0
- /package/client/dist/assets/{aiedit-CV520D4W2.js → aiedit-DNXxq_Dr.js} +0 -0
- /package/client/dist/assets/{aiedit-26p5A_HY2.js → aiedit-DQ-0_pvE.js} +0 -0
- /package/client/dist/assets/{aiedit-a-Z-g5iC.js → aiedit-olnv3QSt2.js} +0 -0
- /package/client/dist/assets/{analytics-DEe5BMz8.js → analytics-BC7mwR4s.js} +0 -0
- /package/client/dist/assets/{analytics-4HvXb0Vn2.js → analytics-BiNg8edP.js} +0 -0
- /package/client/dist/assets/{analytics-CpSTNfui.js → analytics-C0s_T2GN.js} +0 -0
- /package/client/dist/assets/{analytics-4-eskbtX.js → analytics-CdsAPAwZ2.js} +0 -0
- /package/client/dist/assets/{analytics-Bpe41fnX2.js → analytics-Cr1_q-lC.js} +0 -0
- /package/client/dist/assets/{analytics-DcOITHla2.js → analytics-IEsKZvpW2.js} +0 -0
- /package/client/dist/assets/{analytics-Bd9rHGjV.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-CgOQ9K-6.js → attachments-D0wXXt9T2.js} +0 -0
- /package/client/dist/assets/{attachments-DlEQu56g2.js → attachments-DenIjuVI.js} +0 -0
- /package/client/dist/assets/{attachments-CA2f3qL3.js → attachments-DhO7LPfy.js} +0 -0
- /package/client/dist/assets/{attachments-D2jwJXQ42.js → attachments-DoH5w0x4.js} +0 -0
- /package/client/dist/assets/{attachments-CHDbhKeH.js → attachments-Q9yNtiEM.js} +0 -0
- /package/client/dist/assets/{attachments-CKAY5aYG2.js → attachments-V_WZAYXx2.js} +0 -0
- /package/client/dist/assets/{attachments-jVdKzAXn.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-Cl-FwPXs.js → browser-C25DkQiO.js} +0 -0
- /package/client/dist/assets/{browser-JsiohNmW.js → browser-CiJ7-L32.js} +0 -0
- /package/client/dist/assets/{browser-BbJ_q_OT2.js → browser-CvgDySd9.js} +0 -0
- /package/client/dist/assets/{browser-BQFDxrRq.js → browser-CyR1Kp3m2.js} +0 -0
- /package/client/dist/assets/{browser-Do2jf8_N.js → browser-DwZljcRh2.js} +0 -0
- /package/client/dist/assets/{browser-C4VJ5CW12.js → browser-DxP-alBK.js} +0 -0
- /package/client/dist/assets/{browser-kA31eYhu2.js → browser-rRCwi7Y7.js} +0 -0
- /package/client/dist/assets/{cameligo-6R45j6Ph.js → cameligo-DMUM7wLl.js} +0 -0
- /package/client/dist/assets/{chat-BalviUAD.js → chat-2rIB8HhT.js} +0 -0
- /package/client/dist/assets/{chat-Ds8GvCnv.js → chat-BR8oe53x2.js} +0 -0
- /package/client/dist/assets/{chat-D5xEtsf3.js → chat-Bua03tty.js} +0 -0
- /package/client/dist/assets/{chat-SaG-ng1x2.js → chat-CZpAQan1.js} +0 -0
- /package/client/dist/assets/{chat-CJddjN3D2.js → chat-CdY5SGvS.js} +0 -0
- /package/client/dist/assets/{chat-n43nZGvn.js → chat-DZyu3ImY2.js} +0 -0
- /package/client/dist/assets/{chat-BMWD_Rx12.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-C9U6M5-e2.js → code-76gS0IdL.js} +0 -0
- /package/client/dist/assets/{code-BotxM43N2.js → code-C1VwxlML.js} +0 -0
- /package/client/dist/assets/{code-HDK4P6Rx.js → code-CnMZyJpr.js} +0 -0
- /package/client/dist/assets/{code-DUe1Bouz.js → code-D20ef9Cf2.js} +0 -0
- /package/client/dist/assets/{code-Bit_z2Xd2.js → code-DdOXaWRY.js} +0 -0
- /package/client/dist/assets/{code-F_KjccGf.js → code-Duohuf9l.js} +0 -0
- /package/client/dist/assets/{code-CIPmDEd0.js → code-geHmknB92.js} +0 -0
- /package/client/dist/assets/{coffee-AruSygo-.js → coffee-Ba7i2nA0.js} +0 -0
- /package/client/dist/assets/{commands-BFQZc9AG.js → commands-B9RZO4qS.js} +0 -0
- /package/client/dist/assets/{commands-BMkC5-ki.js → commands-BEwbAjtD2.js} +0 -0
- /package/client/dist/assets/{commands-KBbhsnw8.js → commands-Bbxu8kLU.js} +0 -0
- /package/client/dist/assets/{commands-CzFSoIiq2.js → commands-CDU3qv68.js} +0 -0
- /package/client/dist/assets/{commands-CWX9WmDX2.js → commands-CzezgtQj.js} +0 -0
- /package/client/dist/assets/{commands-BllkJauO.js → commands-DTQBF_q52.js} +0 -0
- /package/client/dist/assets/{commands-cKTaLwwV2.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-CAc8Qzu32.js → explore-BG99VGpL.js} +0 -0
- /package/client/dist/assets/{explore-BO_OinBD.js → explore-C0TjprJW2.js} +0 -0
- /package/client/dist/assets/{explore-Bhy7WWXv.js → explore-C2dgEo7q.js} +0 -0
- /package/client/dist/assets/{explore-ELbrSZyQ2.js → explore-C8NDHbob.js} +0 -0
- /package/client/dist/assets/{explore-DW_YTkQh.js → explore-CWWgzQGR.js} +0 -0
- /package/client/dist/assets/{explore-DCZL9gRN2.js → explore-CcQXpdtS.js} +0 -0
- /package/client/dist/assets/{explore-BEpIs343.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-Db1viey1.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-CG2dp3Kd.js → integrations-CH0zy1Mw2.js} +0 -0
- /package/client/dist/assets/{integrations-CUNe3t9l2.js → integrations-CIAjhd7L.js} +0 -0
- /package/client/dist/assets/{integrations-CWjh5VRk.js → integrations-CZdwOe8A.js} +0 -0
- /package/client/dist/assets/{integrations-RJfzXkPx2.js → integrations-Ct7omMld.js} +0 -0
- /package/client/dist/assets/{integrations-CAjq5tdW2.js → integrations-D6MSxy3-.js} +0 -0
- /package/client/dist/assets/{integrations-JOHSCvB3.js → integrations-DbZa-Zyc.js} +0 -0
- /package/client/dist/assets/{integrations-CP_aiV_8.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-CQ7mrZde.js → jira-BySfUuQZ.js} +0 -0
- /package/client/dist/assets/{jira-DIRkZk9U.js → jira-Byw2C0F8.js} +0 -0
- /package/client/dist/assets/{jira-C5UjxHE4.js → jira-CNeYi06w2.js} +0 -0
- /package/client/dist/assets/{jira-ZdKb-50F2.js → jira-D11gPhxz.js} +0 -0
- /package/client/dist/assets/{jira-DTYu3hJi2.js → jira-D38Gj7AT.js} +0 -0
- /package/client/dist/assets/{jira-B5wszJ1-2.js → jira-gAvOyije.js} +0 -0
- /package/client/dist/assets/{jira-DtZpokjJ.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-DKzYFcq6.js → loops-CPpgMapU.js} +0 -0
- /package/client/dist/assets/{loops-Du-bNVpc2.js → loops-CWWbKuVL.js} +0 -0
- /package/client/dist/assets/{loops-DDs4KPu_.js → loops-Crc9It8o2.js} +0 -0
- /package/client/dist/assets/{loops-Bi4ywYGn.js → loops-DH2hZcbK2.js} +0 -0
- /package/client/dist/assets/{loops-DvGbSfxw2.js → loops-DS5TmvIU2.js} +0 -0
- /package/client/dist/assets/{loops-D3wdUkZV.js → loops-P2f0cN8-.js} +0 -0
- /package/client/dist/assets/{loops-BjK8lmoZ2.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-DfmzoT5t.js → mcp-Czd_iyp02.js} +0 -0
- /package/client/dist/assets/{mcp-BG-OCJca2.js → mcp-D2pc5GGx.js} +0 -0
- /package/client/dist/assets/{mcp-CrWdq5db.js → mcp-D4ZjtIx32.js} +0 -0
- /package/client/dist/assets/{mcp-CbJ04oFS2.js → mcp-DIfBUv2D2.js} +0 -0
- /package/client/dist/assets/{mcp-Dd844F5_.js → mcp-DsTwmjST.js} +0 -0
- /package/client/dist/assets/{mcp-zSJ2TsJj.js → mcp-SMkn8boG.js} +0 -0
- /package/client/dist/assets/{mcp-DyULurYS2.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-DUuw9VIU2.js → nav-BRJBxaGt2.js} +0 -0
- /package/client/dist/assets/{nav-D0Erv9ZJ2.js → nav-CAGKv3_F.js} +0 -0
- /package/client/dist/assets/{nav-BY_4EWRv.js → nav-CNskRJKQ2.js} +0 -0
- /package/client/dist/assets/{nav-CVXtv0wX2.js → nav-DY93irBL.js} +0 -0
- /package/client/dist/assets/{nav-B2_ToNqS.js → nav-DhjvYgqt.js} +0 -0
- /package/client/dist/assets/{nav-S4HM94vW.js → nav-Gb-r4wop.js} +0 -0
- /package/client/dist/assets/{nav-BAMkVEId.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/{setup-JeBwoXoM.js → setup-CGZjy2IF2.js} +0 -0
- /package/client/dist/assets/{setup-1vN48B-m2.js → setup-CXa0Sx16.js} +0 -0
- /package/client/dist/assets/{setup-BmJ0Kx16.js → setup-D2ktKKMF2.js} +0 -0
- /package/client/dist/assets/{setup-T_ApeQDV2.js → setup-DG2H7ha5.js} +0 -0
- /package/client/dist/assets/{setup-DQWsu2TR.js → setup-DK5bHDUq2.js} +0 -0
- /package/client/dist/assets/{setup-q0wRHUp1.js → setup-DrWlIQTZ.js} +0 -0
- /package/client/dist/assets/{setup-mXnzww4f.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-BGRKhM8t.js → specs-BcyxaCwf2.js} +0 -0
- /package/client/dist/assets/{specs-GkiL-snY.js → specs-C1q9geAi.js} +0 -0
- /package/client/dist/assets/{specs-DbtIzV8a.js → specs-CLZKysq-2.js} +0 -0
- /package/client/dist/assets/{specs-Ce5y2iFc2.js → specs-DZMS9o54.js} +0 -0
- /package/client/dist/assets/{specs-CT8exGOy2.js → specs-Dm6zUI-R.js} +0 -0
- /package/client/dist/assets/{specs-C6J2zrLM.js → specs-V3TvhKLX.js} +0 -0
- /package/client/dist/assets/{specs-DZDoZsDq.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-BTpE7iki.js → terminal-BZiIu3rf.js} +0 -0
- /package/client/dist/assets/{terminal-DEyt0prF.js → terminal-C2NAd7BW2.js} +0 -0
- /package/client/dist/assets/{terminal-a6Uq8B0p.js → terminal-IahE4LW92.js} +0 -0
- /package/client/dist/assets/{terminal-DDk_bTw0.js → terminal-QpXZU2Bn2.js} +0 -0
- /package/client/dist/assets/{terminal-BNSg2lR7.js → terminal-ScjyXNxQ.js} +0 -0
- /package/client/dist/assets/{terminal-Cvabyzm42.js → terminal-d7GadXbN.js} +0 -0
- /package/client/dist/assets/{terminal-D0z12_sJ2.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
|
@@ -38,7 +38,7 @@ exports.isPrDecisionAction = isPrDecisionAction;
|
|
|
38
38
|
exports.executePrDecision = executePrDecision;
|
|
39
39
|
/**
|
|
40
40
|
* Execute a user's ask-first PR decision (safe-pr-review-flow) against its
|
|
41
|
-
* authoritative `rail_pr_deliveries` row: the
|
|
41
|
+
* authoritative `rail_pr_deliveries` row: the actions both decision
|
|
42
42
|
* surfaces (the dashboard rail row and the launching agent-chat card) POST to
|
|
43
43
|
* `/rails/pr-decision`.
|
|
44
44
|
*
|
|
@@ -57,8 +57,8 @@ exports.executePrDecision = executePrDecision;
|
|
|
57
57
|
* conflict). Without it, a repo with no GitHub remote wedges
|
|
58
58
|
* forever in retry(local-only)/discard — no way to ACCEPT work.
|
|
59
59
|
*
|
|
60
|
-
* Every mutation rides
|
|
61
|
-
*
|
|
60
|
+
* Every mutation rides a lease-owned compare-and-set (a raced concurrent
|
|
61
|
+
* decision loses with a 409 before effects) and re-broadcasts the durable
|
|
62
62
|
* `rail.pr_state` snapshot so both surfaces converge; agent-chat-originated
|
|
63
63
|
* rows also update their inline decision card in place. Deps are injected
|
|
64
64
|
* (db/git/exec/broadcast/jira/agent-chat) so the whole matrix is unit-testable
|
|
@@ -78,10 +78,18 @@ const rail_worktrees_store_1 = require("./rail-worktrees-store");
|
|
|
78
78
|
const ticket_store_1 = require("./ticket-store");
|
|
79
79
|
const workspace_resolution_1 = require("./workspace-resolution");
|
|
80
80
|
const agent_chat_registry_1 = require("./agent-chat-registry");
|
|
81
|
-
|
|
81
|
+
const ids_1 = require("./ids");
|
|
82
|
+
const repo_lock_1 = require("./repo-lock");
|
|
83
|
+
const rail_pr_ticket_effects_1 = require("./rail-pr-ticket-effects");
|
|
84
|
+
const pr_lifecycle_1 = require("./pr-lifecycle");
|
|
85
|
+
exports.PR_DECISION_ACTIONS = ['create-pr', 'publish', 'discard', 'dismiss', 'poll-merge', 'reopen', 'merge-local', 'acknowledge-no-changes'];
|
|
82
86
|
function isPrDecisionAction(v) {
|
|
83
87
|
return typeof v === 'string' && exports.PR_DECISION_ACTIONS.includes(v);
|
|
84
88
|
}
|
|
89
|
+
// Action handlers keep using one finalization helper, but the operation lease
|
|
90
|
+
// must be absent from every emitted snapshot. Calls made while a lease is held
|
|
91
|
+
// are deferred until executePrDecision's finally releases that exact token.
|
|
92
|
+
const deferredFinalizations = new Set();
|
|
85
93
|
function staleDecision(current) {
|
|
86
94
|
return { status: 409, body: { error: 'stale_decision', current } };
|
|
87
95
|
}
|
|
@@ -101,21 +109,31 @@ function ghFailed(r) {
|
|
|
101
109
|
function actionAllowed(action, row) {
|
|
102
110
|
switch (action) {
|
|
103
111
|
case 'create-pr':
|
|
104
|
-
return row.decision === 'on_review' ||
|
|
112
|
+
return row.decision === 'on_review' ||
|
|
113
|
+
(row.decision === 'pr_failed' && (row.delivery_outcome === 'retryable_failure' || row.delivery_outcome === 'unknown')) ||
|
|
105
114
|
(row.decision === 'pr_draft' && row.pr_url === null);
|
|
106
115
|
case 'publish':
|
|
107
116
|
return row.decision === 'pr_draft' && row.pr_url !== null;
|
|
108
117
|
case 'discard':
|
|
109
118
|
return row.decision === 'on_review' || row.decision === 'pr_draft' ||
|
|
110
|
-
row.decision === 'pr_ready' || row.decision === '
|
|
119
|
+
row.decision === 'pr_ready' || row.decision === 'pr_closed' || row.decision === 'no_changes' ||
|
|
120
|
+
row.decision === 'implementation_failed' ||
|
|
111
121
|
row.decision === 'pr_failed';
|
|
122
|
+
case 'dismiss':
|
|
123
|
+
return row.is_continuation === 1 && row.decision !== 'building' &&
|
|
124
|
+
row.decision !== 'merged' && row.decision !== 'discarded' && row.decision !== 'superseded' &&
|
|
125
|
+
row.delivery_outcome !== 'blocked';
|
|
112
126
|
case 'poll-merge':
|
|
113
|
-
return (row.decision === 'pr_draft' || row.decision === 'pr_ready') && row.pr_url !== null;
|
|
127
|
+
return (row.decision === 'pr_draft' || row.decision === 'pr_ready' || row.decision === 'pr_closed') && row.pr_url !== null;
|
|
128
|
+
case 'reopen':
|
|
129
|
+
return row.decision === 'pr_closed' && row.pr_url !== null;
|
|
114
130
|
case 'merge-local':
|
|
115
131
|
// Remote-less acceptance ONLY: once a real PR exists (pr_url), GitHub is
|
|
116
132
|
// the merge authority — merging under an open PR would leave it dangling.
|
|
117
133
|
return row.pr_url === null &&
|
|
118
134
|
(row.decision === 'on_review' || row.decision === 'pr_failed' || row.decision === 'pr_draft');
|
|
135
|
+
case 'acknowledge-no-changes':
|
|
136
|
+
return row.decision === 'no_changes' && row.is_continuation !== 1;
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
async function executePrDecision(deps, input) {
|
|
@@ -128,12 +146,43 @@ async function executePrDecision(deps, input) {
|
|
|
128
146
|
return staleDecision(row.decision);
|
|
129
147
|
if (!actionAllowed(input.action, row))
|
|
130
148
|
return illegalAction(row.decision);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
// Claim before any git/GitHub/cleanup/ticket effect. The visible decision is
|
|
150
|
+
// unchanged while the winner works, but a second surface cannot start.
|
|
151
|
+
const operationToken = (0, ids_1.newId)();
|
|
152
|
+
if (!(0, rail_pr_store_1.claimPrDeliveryOperation)(deps.db, row.id, row.decision, input.action, operationToken)) {
|
|
153
|
+
const current = (0, rail_pr_store_1.getPrDelivery)(deps.db, row.id);
|
|
154
|
+
if (!current || current.decision !== input.expectedDecision) {
|
|
155
|
+
return staleDecision(current?.decision ?? row.decision);
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
status: 409,
|
|
159
|
+
body: { error: 'operation_in_progress', current: current.decision, operation: current.operation },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const claimedRow = (0, rail_pr_store_1.getPrDelivery)(deps.db, row.id);
|
|
163
|
+
if (!claimedRow || claimedRow.operation_token !== operationToken) {
|
|
164
|
+
return { status: 409, body: { error: 'operation_in_progress', current: claimedRow?.decision ?? row.decision } };
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
switch (input.action) {
|
|
168
|
+
case 'create-pr': return await runCreatePr(deps, claimedRow);
|
|
169
|
+
case 'publish': return await runPublish(deps, claimedRow);
|
|
170
|
+
case 'discard': return await runDiscard(deps, claimedRow);
|
|
171
|
+
case 'dismiss': return await runDismiss(deps, claimedRow);
|
|
172
|
+
case 'poll-merge': return await runPollMerge(deps, claimedRow);
|
|
173
|
+
case 'reopen': return await runReopen(deps, claimedRow);
|
|
174
|
+
case 'merge-local': return await runMergeLocal(deps, claimedRow);
|
|
175
|
+
case 'acknowledge-no-changes': return await runAcknowledgeNoChanges(deps, claimedRow);
|
|
176
|
+
}
|
|
177
|
+
return illegalAction(row.decision);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
try {
|
|
181
|
+
(0, rail_pr_store_1.releasePrDeliveryOperation)(deps.db, row.id, operationToken);
|
|
182
|
+
}
|
|
183
|
+
catch { /* durable state remains authoritative */ }
|
|
184
|
+
if (deferredFinalizations.delete(row.id))
|
|
185
|
+
finalizeTransition(deps, row.id);
|
|
137
186
|
}
|
|
138
187
|
}
|
|
139
188
|
/**
|
|
@@ -141,11 +190,30 @@ async function executePrDecision(deps, input) {
|
|
|
141
190
|
* between the pre-check and here — surface the same 409 with the fresh state.
|
|
142
191
|
*/
|
|
143
192
|
function casTransition(deps, row, next, patch = {}) {
|
|
144
|
-
if ((0, rail_pr_store_1.
|
|
193
|
+
if (row.operation_token && (0, rail_pr_store_1.transitionClaimedDecision)(deps.db, row.id, row.decision, next, row.operation_token, patch))
|
|
194
|
+
return null;
|
|
195
|
+
const current = (0, rail_pr_store_1.getPrDelivery)(deps.db, row.id);
|
|
196
|
+
return staleDecision(current?.decision ?? row.decision);
|
|
197
|
+
}
|
|
198
|
+
function casTransitionWithTicketEffect(deps, row, next, patch, effect) {
|
|
199
|
+
if (row.operation_token &&
|
|
200
|
+
(0, rail_pr_ticket_effects_1.transitionClaimedDecisionWithTicketEffect)(deps.db, row.id, row.decision, next, row.operation_token, patch, effect))
|
|
145
201
|
return null;
|
|
146
202
|
const current = (0, rail_pr_store_1.getPrDelivery)(deps.db, row.id);
|
|
147
203
|
return staleDecision(current?.decision ?? row.decision);
|
|
148
204
|
}
|
|
205
|
+
function applyTerminalTicketEffect(deps, row, terminalDecision, cleanupWarnings) {
|
|
206
|
+
const result = (0, rail_pr_ticket_effects_1.applyRailPrTicketEffect)(deps, row.id);
|
|
207
|
+
if (result.ok)
|
|
208
|
+
return;
|
|
209
|
+
cleanupWarnings.push(`ticket status update pending: ${result.error ?? 'unknown ticket-store error'}`);
|
|
210
|
+
if (!row.operation_token)
|
|
211
|
+
return;
|
|
212
|
+
(0, rail_pr_store_1.transitionClaimedDecision)(deps.db, row.id, terminalDecision, terminalDecision, row.operation_token, {
|
|
213
|
+
statusCode: 'cleanup_incomplete',
|
|
214
|
+
cleanupWarnings,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
149
217
|
/**
|
|
150
218
|
* Post-transition fan-out: re-broadcast the durable rail.pr_state snapshot
|
|
151
219
|
* (both surfaces converge on it) and, for agent-chat-originated launches,
|
|
@@ -156,6 +224,10 @@ function finalizeTransition(deps, id) {
|
|
|
156
224
|
if (!row)
|
|
157
225
|
return undefined;
|
|
158
226
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
227
|
+
if (row.operation_token) {
|
|
228
|
+
deferredFinalizations.add(id);
|
|
229
|
+
return snap;
|
|
230
|
+
}
|
|
159
231
|
deps.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(deps.project.id, snap));
|
|
160
232
|
if (row.origin_conversation_id) {
|
|
161
233
|
const agent = (deps.agentChat ?? agent_chat_registry_1.getAgentChatManager)();
|
|
@@ -174,45 +246,189 @@ function batchWorktreeRoot(slug) {
|
|
|
174
246
|
// Mirrors rail-isolated-launch's worktreesRoot — derivable, never stored.
|
|
175
247
|
return path.join((0, artifact_registry_1.resolveHome)(), '.specrails', 'projects', slug, 'worktrees');
|
|
176
248
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
249
|
+
/** A legacy record's `succeeded` bit meant delivery eligibility. New records
|
|
250
|
+
* state that eligibility directly; blocked/no-change units are never swept as
|
|
251
|
+
* a side effect of delivering another unit in the same batch. */
|
|
252
|
+
function unitWasDelivered(unit) {
|
|
253
|
+
return unit.deliveryOutcome === undefined ? unit.succeeded : unit.deliveryOutcome === 'ready';
|
|
254
|
+
}
|
|
255
|
+
/** Branch deletion is ownership-based, never name-derived. Legacy rows without
|
|
256
|
+
* ownership evidence degrade to preservation. A multi-unit assembled head is
|
|
257
|
+
* owned when it is distinct from every recorded unit branch. */
|
|
258
|
+
function ownedDeliveryBranches(row, snap, cleanupWarnings) {
|
|
259
|
+
const unknownOwnership = row.is_continuation !== 1
|
|
260
|
+
? snap.branches.filter((unit) => unitWasDelivered(unit) && unit.branchOwnership === undefined)
|
|
261
|
+
: [];
|
|
262
|
+
if (cleanupWarnings && unknownOwnership.length > 0) {
|
|
263
|
+
cleanupWarnings.push(`branch cleanup preserved ${unknownOwnership.length} legacy ${unknownOwnership.length === 1 ? 'branch' : 'branches'} because ownership was not recorded`);
|
|
190
264
|
}
|
|
191
|
-
|
|
265
|
+
const owned = new Set(snap.branches
|
|
266
|
+
.filter((unit) => unit.branchOwnership === 'created' && unitWasDelivered(unit))
|
|
267
|
+
.map((unit) => unit.branch));
|
|
268
|
+
if (row.is_continuation !== 1 && snap.branches.length > 1 && row.branch &&
|
|
269
|
+
!snap.branches.some((unit) => unit.branch === row.branch))
|
|
270
|
+
owned.add(row.branch);
|
|
271
|
+
return owned;
|
|
272
|
+
}
|
|
273
|
+
function releasableWorktreeIds(deps, snap) {
|
|
274
|
+
const preservedBranches = new Set(snap.branches.filter((unit) => !unitWasDelivered(unit)).map((unit) => unit.branch));
|
|
275
|
+
return snap.worktreeIds.filter((id) => {
|
|
276
|
+
const wt = (0, rail_worktrees_store_1.getRailWorktree)(deps.db, id);
|
|
277
|
+
return !wt || !preservedBranches.has(wt.branch);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async function observePrLifecycle(deps, prUrl) {
|
|
281
|
+
const observed = await (0, pr_lifecycle_1.observePrLifecycle)(deps.exec, deps.project.path, prUrl);
|
|
282
|
+
return observed.ok ? { state: observed.state, isDraft: observed.isDraft } : null;
|
|
283
|
+
}
|
|
284
|
+
function mergedEvidenceDetail(observation) {
|
|
285
|
+
return observation.includesExpectedSha === null
|
|
286
|
+
? 'GitHub did not return enough commit evidence to prove the previous PR included the verified implementation commit'
|
|
287
|
+
: 'the previous PR merged without the verified implementation commit';
|
|
288
|
+
}
|
|
289
|
+
async function rerouteFromStalePr(deps, row, detail, exactWasPushed) {
|
|
290
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
291
|
+
const cleanupWarnings = await (0, rail_worktree_release_1.releaseRailWorktrees)({
|
|
292
|
+
db: deps.db, git: deps.git, repoDir: deps.project.path,
|
|
293
|
+
worktreeIds: releasableWorktreeIds(deps, snap),
|
|
294
|
+
});
|
|
295
|
+
const partial = row.implementation_outcome === 'partially_succeeded';
|
|
296
|
+
const conflict = casTransition(deps, row, 'on_review', {
|
|
297
|
+
prUrl: null,
|
|
298
|
+
prNumber: null,
|
|
299
|
+
prState: exactWasPushed ? 'pushed' : 'local-only',
|
|
300
|
+
deliveryOutcome: partial ? 'partial' : 'ready',
|
|
301
|
+
statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : partial ? 'partial_success' : 'ready_for_review',
|
|
302
|
+
statusDetail: `${detail}; create a new draft PR from the preserved exact commit`,
|
|
303
|
+
cleanupWarnings,
|
|
304
|
+
// The replacement PR belongs to this generation. Per-unit branch ownership
|
|
305
|
+
// remains unchanged so a borrowed/pre-existing ref is still never deleted.
|
|
306
|
+
isContinuation: false,
|
|
307
|
+
});
|
|
308
|
+
if (conflict)
|
|
309
|
+
return conflict;
|
|
310
|
+
const after = finalizeTransition(deps, row.id);
|
|
311
|
+
return {
|
|
312
|
+
status: 200,
|
|
313
|
+
body: {
|
|
314
|
+
ok: true,
|
|
315
|
+
decision: 'on_review',
|
|
316
|
+
prUrl: null,
|
|
317
|
+
prState: after?.prState ?? (exactWasPushed ? 'pushed' : 'local-only'),
|
|
318
|
+
rerouted: true,
|
|
319
|
+
detail,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
async function settleObservedExistingPr(deps, row, observation) {
|
|
324
|
+
const next = observation.state === 'MERGED' || observation.isDraft === false ? 'pr_ready' : 'pr_draft';
|
|
325
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
326
|
+
const cleanupWarnings = await (0, rail_worktree_release_1.releaseRailWorktrees)({
|
|
327
|
+
db: deps.db, git: deps.git, repoDir: deps.project.path,
|
|
328
|
+
worktreeIds: releasableWorktreeIds(deps, snap),
|
|
329
|
+
});
|
|
330
|
+
const conflict = casTransition(deps, row, next, {
|
|
331
|
+
prState: 'pr-created',
|
|
332
|
+
deliveryOutcome: 'delivered',
|
|
333
|
+
statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : next === 'pr_ready' ? 'pr_ready' : 'existing_pr_updated',
|
|
334
|
+
cleanupWarnings,
|
|
335
|
+
});
|
|
336
|
+
if (conflict)
|
|
337
|
+
return conflict;
|
|
338
|
+
const after = finalizeTransition(deps, row.id);
|
|
339
|
+
return { status: 200, body: { ok: true, decision: next, prUrl: after?.prUrl ?? row.pr_url, prState: 'pr-created' } };
|
|
340
|
+
}
|
|
341
|
+
function persistRetryablePrObservationFailure(deps, row, detail) {
|
|
342
|
+
const conflict = casTransition(deps, row, 'pr_failed', {
|
|
343
|
+
deliveryOutcome: 'retryable_failure',
|
|
344
|
+
statusCode: 'push_failed',
|
|
345
|
+
statusDetail: detail,
|
|
346
|
+
});
|
|
347
|
+
if (conflict)
|
|
348
|
+
return conflict;
|
|
349
|
+
finalizeTransition(deps, row.id);
|
|
350
|
+
return { status: 200, body: { ok: true, decision: 'pr_failed', prUrl: row.pr_url, detail } };
|
|
351
|
+
}
|
|
352
|
+
function persistMovedPrHead(deps, row, detail) {
|
|
353
|
+
const conflict = casTransition(deps, row, 'pr_failed', {
|
|
354
|
+
deliveryOutcome: 'blocked',
|
|
355
|
+
statusCode: 'branch_verification_failed',
|
|
356
|
+
statusDetail: detail,
|
|
357
|
+
});
|
|
358
|
+
if (conflict)
|
|
359
|
+
return conflict;
|
|
360
|
+
finalizeTransition(deps, row.id);
|
|
361
|
+
return { status: 200, body: { ok: true, decision: 'pr_failed', prUrl: row.pr_url, detail } };
|
|
192
362
|
}
|
|
193
363
|
async function retryExistingPrFollowupPush(deps, row) {
|
|
194
364
|
if (!row.branch || !row.pr_url)
|
|
195
365
|
return { status: 502, body: { error: 'push_failed', detail: 'missing PR branch/url' } };
|
|
366
|
+
if (!row.delivery_sha) {
|
|
367
|
+
const detail = 'missing verified delivery SHA; refusing to resolve a mutable branch for retry';
|
|
368
|
+
const conflict = casTransition(deps, row, 'pr_failed', {
|
|
369
|
+
deliveryOutcome: 'blocked',
|
|
370
|
+
statusCode: 'branch_verification_failed',
|
|
371
|
+
statusDetail: detail,
|
|
372
|
+
});
|
|
373
|
+
if (conflict)
|
|
374
|
+
return conflict;
|
|
375
|
+
finalizeTransition(deps, row.id);
|
|
376
|
+
return { status: 409, body: { error: 'missing_verified_sha', decision: 'pr_failed', prUrl: row.pr_url, detail } };
|
|
377
|
+
}
|
|
378
|
+
const beforePush = await (0, pr_lifecycle_1.observePrLifecycle)(deps.exec, deps.project.path, row.pr_url, row.delivery_sha);
|
|
379
|
+
if (!beforePush.ok) {
|
|
380
|
+
return persistRetryablePrObservationFailure(deps, row, `could not confirm the existing PR is open before retry: ${beforePush.detail}`);
|
|
381
|
+
}
|
|
382
|
+
if (!(0, pr_lifecycle_1.isExactOpenPr)(beforePush, row.branch, row.base_branch)) {
|
|
383
|
+
if (beforePush.state === 'MERGED' && beforePush.includesExpectedSha === true) {
|
|
384
|
+
return settleObservedExistingPr(deps, row, beforePush);
|
|
385
|
+
}
|
|
386
|
+
if (beforePush.state === 'MERGED') {
|
|
387
|
+
return rerouteFromStalePr(deps, row, mergedEvidenceDetail(beforePush), false);
|
|
388
|
+
}
|
|
389
|
+
if (beforePush.state === 'CLOSED') {
|
|
390
|
+
return rerouteFromStalePr(deps, row, 'the previous PR closed before this implementation was delivered', false);
|
|
391
|
+
}
|
|
392
|
+
return persistMovedPrHead(deps, row, 'the existing PR no longer matches its recorded head/base; the verified commit was not pushed');
|
|
393
|
+
}
|
|
196
394
|
const pushed = await (0, pr_publisher_1.pushBranch)(deps.exec, {
|
|
197
395
|
repoDir: deps.project.path,
|
|
198
396
|
branch: row.branch,
|
|
199
397
|
baseBranch: row.base_branch,
|
|
398
|
+
sourceSha: row.delivery_sha,
|
|
200
399
|
});
|
|
201
400
|
if (pushed.state === 'local-only') {
|
|
202
|
-
const conflict = casTransition(deps, row, 'pr_failed', {
|
|
401
|
+
const conflict = casTransition(deps, row, 'pr_failed', {
|
|
402
|
+
prState: 'local-only',
|
|
403
|
+
deliveryOutcome: 'retryable_failure',
|
|
404
|
+
statusCode: 'push_failed',
|
|
405
|
+
statusDetail: pushed.reason,
|
|
406
|
+
});
|
|
203
407
|
if (conflict)
|
|
204
408
|
return conflict;
|
|
205
409
|
finalizeTransition(deps, row.id);
|
|
206
410
|
return { status: 200, body: { ok: true, decision: 'pr_failed', prUrl: row.pr_url, detail: pushed.reason } };
|
|
207
411
|
}
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
412
|
+
const afterPush = await (0, pr_lifecycle_1.observePrLifecycle)(deps.exec, deps.project.path, row.pr_url, row.delivery_sha);
|
|
413
|
+
if (!afterPush.ok) {
|
|
414
|
+
return persistRetryablePrObservationFailure(deps, row, `exact commit was pushed, but the PR lifecycle could not be confirmed: ${afterPush.detail}`);
|
|
415
|
+
}
|
|
416
|
+
if ((0, pr_lifecycle_1.isExactOpenPr)(afterPush, row.branch, row.base_branch)) {
|
|
417
|
+
if (afterPush.headRefOid?.toLowerCase() !== row.delivery_sha.toLowerCase()) {
|
|
418
|
+
return persistMovedPrHead(deps, row, 'the PR head moved after the exact push; the verified commit is preserved but no longer proven as the remote head');
|
|
419
|
+
}
|
|
420
|
+
return settleObservedExistingPr(deps, row, afterPush);
|
|
421
|
+
}
|
|
422
|
+
if (afterPush.state === 'MERGED' && afterPush.includesExpectedSha === true) {
|
|
423
|
+
return settleObservedExistingPr(deps, row, afterPush);
|
|
424
|
+
}
|
|
425
|
+
if (afterPush.state === 'MERGED') {
|
|
426
|
+
return rerouteFromStalePr(deps, row, mergedEvidenceDetail(afterPush), true);
|
|
427
|
+
}
|
|
428
|
+
if (afterPush.state === 'CLOSED') {
|
|
429
|
+
return rerouteFromStalePr(deps, row, 'the previous PR closed before this implementation was delivered', true);
|
|
430
|
+
}
|
|
431
|
+
return persistMovedPrHead(deps, row, 'the existing PR no longer matches its recorded head/base after the exact push');
|
|
216
432
|
}
|
|
217
433
|
/**
|
|
218
434
|
* Load the PR's ticket data (title/labels/description) from the ticket store,
|
|
@@ -254,63 +470,69 @@ async function branchExists(deps, branch) {
|
|
|
254
470
|
const r = await deps.git.run(['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], deps.project.path);
|
|
255
471
|
return r.code === 0;
|
|
256
472
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
* rail_worktrees ledger (newest first) for ANY other branch a rail allocated
|
|
262
|
-
* for the same ticket that still exists AND has commits ahead of the base —
|
|
263
|
-
* that branch is the real carrier of the work. Null when nothing recoverable.
|
|
264
|
-
*/
|
|
265
|
-
async function findRecoverableBranch(deps, unit, baseBranch) {
|
|
266
|
-
let candidates = [];
|
|
473
|
+
const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
474
|
+
async function commitObjectExists(deps, sha) {
|
|
475
|
+
if (!COMMIT_SHA_RE.test(sha))
|
|
476
|
+
return false;
|
|
267
477
|
try {
|
|
268
|
-
|
|
478
|
+
return (await deps.git.run(['cat-file', '-e', `${sha}^{commit}`], deps.project.path)).code === 0;
|
|
269
479
|
}
|
|
270
480
|
catch {
|
|
271
|
-
return
|
|
481
|
+
return false;
|
|
272
482
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
483
|
+
}
|
|
484
|
+
/** Resolve a legacy mutable ref once into an immutable object. New settlement
|
|
485
|
+
* rows already carry finalSha and never take this compatibility path. */
|
|
486
|
+
async function captureBranchSha(deps, branch) {
|
|
487
|
+
try {
|
|
488
|
+
const result = await deps.git.run(['rev-parse', '--verify', `refs/heads/${branch}`], deps.project.path);
|
|
489
|
+
const sha = result.code === 0 ? result.stdout.trim() : '';
|
|
490
|
+
return COMMIT_SHA_RE.test(sha) ? sha : null;
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
return null;
|
|
282
494
|
}
|
|
283
|
-
return null;
|
|
284
495
|
}
|
|
285
496
|
async function runCreatePr(deps, row) {
|
|
286
497
|
if (row.decision === 'pr_failed' && row.pr_url && row.branch) {
|
|
287
498
|
return retryExistingPrFollowupPush(deps, row);
|
|
288
499
|
}
|
|
289
500
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
// and persist the substitution on the row; unrecoverable → honest pr_failed.
|
|
501
|
+
// Resolve every deliverable unit to an immutable object. New rows carry the
|
|
502
|
+
// verified final SHA from settlement; legacy rows may capture their recorded
|
|
503
|
+
// ref once. Never substitute another historical branch merely because it
|
|
504
|
+
// belongs to the same ticket.
|
|
295
505
|
let effectiveBranches = snap.branches;
|
|
296
|
-
let
|
|
297
|
-
for (
|
|
506
|
+
let branchEvidenceCaptured = false;
|
|
507
|
+
for (let index = 0; index < snap.branches.length; index++) {
|
|
508
|
+
const unit = snap.branches[index];
|
|
298
509
|
if (!unit.succeeded)
|
|
299
510
|
continue;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
511
|
+
let sourceSha = unit.finalSha && COMMIT_SHA_RE.test(unit.finalSha) ? unit.finalSha : null;
|
|
512
|
+
if (sourceSha) {
|
|
513
|
+
if (!(await commitObjectExists(deps, sourceSha)))
|
|
514
|
+
sourceSha = null;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
sourceSha = await captureBranchSha(deps, unit.branch);
|
|
518
|
+
if (sourceSha && !(await commitObjectExists(deps, sourceSha)))
|
|
519
|
+
sourceSha = null;
|
|
520
|
+
}
|
|
521
|
+
if (!sourceSha) {
|
|
522
|
+
const detail = `the verified commit for branch '${unit.branch}' (ticket #${unit.ticketId}) is unavailable; refusing to deliver a mutable or unrelated ref`;
|
|
523
|
+
const conflict = casTransition(deps, row, 'pr_failed', {
|
|
524
|
+
deliveryOutcome: 'blocked', statusCode: 'branch_verification_failed', statusDetail: detail,
|
|
525
|
+
});
|
|
306
526
|
if (conflict)
|
|
307
527
|
return conflict;
|
|
308
528
|
finalizeTransition(deps, row.id);
|
|
309
529
|
return { status: 200, body: { ok: true, decision: 'pr_failed', detail } };
|
|
310
530
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
531
|
+
if (unit.finalSha !== sourceSha)
|
|
532
|
+
branchEvidenceCaptured = true;
|
|
533
|
+
effectiveBranches = effectiveBranches.map((branch, branchIndex) => branchIndex === index
|
|
534
|
+
? { ...branch, finalSha: sourceSha }
|
|
535
|
+
: branch);
|
|
314
536
|
}
|
|
315
537
|
// Succeeded COVERED tickets for the PR title/body: a single unit covers every
|
|
316
538
|
// launch ticket (scope='all'), per-ticket units cover exactly their own.
|
|
@@ -322,16 +544,7 @@ async function runCreatePr(deps, row) {
|
|
|
322
544
|
// The batch name is derived ONLY for multi-unit deliveries — the single-ticket
|
|
323
545
|
// path delivers straight from its unit branch and must never even compute one.
|
|
324
546
|
const batchPreferred = succeededUnits.length > 1 ? (0, pr_naming_1.batchBranchNameFor)(ticketData) : null;
|
|
325
|
-
// Retry hazard: deliverRailAsPr assembles with `worktree add -b` (no -B) and a
|
|
326
|
-
// prior degraded delivery KEEPS the batch branch — so a retry would die on
|
|
327
|
-
// "branch already exists". Defensively drop the stale batch branch first
|
|
328
|
-
// (batch deliveries only). HARD GUARDS: NEVER a branch present in the row's
|
|
329
|
-
// unit records (deleting one would destroy the commits about to be delivered)
|
|
330
|
-
// and NEVER the integration branch; a missing branch is tolerated.
|
|
331
547
|
const unitBranches = new Set(effectiveBranches.map((b) => b.branch));
|
|
332
|
-
if (batchPreferred && batchPreferred !== row.base_branch && !unitBranches.has(batchPreferred)) {
|
|
333
|
-
await deps.git.run(['branch', '-D', batchPreferred], deps.project.path).catch(() => { });
|
|
334
|
-
}
|
|
335
548
|
// Per-ticket covering branch for the body's honest Tests digest: per-ticket
|
|
336
549
|
// units map 1:1; a single 'all'-scope unit covers every launch ticket.
|
|
337
550
|
const branchFor = (ticketId) => {
|
|
@@ -358,38 +571,75 @@ async function runCreatePr(deps, row) {
|
|
|
358
571
|
});
|
|
359
572
|
let next;
|
|
360
573
|
// Underlying git/gh failure detail for a degraded or failed delivery —
|
|
361
|
-
// relayed on the response
|
|
362
|
-
// of a bare "retry". Never persisted (the durable columns stay state-only).
|
|
574
|
+
// relayed on the response and persisted as bounded secondary diagnostics.
|
|
363
575
|
let detail = null;
|
|
364
576
|
const patch = {};
|
|
365
|
-
|
|
366
|
-
// actually carries the commits — the next retry/discard works from reality.
|
|
367
|
-
if (branchesRecovered)
|
|
577
|
+
if (branchEvidenceCaptured)
|
|
368
578
|
patch.branches = effectiveBranches;
|
|
369
579
|
try {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
580
|
+
// A degraded multi-unit attempt already owns an assembled batch branch.
|
|
581
|
+
// Retry that exact head so exact head/base PR discovery remains idempotent;
|
|
582
|
+
// never `branch -D` a merely name-matching ref, which could destroy a user's
|
|
583
|
+
// unrelated or subsequently edited local branch.
|
|
584
|
+
let degradedBatchSha = null;
|
|
585
|
+
if (batchPreferred && row.branch && row.pr_url === null &&
|
|
586
|
+
(row.pr_state === 'pushed' || row.pr_state === 'local-only') &&
|
|
587
|
+
row.branch !== row.base_branch && !unitBranches.has(row.branch)) {
|
|
588
|
+
if (row.delivery_sha && await commitObjectExists(deps, row.delivery_sha)) {
|
|
589
|
+
degradedBatchSha = row.delivery_sha;
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
degradedBatchSha = await captureBranchSha(deps, row.branch);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const result = degradedBatchSha
|
|
596
|
+
? {
|
|
597
|
+
state: 'delivered',
|
|
598
|
+
branch: row.branch,
|
|
599
|
+
pr: await (0, pr_publisher_1.publishDraftPr)(deps.exec, {
|
|
600
|
+
repoDir: deps.project.path,
|
|
601
|
+
branch: row.branch,
|
|
602
|
+
baseBranch: row.base_branch,
|
|
603
|
+
title,
|
|
604
|
+
body,
|
|
605
|
+
sourceSha: degradedBatchSha,
|
|
606
|
+
}),
|
|
607
|
+
ticketIds: succeededTicketIds,
|
|
608
|
+
}
|
|
609
|
+
: await (0, rail_pr_delivery_1.deliverRailAsPr)(deps.git, deps.exec, {
|
|
610
|
+
baseRepo: deps.project.path,
|
|
611
|
+
integrationBranch: row.base_branch,
|
|
612
|
+
railKey: row.rail_key,
|
|
613
|
+
batchBranch: batchPreferred ?? undefined,
|
|
614
|
+
batchWorktreeRoot: batchWorktreeRoot(deps.project.slug),
|
|
615
|
+
branches: effectiveBranches.map((unit) => ({
|
|
616
|
+
...unit,
|
|
617
|
+
sourceSha: unit.finalSha ?? undefined,
|
|
618
|
+
})),
|
|
619
|
+
title,
|
|
620
|
+
body,
|
|
621
|
+
});
|
|
380
622
|
if (result.state === 'delivered') {
|
|
381
|
-
next = 'pr_draft';
|
|
623
|
+
next = result.pr.state === 'pr-created' && result.pr.isDraft === false ? 'pr_ready' : 'pr_draft';
|
|
382
624
|
patch.branch = result.branch;
|
|
383
625
|
patch.prState = result.pr.state;
|
|
626
|
+
const resultSha = degradedBatchSha
|
|
627
|
+
?? (succeededUnits.length === 1 ? succeededUnits[0].finalSha ?? null : await captureBranchSha(deps, result.branch));
|
|
628
|
+
if (resultSha && await commitObjectExists(deps, resultSha))
|
|
629
|
+
patch.deliverySha = resultSha;
|
|
384
630
|
if (result.pr.state === 'pr-created' && result.pr.prUrl) {
|
|
385
631
|
patch.prUrl = result.pr.prUrl;
|
|
386
632
|
patch.prNumber = parsePrNumber(result.pr.prUrl);
|
|
633
|
+
patch.deliveryOutcome = 'delivered';
|
|
634
|
+
patch.statusCode = next === 'pr_ready' ? 'pr_ready' : 'pr_draft_ready';
|
|
387
635
|
}
|
|
388
636
|
else {
|
|
389
637
|
// Degraded (pushed / local-only): no PR exists — only retry or discard.
|
|
390
638
|
patch.prUrl = null;
|
|
391
639
|
patch.prNumber = null;
|
|
392
640
|
detail = result.pr.reason ?? null;
|
|
641
|
+
patch.deliveryOutcome = 'retryable_failure';
|
|
642
|
+
patch.statusCode = result.pr.state === 'local-only' ? 'push_failed' : 'delivery_failed';
|
|
393
643
|
}
|
|
394
644
|
}
|
|
395
645
|
else {
|
|
@@ -397,6 +647,11 @@ async function runCreatePr(deps, row) {
|
|
|
397
647
|
// succeeded unit, but a wedged row must not 500) → retryable failure.
|
|
398
648
|
next = 'pr_failed';
|
|
399
649
|
detail = result.reason;
|
|
650
|
+
const retryable = !result.reason.startsWith('merge-conflict:') &&
|
|
651
|
+
!result.reason.startsWith('batch-branch-collision:') &&
|
|
652
|
+
result.reason !== 'missing-batch-branch' && result.state === 'assembly-failed';
|
|
653
|
+
patch.deliveryOutcome = retryable ? 'retryable_failure' : 'blocked';
|
|
654
|
+
patch.statusCode = 'delivery_failed';
|
|
400
655
|
}
|
|
401
656
|
}
|
|
402
657
|
catch (err) {
|
|
@@ -405,13 +660,19 @@ async function runCreatePr(deps, row) {
|
|
|
405
660
|
console.error('[rail-pr-decision] create-pr delivery failed:', err);
|
|
406
661
|
next = 'pr_failed';
|
|
407
662
|
detail = err instanceof Error ? err.message : String(err);
|
|
663
|
+
patch.deliveryOutcome = 'blocked';
|
|
664
|
+
patch.statusCode = 'delivery_failed';
|
|
665
|
+
}
|
|
666
|
+
if (detail)
|
|
667
|
+
patch.statusDetail = detail;
|
|
668
|
+
if ((next === 'pr_draft' || next === 'pr_ready') && patch.prUrl) {
|
|
669
|
+
patch.cleanupWarnings = await (0, rail_worktree_release_1.releaseRailWorktrees)({
|
|
670
|
+
db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: releasableWorktreeIds(deps, snap),
|
|
671
|
+
});
|
|
408
672
|
}
|
|
409
673
|
const conflict = casTransition(deps, row, next, patch);
|
|
410
674
|
if (conflict)
|
|
411
675
|
return conflict;
|
|
412
|
-
if (next === 'pr_draft' && patch.prUrl) {
|
|
413
|
-
await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds });
|
|
414
|
-
}
|
|
415
676
|
const after = finalizeTransition(deps, row.id);
|
|
416
677
|
// Tickets stay on_review — a draft PR is still awaiting the engineer's merge.
|
|
417
678
|
return {
|
|
@@ -426,158 +687,310 @@ async function runCreatePr(deps, row) {
|
|
|
426
687
|
};
|
|
427
688
|
}
|
|
428
689
|
async function runPublish(deps, row) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
690
|
+
let r;
|
|
691
|
+
try {
|
|
692
|
+
r = await deps.exec.run('gh', ['pr', 'ready', row.pr_url], deps.project.path);
|
|
693
|
+
}
|
|
694
|
+
catch (err) {
|
|
695
|
+
r = { code: 1, stdout: '', stderr: err instanceof Error ? err.message : String(err) };
|
|
696
|
+
}
|
|
697
|
+
if (r.code !== 0) {
|
|
698
|
+
// Crash/timeout ambiguity: `gh` may have made the PR ready before the local
|
|
699
|
+
// command reported failure. Adopt exact remote truth instead of retrying
|
|
700
|
+
// forever on "already ready".
|
|
701
|
+
const lifecycle = await observePrLifecycle(deps, row.pr_url);
|
|
702
|
+
if (lifecycle?.state === 'CLOSED') {
|
|
703
|
+
const conflict = casTransition(deps, row, 'pr_closed', {
|
|
704
|
+
deliveryOutcome: 'delivered', statusCode: 'pr_closed',
|
|
705
|
+
});
|
|
706
|
+
if (conflict)
|
|
707
|
+
return conflict;
|
|
708
|
+
finalizeTransition(deps, row.id);
|
|
709
|
+
return { status: 200, body: { ok: true, decision: 'pr_closed', closed: true, prUrl: row.pr_url } };
|
|
710
|
+
}
|
|
711
|
+
if (lifecycle?.state !== 'OPEN' || lifecycle.isDraft)
|
|
712
|
+
return ghFailed(r);
|
|
713
|
+
}
|
|
432
714
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
433
|
-
const
|
|
715
|
+
const cleanupWarnings = await (0, rail_worktree_release_1.releaseRailWorktrees)({
|
|
716
|
+
db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: releasableWorktreeIds(deps, snap),
|
|
717
|
+
});
|
|
718
|
+
const conflict = casTransition(deps, row, 'pr_ready', {
|
|
719
|
+
deliveryOutcome: 'delivered', statusCode: 'pr_ready', cleanupWarnings,
|
|
720
|
+
});
|
|
434
721
|
if (conflict)
|
|
435
722
|
return conflict;
|
|
436
|
-
await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds });
|
|
437
723
|
finalizeTransition(deps, row.id);
|
|
438
724
|
return { status: 200, body: { ok: true, decision: 'pr_ready', prUrl: row.pr_url } };
|
|
439
725
|
}
|
|
440
|
-
async function runDiscard(deps, row) {
|
|
726
|
+
async function runDiscard(deps, row, opts = { removeNeedsReview: true }) {
|
|
441
727
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
442
728
|
const implementationFailed = row.decision === 'implementation_failed';
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
// must not close or delete the user's existing review branch.
|
|
450
|
-
if (row.pr_url && !implementationFailed) {
|
|
729
|
+
const preserveBorrowedReview = row.is_continuation === 1;
|
|
730
|
+
const cleanupWarnings = [];
|
|
731
|
+
// A continuation borrows an existing PR/head. Discarding its local iteration
|
|
732
|
+
// may remove Specrails' worktree, but never closes or deletes borrowed review
|
|
733
|
+
// state. Fresh deliveries retain the original destructive semantics.
|
|
734
|
+
if (row.pr_url && !implementationFailed && !preserveBorrowedReview) {
|
|
451
735
|
try {
|
|
452
736
|
const r = await deps.exec.run('gh', ['pr', 'close', row.pr_url, '--delete-branch'], deps.project.path);
|
|
453
737
|
if (r.code !== 0) {
|
|
454
|
-
|
|
738
|
+
const detail = (r.stderr.trim() || r.stdout.trim()).split('\n')[0] || `exit ${r.code}`;
|
|
739
|
+
cleanupWarnings.push(`PR close ${row.pr_url}: ${detail}`);
|
|
740
|
+
console.warn(`[rail-pr-decision] gh pr close failed (continuing discard): ${detail}`);
|
|
455
741
|
}
|
|
456
742
|
}
|
|
457
743
|
catch (err) {
|
|
744
|
+
cleanupWarnings.push(`PR close ${row.pr_url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
458
745
|
console.warn('[rail-pr-decision] gh pr close threw (continuing discard):', err);
|
|
459
746
|
}
|
|
460
747
|
}
|
|
461
748
|
// 2. Remove the launch's worktrees (still on disk unless a restart already
|
|
462
749
|
// swept them) and close their ledger rows — the reconcile-style removal,
|
|
463
750
|
// branch deletion deferred to step 3 (a checked-out branch can't be -D'd).
|
|
464
|
-
const
|
|
751
|
+
const deleteOwnedBranches = !implementationFailed && !preserveBorrowedReview;
|
|
752
|
+
const branchSet = deleteOwnedBranches ? ownedDeliveryBranches(row, snap, cleanupWarnings) : new Set();
|
|
465
753
|
for (const wtId of snap.worktreeIds) {
|
|
466
754
|
const wt = (0, rail_worktrees_store_1.getRailWorktree)(deps.db, wtId);
|
|
467
755
|
if (!wt)
|
|
468
756
|
continue;
|
|
469
|
-
if (
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
757
|
+
if (wt.merge_state === 'needs-review' && opts.removeNeedsReview !== true)
|
|
758
|
+
continue;
|
|
759
|
+
if (wt.merge_state !== 'needs-review' && (0, rail_worktrees_store_1.isTerminalMergeState)(wt.merge_state))
|
|
760
|
+
continue;
|
|
761
|
+
try {
|
|
762
|
+
await (0, worktree_manager_1.removeWorktree)(deps.git, {
|
|
763
|
+
repoDir: deps.project.path, worktreePath: wt.worktree_path, branch: wt.branch, deleteBranch: false,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
catch (err) {
|
|
767
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
768
|
+
cleanupWarnings.push(`worktree ${wt.worktree_path}: ${detail}`);
|
|
769
|
+
console.warn(`[rail-pr-decision] discard could not remove worktree ${wt.worktree_path}: ${detail}`);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
474
772
|
if (!(0, rail_worktrees_store_1.isTerminalMergeState)(wt.merge_state))
|
|
475
773
|
(0, rail_worktrees_store_1.updateRailWorktreeState)(deps.db, wt.id, 'failed');
|
|
476
774
|
}
|
|
477
|
-
// 3. Delete
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (succeededUnits.length > 1) {
|
|
775
|
+
// 3. Delete only branches this delivery durably records as created: owned
|
|
776
|
+
// per-unit sources and, for a multi-unit fresh delivery, its exact
|
|
777
|
+
// assembled head. A preferred name is never recomputed here because it
|
|
778
|
+
// may belong to the user. Missing branches are tolerated; the integration
|
|
779
|
+
// branch is NEVER deleted.
|
|
780
|
+
if (deleteOwnedBranches) {
|
|
781
|
+
for (const branch of branchSet) {
|
|
782
|
+
if (!branch || branch === row.base_branch)
|
|
783
|
+
continue;
|
|
487
784
|
try {
|
|
488
|
-
|
|
785
|
+
const deleted = await deps.git.run(['branch', '-D', branch], deps.project.path);
|
|
786
|
+
if (deleted.code !== 0) {
|
|
787
|
+
cleanupWarnings.push(`branch ${branch}: ${(deleted.stderr.trim() || deleted.stdout.trim()).split('\n')[0] || `exit ${deleted.code}`}`);
|
|
788
|
+
}
|
|
489
789
|
}
|
|
490
|
-
catch {
|
|
491
|
-
|
|
790
|
+
catch (err) {
|
|
791
|
+
cleanupWarnings.push(`branch ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
492
792
|
}
|
|
493
793
|
}
|
|
494
|
-
|
|
495
|
-
|
|
794
|
+
}
|
|
795
|
+
const terminalPatch = {
|
|
796
|
+
deliveryOutcome: 'not_started',
|
|
797
|
+
statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : 'discarded',
|
|
798
|
+
cleanupWarnings,
|
|
799
|
+
};
|
|
800
|
+
const conflict = preserveBorrowedReview
|
|
801
|
+
? casTransition(deps, row, 'discarded', terminalPatch)
|
|
802
|
+
: casTransitionWithTicketEffect(deps, row, 'discarded', terminalPatch, {
|
|
803
|
+
deliveryId: row.id,
|
|
804
|
+
ticketIds: snap.ticketIds,
|
|
805
|
+
targetStatus: 'todo',
|
|
806
|
+
// The dashboard labels discard-from-no_changes as Refine. Jira must
|
|
807
|
+
// mirror that truthful backlog return and never apply discardStatus.
|
|
808
|
+
jiraAction: row.decision === 'no_changes' ? 'refine' : 'discard',
|
|
809
|
+
prUrl: null,
|
|
810
|
+
});
|
|
811
|
+
if (conflict)
|
|
812
|
+
return conflict;
|
|
813
|
+
if (!preserveBorrowedReview)
|
|
814
|
+
applyTerminalTicketEffect(deps, row, 'discarded', cleanupWarnings);
|
|
815
|
+
finalizeTransition(deps, row.id);
|
|
816
|
+
return {
|
|
817
|
+
status: 200,
|
|
818
|
+
body: {
|
|
819
|
+
ok: true,
|
|
820
|
+
decision: 'discarded',
|
|
821
|
+
...(cleanupWarnings.length > 0 ? { cleanupWarnings } : {}),
|
|
822
|
+
...(preserveBorrowedReview ? { preservedBorrowedReview: true } : {}),
|
|
823
|
+
},
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
async function runDismiss(deps, row) {
|
|
827
|
+
// Legality restricts dismiss to a clean/retryable continuation. Reuse the
|
|
828
|
+
// ownership-safe discard path; its external PR, head and tickets are borrowed.
|
|
829
|
+
return runDiscard(deps, row, { removeNeedsReview: false });
|
|
830
|
+
}
|
|
831
|
+
async function runAcknowledgeNoChanges(deps, row) {
|
|
832
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
833
|
+
const cleanupWarnings = await (0, rail_worktree_release_1.releaseRailWorktrees)({
|
|
834
|
+
db: deps.db,
|
|
835
|
+
git: deps.git,
|
|
836
|
+
repoDir: deps.project.path,
|
|
837
|
+
worktreeIds: snap.worktreeIds,
|
|
838
|
+
});
|
|
839
|
+
// A fresh no-change branch is disposable only while Git still proves that it
|
|
840
|
+
// has no commits ahead of the integration base. If the user added work after
|
|
841
|
+
// settlement, preserve it and disclose the incomplete cleanup.
|
|
842
|
+
for (const branch of ownedDeliveryBranches(row, snap, cleanupWarnings)) {
|
|
843
|
+
if (!branch || branch === row.base_branch)
|
|
844
|
+
continue;
|
|
845
|
+
try {
|
|
846
|
+
const ahead = await deps.git.run(['rev-list', '--count', `${row.base_branch}..${branch}`], deps.project.path);
|
|
847
|
+
if (ahead.code !== 0 || Number.parseInt(ahead.stdout.trim(), 10) !== 0) {
|
|
848
|
+
cleanupWarnings.push(`branch ${branch}: retained because no-change cleanup could not prove it has zero commits ahead of ${row.base_branch}`);
|
|
496
849
|
continue;
|
|
497
|
-
|
|
850
|
+
}
|
|
851
|
+
const deleted = await deps.git.run(['branch', '-d', branch], deps.project.path);
|
|
852
|
+
if (deleted.code !== 0) {
|
|
853
|
+
cleanupWarnings.push(`branch ${branch}: ${(deleted.stderr.trim() || deleted.stdout.trim()).split('\n')[0] || `exit ${deleted.code}`}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
catch (err) {
|
|
857
|
+
cleanupWarnings.push(`branch ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
498
858
|
}
|
|
499
859
|
}
|
|
500
|
-
const conflict =
|
|
860
|
+
const conflict = casTransitionWithTicketEffect(deps, row, 'completed', {
|
|
861
|
+
deliveryOutcome: 'no_changes',
|
|
862
|
+
statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : 'no_changes',
|
|
863
|
+
cleanupWarnings,
|
|
864
|
+
}, {
|
|
865
|
+
deliveryId: row.id,
|
|
866
|
+
ticketIds: snap.ticketIds,
|
|
867
|
+
targetStatus: 'done',
|
|
868
|
+
jiraAction: 'completed',
|
|
869
|
+
prUrl: null,
|
|
870
|
+
});
|
|
501
871
|
if (conflict)
|
|
502
872
|
return conflict;
|
|
873
|
+
applyTerminalTicketEffect(deps, row, 'completed', cleanupWarnings);
|
|
503
874
|
finalizeTransition(deps, row.id);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
return { status: 200, body: { ok: true, decision: 'discarded' } };
|
|
875
|
+
return {
|
|
876
|
+
status: 200,
|
|
877
|
+
body: {
|
|
878
|
+
ok: true,
|
|
879
|
+
decision: 'completed',
|
|
880
|
+
noChanges: true,
|
|
881
|
+
...(cleanupWarnings.length > 0 ? { cleanupWarnings } : {}),
|
|
882
|
+
},
|
|
883
|
+
};
|
|
514
884
|
}
|
|
515
885
|
async function runPollMerge(deps, row) {
|
|
516
|
-
const
|
|
517
|
-
if (
|
|
518
|
-
return
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
886
|
+
const observed = await (0, pr_lifecycle_1.observePrLifecycle)(deps.exec, deps.project.path, row.pr_url, row.delivery_sha);
|
|
887
|
+
if (!observed.ok)
|
|
888
|
+
return { status: 502, body: { error: 'gh_failed', detail: observed.detail } };
|
|
889
|
+
const { state, isDraft } = observed;
|
|
890
|
+
if (state === 'CLOSED') {
|
|
891
|
+
const conflict = casTransition(deps, row, 'pr_closed', {
|
|
892
|
+
deliveryOutcome: 'delivered', statusCode: 'pr_closed',
|
|
893
|
+
});
|
|
894
|
+
if (conflict)
|
|
895
|
+
return conflict;
|
|
896
|
+
finalizeTransition(deps, row.id);
|
|
897
|
+
return { status: 200, body: { ok: true, decision: 'pr_closed', merged: false, closed: true, prUrl: row.pr_url } };
|
|
522
898
|
}
|
|
523
|
-
|
|
524
|
-
|
|
899
|
+
if (state === 'OPEN') {
|
|
900
|
+
// A successful reopen can be observed even if the explicit action lost its
|
|
901
|
+
// follow-up `gh view` response. Polling heals pr_closed from remote truth.
|
|
902
|
+
if (row.decision === 'pr_closed') {
|
|
903
|
+
const next = isDraft ? 'pr_draft' : 'pr_ready';
|
|
904
|
+
const conflict = casTransition(deps, row, next, {
|
|
905
|
+
deliveryOutcome: 'delivered', statusCode: isDraft ? 'pr_draft_ready' : 'pr_ready',
|
|
906
|
+
});
|
|
907
|
+
if (conflict)
|
|
908
|
+
return conflict;
|
|
909
|
+
finalizeTransition(deps, row.id);
|
|
910
|
+
return { status: 200, body: { ok: true, decision: next, merged: false, reopened: true, prUrl: row.pr_url } };
|
|
911
|
+
}
|
|
912
|
+
return { status: 200, body: { ok: true, decision: row.decision, merged: false } };
|
|
525
913
|
}
|
|
526
914
|
if (state !== 'MERGED') {
|
|
527
|
-
|
|
528
|
-
return { status: 200, body: { ok: true, decision: row.decision, merged: false } };
|
|
915
|
+
return { status: 502, body: { error: 'gh_failed', detail: `unexpected PR state: ${state}` } };
|
|
529
916
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (succeededUnits.length > 1) {
|
|
536
|
-
try {
|
|
537
|
-
branchSet.add((0, pr_naming_1.batchBranchNameFor)(loadPrTicketData(deps, succeededUnits.map((b) => b.ticketId))));
|
|
538
|
-
}
|
|
539
|
-
catch {
|
|
540
|
-
/* best-effort cleanup only */
|
|
541
|
-
}
|
|
917
|
+
// A PR can merge just before a continuation push; GitHub may then accept a
|
|
918
|
+
// later update to the head branch even though that object was never part of
|
|
919
|
+
// the merge. Never move tickets to Done from state=MERGED alone.
|
|
920
|
+
if (!row.delivery_sha) {
|
|
921
|
+
return persistMovedPrHead(deps, row, 'the merged PR has no persisted verified delivery SHA; refusing to infer delivery from a mutable branch');
|
|
542
922
|
}
|
|
543
|
-
|
|
923
|
+
if (observed.includesExpectedSha !== true) {
|
|
924
|
+
return rerouteFromStalePr(deps, row, mergedEvidenceDetail(observed), true);
|
|
925
|
+
}
|
|
926
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
927
|
+
const cleanupWarnings = await (0, rail_worktree_release_1.releaseRailWorktrees)({
|
|
928
|
+
db: deps.db, git: deps.git, repoDir: deps.project.path,
|
|
929
|
+
worktreeIds: releasableWorktreeIds(deps, snap), state: 'merged',
|
|
930
|
+
});
|
|
931
|
+
const branchSet = ownedDeliveryBranches(row, snap, cleanupWarnings);
|
|
544
932
|
for (const branch of branchSet) {
|
|
545
933
|
if (!branch || branch === row.base_branch)
|
|
546
934
|
continue;
|
|
547
|
-
|
|
935
|
+
try {
|
|
936
|
+
const deleted = await deps.git.run(['branch', '-D', branch], deps.project.path);
|
|
937
|
+
if (deleted.code !== 0) {
|
|
938
|
+
cleanupWarnings.push(`branch ${branch}: ${(deleted.stderr.trim() || deleted.stdout.trim()).split('\n')[0] || `exit ${deleted.code}`}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
catch (err) {
|
|
942
|
+
cleanupWarnings.push(`branch ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
943
|
+
}
|
|
548
944
|
}
|
|
549
|
-
const conflict =
|
|
945
|
+
const conflict = casTransitionWithTicketEffect(deps, row, 'merged', {
|
|
946
|
+
deliveryOutcome: 'delivered',
|
|
947
|
+
statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : 'merged',
|
|
948
|
+
cleanupWarnings,
|
|
949
|
+
}, {
|
|
950
|
+
deliveryId: row.id,
|
|
951
|
+
ticketIds: snap.ticketIds,
|
|
952
|
+
targetStatus: 'done',
|
|
953
|
+
jiraAction: 'merged',
|
|
954
|
+
prUrl: row.pr_url,
|
|
955
|
+
});
|
|
550
956
|
if (conflict)
|
|
551
957
|
return conflict;
|
|
958
|
+
applyTerminalTicketEffect(deps, row, 'merged', cleanupWarnings);
|
|
552
959
|
finalizeTransition(deps, row.id);
|
|
553
|
-
|
|
960
|
+
return { status: 200, body: { ok: true, decision: 'merged', merged: true, prUrl: row.pr_url } };
|
|
961
|
+
}
|
|
962
|
+
async function runReopen(deps, row) {
|
|
963
|
+
let reopened;
|
|
554
964
|
try {
|
|
555
|
-
deps.
|
|
965
|
+
reopened = await deps.exec.run('gh', ['pr', 'reopen', row.pr_url], deps.project.path);
|
|
556
966
|
}
|
|
557
967
|
catch (err) {
|
|
558
|
-
|
|
968
|
+
reopened = { code: 1, stdout: '', stderr: err instanceof Error ? err.message : String(err) };
|
|
559
969
|
}
|
|
560
|
-
|
|
970
|
+
const lifecycle = await observePrLifecycle(deps, row.pr_url);
|
|
971
|
+
if (lifecycle?.state !== 'OPEN') {
|
|
972
|
+
if (reopened.code !== 0)
|
|
973
|
+
return ghFailed(reopened);
|
|
974
|
+
return { status: 502, body: { error: 'gh_failed', detail: `PR did not reopen (state ${lifecycle?.state ?? 'unknown'})` } };
|
|
975
|
+
}
|
|
976
|
+
const next = lifecycle.isDraft ? 'pr_draft' : 'pr_ready';
|
|
977
|
+
const conflict = casTransition(deps, row, next, {
|
|
978
|
+
deliveryOutcome: 'delivered', statusCode: lifecycle.isDraft ? 'pr_draft_ready' : 'pr_ready',
|
|
979
|
+
});
|
|
980
|
+
if (conflict)
|
|
981
|
+
return conflict;
|
|
982
|
+
finalizeTransition(deps, row.id);
|
|
983
|
+
return { status: 200, body: { ok: true, decision: next, reopened: true, prUrl: row.pr_url } };
|
|
561
984
|
}
|
|
562
|
-
/**
|
|
563
|
-
* Remote-less acceptance: merge the delivery's branches into the integration
|
|
564
|
-
* branch DIRECTLY in the user's checkout. This is the one decision that touches
|
|
565
|
-
* the user's working tree, so it is triple-guarded and transactional-ish:
|
|
566
|
-
*
|
|
567
|
-
* - The checkout must have the integration branch checked out (a branch can
|
|
568
|
-
* only be checked out in one worktree — merging via a temp worktree is
|
|
569
|
-
* impossible while the user holds it) → 409 `merge_local_blocked` otherwise.
|
|
570
|
-
* - The working tree must be CLEAN → 409 `merge_local_blocked` otherwise.
|
|
571
|
-
* - Merge = the assembled head when one exists (row.branch), else each
|
|
572
|
-
* succeeded unit branch sequentially, `--no-ff --no-edit`. Any conflict →
|
|
573
|
-
* `merge --abort` + 502 `merge_failed`, NO transition (retry or discard).
|
|
574
|
-
*
|
|
575
|
-
* Success mirrors poll-merge's MERGED arm: decision → merged (prUrl stays
|
|
576
|
-
* null), worktrees/branches swept, tickets → done, Jira onRailMerged(null).
|
|
577
|
-
*/
|
|
578
985
|
async function runMergeLocal(deps, row) {
|
|
986
|
+
return (0, repo_lock_1.withRepoLock)(deps.project.path, () => runMergeLocalLocked(deps, row));
|
|
987
|
+
}
|
|
988
|
+
async function runMergeLocalLocked(deps, row) {
|
|
989
|
+
deps.assertAdmission?.();
|
|
579
990
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
580
|
-
|
|
991
|
+
const cleanupWarnings = [];
|
|
992
|
+
// Snapshot the checkout the user authorized. The same three invariants are
|
|
993
|
+
// revalidated immediately before the one final fast-forward.
|
|
581
994
|
const head = await deps.git.run(['rev-parse', '--abbrev-ref', 'HEAD'], deps.project.path);
|
|
582
995
|
const currentBranch = head.code === 0 ? head.stdout.trim() : '';
|
|
583
996
|
if (currentBranch !== row.base_branch) {
|
|
@@ -590,72 +1003,186 @@ async function runMergeLocal(deps, row) {
|
|
|
590
1003
|
if (status.code !== 0 || status.stdout.trim() !== '') {
|
|
591
1004
|
return { status: 409, body: { error: 'merge_local_blocked', reason: 'dirty', base: row.base_branch } };
|
|
592
1005
|
}
|
|
593
|
-
|
|
594
|
-
|
|
1006
|
+
const startingHead = await deps.git.run(['rev-parse', '--verify', 'HEAD'], deps.project.path);
|
|
1007
|
+
const startingSha = startingHead.code === 0 ? startingHead.stdout.trim() : '';
|
|
1008
|
+
if (!/^[0-9a-f]{40,64}$/i.test(startingSha)) {
|
|
1009
|
+
return { status: 409, body: { error: 'merge_local_blocked', reason: 'unresolved_head', base: row.base_branch } };
|
|
1010
|
+
}
|
|
1011
|
+
// What to merge: immutable object ids only. The assembled head carries every
|
|
1012
|
+
// unit already; without one, merge each settled unit's exact final SHA.
|
|
595
1013
|
let toMerge;
|
|
596
|
-
|
|
597
|
-
|
|
1014
|
+
let deliveryHeadSha = null;
|
|
1015
|
+
if (row.delivery_sha && await commitObjectExists(deps, row.delivery_sha)) {
|
|
1016
|
+
deliveryHeadSha = row.delivery_sha;
|
|
1017
|
+
}
|
|
1018
|
+
else if (row.branch) {
|
|
1019
|
+
deliveryHeadSha = await captureBranchSha(deps, row.branch);
|
|
1020
|
+
}
|
|
1021
|
+
if (deliveryHeadSha) {
|
|
1022
|
+
toMerge = [deliveryHeadSha];
|
|
598
1023
|
}
|
|
599
1024
|
else {
|
|
600
1025
|
toMerge = [];
|
|
601
1026
|
for (const unit of snap.branches.filter((b) => b.succeeded)) {
|
|
602
|
-
let
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
|
|
1027
|
+
let sourceSha = unit.finalSha && COMMIT_SHA_RE.test(unit.finalSha) ? unit.finalSha : null;
|
|
1028
|
+
if (sourceSha) {
|
|
1029
|
+
if (!(await commitObjectExists(deps, sourceSha)))
|
|
1030
|
+
sourceSha = null;
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
sourceSha = await captureBranchSha(deps, unit.branch);
|
|
1034
|
+
}
|
|
1035
|
+
if (!sourceSha) {
|
|
606
1036
|
return {
|
|
607
1037
|
status: 502,
|
|
608
|
-
body: { error: 'merge_failed', detail: `
|
|
1038
|
+
body: { error: 'merge_failed', detail: `verified commit for '${unit.branch}' (ticket #${unit.ticketId}) is unavailable` },
|
|
609
1039
|
};
|
|
610
1040
|
}
|
|
611
|
-
toMerge.push(
|
|
1041
|
+
toMerge.push(sourceSha);
|
|
612
1042
|
}
|
|
613
1043
|
}
|
|
614
1044
|
if (toMerge.length === 0) {
|
|
615
1045
|
return { status: 502, body: { error: 'merge_failed', detail: 'no succeeded branch to merge' } };
|
|
616
1046
|
}
|
|
1047
|
+
// Assemble every merge away from the user's checkout. A conflict can only
|
|
1048
|
+
// dirty this disposable detached worktree, never the integration branch.
|
|
1049
|
+
const safeId = row.id.replace(/[^A-Za-z0-9-]/g, '').slice(0, 64) || 'delivery';
|
|
1050
|
+
const assemblyPath = path.join(deps.assemblyRoot ?? batchWorktreeRoot(deps.project.slug), `local-merge-${safeId}-${(0, ids_1.newId)()}`);
|
|
1051
|
+
const added = await deps.git.run(['worktree', 'add', '--detach', assemblyPath, startingSha], deps.project.path);
|
|
1052
|
+
if (added.code !== 0) {
|
|
1053
|
+
const detail = (added.stderr.trim() || added.stdout.trim()).split('\n')[0] || `exit ${added.code}`;
|
|
1054
|
+
return { status: 502, body: { error: 'merge_failed', detail: `creating isolated assembly: ${detail}` } };
|
|
1055
|
+
}
|
|
1056
|
+
const removeAssembly = async () => {
|
|
1057
|
+
try {
|
|
1058
|
+
const removed = await deps.git.run(['worktree', 'remove', '--force', assemblyPath], deps.project.path);
|
|
1059
|
+
if (removed.code !== 0) {
|
|
1060
|
+
cleanupWarnings.push(`assembly worktree ${assemblyPath}: ${(removed.stderr.trim() || removed.stdout.trim()).split('\n')[0] || `exit ${removed.code}`}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
catch (err) {
|
|
1064
|
+
cleanupWarnings.push(`assembly worktree ${assemblyPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
const persistAssemblyFailure = (detail) => {
|
|
1068
|
+
const conflict = casTransition(deps, row, row.decision, {
|
|
1069
|
+
deliveryOutcome: 'blocked', statusCode: 'delivery_failed', statusDetail: detail, cleanupWarnings,
|
|
1070
|
+
});
|
|
1071
|
+
if (conflict)
|
|
1072
|
+
return conflict;
|
|
1073
|
+
finalizeTransition(deps, row.id);
|
|
1074
|
+
return null;
|
|
1075
|
+
};
|
|
617
1076
|
for (const branch of toMerge) {
|
|
618
|
-
const r = await deps.git.run(['merge', '--no-ff', '--no-edit', branch],
|
|
1077
|
+
const r = await deps.git.run(['merge', '--no-ff', '--no-edit', branch], assemblyPath);
|
|
619
1078
|
if (r.code !== 0) {
|
|
620
|
-
|
|
1079
|
+
try {
|
|
1080
|
+
const aborted = await deps.git.run(['merge', '--abort'], assemblyPath);
|
|
1081
|
+
if (aborted.code !== 0)
|
|
1082
|
+
cleanupWarnings.push(`assembly abort ${branch}: exit ${aborted.code}`);
|
|
1083
|
+
}
|
|
1084
|
+
catch (err) {
|
|
1085
|
+
cleanupWarnings.push(`assembly abort ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1086
|
+
}
|
|
1087
|
+
await removeAssembly();
|
|
621
1088
|
const detail = (r.stderr.trim() || r.stdout.trim()).split('\n')[0] || `exit ${r.code}`;
|
|
1089
|
+
const conflict = persistAssemblyFailure(`merging '${branch}': ${detail}`);
|
|
1090
|
+
if (conflict)
|
|
1091
|
+
return conflict;
|
|
622
1092
|
return { status: 502, body: { error: 'merge_failed', detail: `merging '${branch}': ${detail}` } };
|
|
623
1093
|
}
|
|
624
1094
|
}
|
|
625
|
-
|
|
1095
|
+
const assembled = await deps.git.run(['rev-parse', '--verify', 'HEAD'], assemblyPath);
|
|
1096
|
+
const assembledSha = assembled.code === 0 ? assembled.stdout.trim() : '';
|
|
1097
|
+
if (!/^[0-9a-f]{40,64}$/i.test(assembledSha)) {
|
|
1098
|
+
await removeAssembly();
|
|
1099
|
+
const detail = 'isolated assembly produced no verifiable HEAD';
|
|
1100
|
+
const conflict = persistAssemblyFailure(detail);
|
|
1101
|
+
if (conflict)
|
|
1102
|
+
return conflict;
|
|
1103
|
+
return { status: 502, body: { error: 'merge_failed', detail } };
|
|
1104
|
+
}
|
|
1105
|
+
const [finalBranch, finalStatus, finalHead] = await Promise.all([
|
|
1106
|
+
deps.git.run(['rev-parse', '--abbrev-ref', 'HEAD'], deps.project.path),
|
|
1107
|
+
deps.git.run(['status', '--porcelain'], deps.project.path),
|
|
1108
|
+
deps.git.run(['rev-parse', '--verify', 'HEAD'], deps.project.path),
|
|
1109
|
+
]);
|
|
1110
|
+
const changedReason = finalBranch.code !== 0 || finalBranch.stdout.trim() !== row.base_branch
|
|
1111
|
+
? 'wrong_branch'
|
|
1112
|
+
: finalStatus.code !== 0 || finalStatus.stdout.trim() !== ''
|
|
1113
|
+
? 'dirty'
|
|
1114
|
+
: finalHead.code !== 0 || finalHead.stdout.trim() !== startingSha
|
|
1115
|
+
? 'head_changed'
|
|
1116
|
+
: null;
|
|
1117
|
+
if (changedReason) {
|
|
1118
|
+
await removeAssembly();
|
|
1119
|
+
if (cleanupWarnings.length > 0) {
|
|
1120
|
+
const conflict = casTransition(deps, row, row.decision, { cleanupWarnings });
|
|
1121
|
+
if (conflict)
|
|
1122
|
+
return conflict;
|
|
1123
|
+
finalizeTransition(deps, row.id);
|
|
1124
|
+
}
|
|
1125
|
+
return { status: 409, body: { error: 'merge_local_blocked', reason: changedReason, base: row.base_branch } };
|
|
1126
|
+
}
|
|
1127
|
+
const advanced = await deps.git.run(['merge', '--ff-only', assembledSha], deps.project.path);
|
|
1128
|
+
if (advanced.code !== 0) {
|
|
1129
|
+
await removeAssembly();
|
|
1130
|
+
const detail = (advanced.stderr.trim() || advanced.stdout.trim()).split('\n')[0] || `exit ${advanced.code}`;
|
|
1131
|
+
const conflict = persistAssemblyFailure(`advancing '${row.base_branch}': ${detail}`);
|
|
1132
|
+
if (conflict)
|
|
1133
|
+
return conflict;
|
|
1134
|
+
return { status: 502, body: { error: 'merge_failed', detail: `advancing '${row.base_branch}': ${detail}` } };
|
|
1135
|
+
}
|
|
1136
|
+
await removeAssembly();
|
|
1137
|
+
// Post-advance sweep: the work now lives on
|
|
626
1138
|
// the integration branch, so the launch's worktrees + branches are spent.
|
|
627
|
-
const branchSet =
|
|
628
|
-
for (const
|
|
629
|
-
branchSet.add(b.branch);
|
|
630
|
-
if (row.branch)
|
|
631
|
-
branchSet.add(row.branch);
|
|
632
|
-
for (const wtId of snap.worktreeIds) {
|
|
1139
|
+
const branchSet = ownedDeliveryBranches(row, snap, cleanupWarnings);
|
|
1140
|
+
for (const wtId of releasableWorktreeIds(deps, snap)) {
|
|
633
1141
|
const wt = (0, rail_worktrees_store_1.getRailWorktree)(deps.db, wtId);
|
|
634
1142
|
if (!wt)
|
|
635
1143
|
continue;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
1144
|
+
try {
|
|
1145
|
+
await (0, worktree_manager_1.removeWorktree)(deps.git, {
|
|
1146
|
+
repoDir: deps.project.path, worktreePath: wt.worktree_path, branch: wt.branch, deleteBranch: false,
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
catch (err) {
|
|
1150
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1151
|
+
cleanupWarnings.push(`worktree ${wt.worktree_path}: ${detail}`);
|
|
1152
|
+
console.warn(`[rail-pr-decision] merge-local could not remove worktree ${wt.worktree_path}: ${detail}`);
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
640
1155
|
if (!(0, rail_worktrees_store_1.isTerminalMergeState)(wt.merge_state))
|
|
641
1156
|
(0, rail_worktrees_store_1.updateRailWorktreeState)(deps.db, wt.id, 'merged');
|
|
642
1157
|
}
|
|
643
1158
|
for (const branch of branchSet) {
|
|
644
1159
|
if (!branch || branch === row.base_branch)
|
|
645
1160
|
continue;
|
|
646
|
-
|
|
1161
|
+
try {
|
|
1162
|
+
const deleted = await deps.git.run(['branch', '-D', branch], deps.project.path);
|
|
1163
|
+
if (deleted.code !== 0) {
|
|
1164
|
+
cleanupWarnings.push(`branch ${branch}: ${(deleted.stderr.trim() || deleted.stdout.trim()).split('\n')[0] || `exit ${deleted.code}`}`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
catch (err) {
|
|
1168
|
+
cleanupWarnings.push(`branch ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1169
|
+
}
|
|
647
1170
|
}
|
|
648
|
-
const conflict =
|
|
1171
|
+
const conflict = casTransitionWithTicketEffect(deps, row, 'merged', {
|
|
1172
|
+
deliveryOutcome: 'delivered',
|
|
1173
|
+
statusCode: cleanupWarnings.length > 0 ? 'cleanup_incomplete' : 'merged',
|
|
1174
|
+
cleanupWarnings,
|
|
1175
|
+
}, {
|
|
1176
|
+
deliveryId: row.id,
|
|
1177
|
+
ticketIds: snap.ticketIds,
|
|
1178
|
+
targetStatus: 'done',
|
|
1179
|
+
jiraAction: 'merged',
|
|
1180
|
+
prUrl: null,
|
|
1181
|
+
});
|
|
649
1182
|
if (conflict)
|
|
650
1183
|
return conflict;
|
|
1184
|
+
applyTerminalTicketEffect(deps, row, 'merged', cleanupWarnings);
|
|
651
1185
|
finalizeTransition(deps, row.id);
|
|
652
|
-
const changed = applyTicketTransition(deps, snap.ticketIds, 'done');
|
|
653
|
-
try {
|
|
654
|
-
deps.jiraSyncManager?.onRailMerged(changed, row.id, null);
|
|
655
|
-
}
|
|
656
|
-
catch (err) {
|
|
657
|
-
console.error('[rail-pr-decision] jira onRailMerged (local) failed:', err);
|
|
658
|
-
}
|
|
659
1186
|
return { status: 200, body: { ok: true, decision: 'merged', merged: true, local: true } };
|
|
660
1187
|
}
|
|
661
1188
|
function resolveTicketFile(deps) {
|
|
@@ -664,44 +1191,3 @@ function resolveTicketFile(deps) {
|
|
|
664
1191
|
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: deps.project.slug, path: deps.project.path });
|
|
665
1192
|
return exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(deps.project.path);
|
|
666
1193
|
}
|
|
667
|
-
/**
|
|
668
|
-
* Move the launch's on_review tickets to `todo` (discard) or `done` (merged),
|
|
669
|
-
* broadcasting ticket_updated per changed spec — mirroring onLoopRunFinished's
|
|
670
|
-
* revert style in project-registry. ONLY tickets still parked at on_review move
|
|
671
|
-
* (a manually re-triaged spec is respected). Best-effort: a ticket-store
|
|
672
|
-
* failure is logged, never fatal (the decision row already transitioned).
|
|
673
|
-
*/
|
|
674
|
-
function applyTicketTransition(deps, ticketIds, to) {
|
|
675
|
-
const changed = [];
|
|
676
|
-
if (ticketIds.length === 0)
|
|
677
|
-
return changed;
|
|
678
|
-
try {
|
|
679
|
-
const ticketFile = resolveTicketFile(deps);
|
|
680
|
-
const now = new Date().toISOString();
|
|
681
|
-
const store = (0, ticket_store_1.mutateStore)(ticketFile, (s) => {
|
|
682
|
-
for (const tid of ticketIds) {
|
|
683
|
-
const ticket = s.tickets[String(tid)];
|
|
684
|
-
if (!ticket || ticket.status !== 'on_review')
|
|
685
|
-
continue;
|
|
686
|
-
ticket.status = to;
|
|
687
|
-
ticket.updated_at = now;
|
|
688
|
-
changed.push(tid);
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
for (const tid of changed) {
|
|
692
|
-
const ticket = store.tickets[String(tid)];
|
|
693
|
-
if (!ticket)
|
|
694
|
-
continue;
|
|
695
|
-
deps.broadcast({
|
|
696
|
-
type: 'ticket_updated',
|
|
697
|
-
ticket: ticket,
|
|
698
|
-
projectId: deps.project.id,
|
|
699
|
-
timestamp: ticket.updated_at,
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
catch (err) {
|
|
704
|
-
console.error('[rail-pr-decision] failed to apply ticket transition:', err);
|
|
705
|
-
}
|
|
706
|
-
return changed;
|
|
707
|
-
}
|