specrails-desktop 2.26.1 → 2.27.0
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-C1LftFSE.js → ActivityFeedPage-BlkNVXCv.js} +1 -1
- package/client/dist/assets/AgentBrowserCapture-76Ri2QR1.js +1 -0
- package/client/dist/assets/AgentIntegrationsModal-D9hVgaBR.js +1 -0
- package/client/dist/assets/{AgentModeCodePane-b6UmdjwB.js → AgentModeCodePane-BUR_QxqR.js} +2 -2
- package/client/dist/assets/AgentModeJobsPane-CnRXelns.js +1 -0
- package/client/dist/assets/AgentsPage-DEt9vpzh.js +86 -0
- package/client/dist/assets/{AnalyticsPage-DjFf1OxU.js → AnalyticsPage-C_eT3I3i.js} +1 -1
- package/client/dist/assets/{BarChart-nmDbbUiR.js → BarChart-DuzYxiJ6.js} +1 -1
- package/client/dist/assets/CodePage-72-FLBNw.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-ee0bpk-F.js → DesktopAnalyticsPage-BSeBkF2n.js} +1 -1
- package/client/dist/assets/{DocsDialog-CGUzB1zX.js → DocsDialog-CGzqDyN3.js} +2 -2
- package/client/dist/assets/DocsPage-BB3VGFM9.js +11 -0
- package/client/dist/assets/{ExportDropdown-Dzk2Lkxw.js → ExportDropdown-IehskH-7.js} +1 -1
- package/client/dist/assets/IntegrationsPage-X8bLyuDk.js +3 -0
- package/client/dist/assets/InteractiveJobComposer-D1HUALME.js +19 -0
- package/client/dist/assets/JobDetailModal-CMg6aKSO.js +1 -0
- package/client/dist/assets/JobDetailPage-Bg01rzeu.js +1 -0
- package/client/dist/assets/JobsPage-Cwkm9yYc.js +1 -0
- package/client/dist/assets/{LoopBuilderPage-5ucN9Hq9.js → LoopBuilderPage-4uPNN8Xn.js} +2 -2
- package/client/dist/assets/{LoopPreviewModal-wKAts-Wf.js → LoopPreviewModal-BNUQ9iL8.js} +1 -1
- package/client/dist/assets/LoopsPage-DLXESZxq.js +1 -0
- package/client/dist/assets/ProjectSettingsDialog-DP6hAug-.js +1 -0
- package/client/dist/assets/{TemplatePreviewModal-_Dy6ceXg.js → TemplatePreviewModal-C6mndL8D.js} +1 -1
- package/client/dist/assets/{agent-gFi-qvxO2.js → agent-B0Rg_eLw2.js} +1 -1
- package/client/dist/assets/{agent-BmH52J4j.js → agent-B8f79myL.js} +1 -1
- package/client/dist/assets/{agent-DolxzTol.js → agent-BCH7a3z-.js} +1 -1
- package/client/dist/assets/{agent-Cg49-TGZ.js → agent-BELAd4RF.js} +1 -1
- package/client/dist/assets/{agent-DwDR2Exf2.js → agent-BrCnHlvX2.js} +1 -1
- package/client/dist/assets/{agent-DgDtg-0Y2.js → agent-CrcIj6WE2.js} +1 -1
- package/client/dist/assets/{agent-_tfCgFUk.js → agent-CtwLUmjl.js} +1 -1
- package/client/dist/assets/{agent-Bxb9RRNW.js → agent-xG4qLg4R.js} +1 -1
- package/client/dist/assets/{brain-BJ2Gd98j.js → brain-Dia32foa.js} +1 -1
- package/client/dist/assets/dashboard-5C2FOZQX.js +1 -0
- package/client/dist/assets/dashboard-B6iQ-IL_.js +1 -0
- package/client/dist/assets/dashboard-BFfnCtq52.js +1 -0
- package/client/dist/assets/dashboard-BIC8KtRf2.js +1 -0
- package/client/dist/assets/dashboard-BawQCyrV.js +1 -0
- package/client/dist/assets/dashboard-CEgg9NAr.js +1 -0
- package/client/dist/assets/dashboard-CiOyDh9L.js +1 -0
- package/client/dist/assets/dashboard-D6x5OnNj2.js +1 -0
- package/client/dist/assets/{dist-js-D7smLA35.js → dist-js-DN7Pxudf.js} +1 -1
- package/client/dist/assets/{dist-js-CXAsO0Z8.js → dist-js-D_4k5prK.js} +1 -1
- package/client/dist/assets/{index-B3ejvKSt.js → index-BbVOi-dY.js} +57 -55
- package/client/dist/assets/{index-C695aPdV.css → index-o3Q2Lii5.css} +1 -1
- package/client/dist/assets/{lib-BIHmUkcn.js → lib-BxSZPd0W.js} +1 -1
- package/client/dist/assets/settings-BFv6UbKV.js +1 -0
- package/client/dist/assets/{settings-BHdPchJP.js → settings-B_v3zgJf.js} +1 -1
- package/client/dist/assets/{settings-BeCczBFS2.js → settings-BemR_e3F.js} +1 -1
- package/client/dist/assets/{settings-D54_cgl12.js → settings-Bp24JHlv2.js} +1 -1
- package/client/dist/assets/{settings-C_nY14Yz.js → settings-BvIwQ3A1.js} +1 -1
- package/client/dist/assets/{settings-BlLi719f.js → settings-Bx2ObkTR2.js} +1 -1
- package/client/dist/assets/{settings-CaZ26a0S.js → settings-C_T0-j9d.js} +1 -1
- package/client/dist/assets/{settings-cn0tbve62.js → settings-DhaFKpcw2.js} +1 -1
- package/client/dist/assets/{upload-xdQuJvXN.js → upload-4u055Eea.js} +1 -1
- package/client/dist/assets/{value-Bk2f9AUg.js → value-C3Qe3VXm.js} +1 -1
- package/client/dist/index.html +5 -5
- package/package.json +1 -1
- package/server/dist/agent-operator-prompt.js +10 -1
- package/server/dist/db.js +51 -2
- package/server/dist/dev-ports.js +35 -0
- package/server/dist/index.js +2 -6
- package/server/dist/mcp/tools/catalog.js +2 -0
- package/server/dist/mcp/tools/env.js +72 -0
- package/server/dist/mcp/tools/jobs.js +66 -1
- package/server/dist/mcp/tools/rails.js +2 -0
- package/server/dist/path-resolver.js +74 -15
- package/server/dist/pr-publisher.js +17 -15
- package/server/dist/project-env.js +30 -0
- package/server/dist/project-git.js +57 -0
- package/server/dist/project-registry.js +4 -2
- package/server/dist/project-router-jobs.js +76 -0
- package/server/dist/project-router-settings.js +10 -1
- package/server/dist/queue-manager.js +9 -1
- package/server/dist/rail-isolated-launch.js +27 -6
- package/server/dist/rail-pr-decision.js +96 -18
- package/server/dist/rail-pr-store.js +36 -0
- package/server/dist/rail-worktree-release.js +25 -0
- package/server/dist/rail-worktrees-store.js +2 -2
- package/server/dist/rails-router.js +95 -9
- package/server/dist/transient-children.js +126 -8
- package/server/dist/workspace-resolution.js +3 -3
- package/server/dist/worktree-env-discovery.js +172 -0
- package/client/dist/assets/AgentBrowserCapture-B5prW-V0.js +0 -1
- package/client/dist/assets/AgentModeJobsPane-DW3Wj5oi.js +0 -1
- package/client/dist/assets/AgentsPage-DBMnhdlq.js +0 -86
- package/client/dist/assets/CodePage-DHd46Sm7.js +0 -2
- package/client/dist/assets/DocsPage-96iws84I.js +0 -11
- package/client/dist/assets/IntegrationsPage-Bta2lAhq.js +0 -3
- package/client/dist/assets/InteractiveJobComposer-B9S_ZGJS.js +0 -19
- package/client/dist/assets/JobDetailModal-DhrwU2z0.js +0 -1
- package/client/dist/assets/JobDetailPage-BnhoXkql.js +0 -1
- package/client/dist/assets/JobsPage-BBT9fyhg.js +0 -1
- package/client/dist/assets/LoopsPage-COyxf6Pa.js +0 -1
- package/client/dist/assets/ProjectSettingsDialog-CcToBI-Z.js +0 -1
- package/client/dist/assets/dashboard-90r0QcUc2.js +0 -1
- package/client/dist/assets/dashboard-B0C6mJP-.js +0 -1
- package/client/dist/assets/dashboard-BOx0FR1L2.js +0 -1
- package/client/dist/assets/dashboard-BcjUOLJr.js +0 -1
- package/client/dist/assets/dashboard-C27kNpjn.js +0 -1
- package/client/dist/assets/dashboard-DJAj2EbH.js +0 -1
- package/client/dist/assets/dashboard-DNd1Gobs.js +0 -1
- package/client/dist/assets/dashboard-DZWrDAYm2.js +0 -1
- package/client/dist/assets/settings-D9bDDe2O.js +0 -1
- /package/client/dist/assets/{activity-BZNfOdiS.js → activity-BZZlEG3a.js} +0 -0
- /package/client/dist/assets/{activity--iN4Ad2R2.js → activity-BbWiDfn-2.js} +0 -0
- /package/client/dist/assets/{activity-CIOLxGvh.js → activity-D3Ic-HTS.js} +0 -0
- /package/client/dist/assets/{activity-DtxrAUMo2.js → activity-DFbvNqhE2.js} +0 -0
- /package/client/dist/assets/{activity-BuVo5sPB.js → activity-DtzRy92x.js} +0 -0
- /package/client/dist/assets/{activity-6hM_ZSg9.js → activity-DvECTYGt.js} +0 -0
- /package/client/dist/assets/{activity-D2r2h3SE2.js → activity-FleOlK0l2.js} +0 -0
- /package/client/dist/assets/{addspec-D-JLDsUV.js → addspec-BKrH2Ucz.js} +0 -0
- /package/client/dist/assets/{addspec-DfhDbg5c.js → addspec-B_LQMHW3.js} +0 -0
- /package/client/dist/assets/{addspec-bsNDdqRm2.js → addspec-Bw_YQwo62.js} +0 -0
- /package/client/dist/assets/{addspec-BMtLZLW62.js → addspec-CFxGvmJR2.js} +0 -0
- /package/client/dist/assets/{addspec-mgPOt5q02.js → addspec-CR8MB5Fr2.js} +0 -0
- /package/client/dist/assets/{addspec-CP0HRuxY.js → addspec-DaA-CbTt.js} +0 -0
- /package/client/dist/assets/{addspec-CRuvWn8d.js → addspec-NBbcTbLc.js} +0 -0
- /package/client/dist/assets/{agents-BpoUaoW92.js → agents-B9pFSJQI2.js} +0 -0
- /package/client/dist/assets/{agents-78bTZGZh.js → agents-BHgn--3j.js} +0 -0
- /package/client/dist/assets/{agents-BUdEi3-N.js → agents-B_qGaB0F.js} +0 -0
- /package/client/dist/assets/{agents-bvyRLczL.js → agents-CP8_ezYG.js} +0 -0
- /package/client/dist/assets/{agents-BLhWCmDS2.js → agents-CV-_xBvX2.js} +0 -0
- /package/client/dist/assets/{agents-CWo-KyAB.js → agents-HFCqQH0Z.js} +0 -0
- /package/client/dist/assets/{agents-DDtCJ5M02.js → agents-VsyFhGOS2.js} +0 -0
- /package/client/dist/assets/{agentstudio-BMQxc_bz.js → agentstudio-BEp7Sb46.js} +0 -0
- /package/client/dist/assets/{agentstudio-Dtq3dGYk.js → agentstudio-BOjG6tgJ.js} +0 -0
- /package/client/dist/assets/{agentstudio-C_Jq8Tl4.js → agentstudio-Br3WD7Xs.js} +0 -0
- /package/client/dist/assets/{agentstudio-sI27TpBu2.js → agentstudio-CuTt3ZGe2.js} +0 -0
- /package/client/dist/assets/{agentstudio-CWgXG5W52.js → agentstudio-D2A5peWV2.js} +0 -0
- /package/client/dist/assets/{agentstudio-COZriimI2.js → agentstudio-RfZavULt2.js} +0 -0
- /package/client/dist/assets/{agentstudio-jKMTdT70.js → agentstudio-RsxnZtUA.js} +0 -0
- /package/client/dist/assets/{aiedit-igmw-ZoZ2.js → aiedit-26p5A_HY2.js} +0 -0
- /package/client/dist/assets/{aiedit-6CHdb_iv2.js → aiedit-CV520D4W2.js} +0 -0
- /package/client/dist/assets/{aiedit-pc5JDUq1.js → aiedit-CcugGPbS.js} +0 -0
- /package/client/dist/assets/{aiedit-eMN4eQPF.js → aiedit-D0Syl-mC.js} +0 -0
- /package/client/dist/assets/{aiedit-hknZlly82.js → aiedit-DnBzjlVz2.js} +0 -0
- /package/client/dist/assets/{aiedit-B03pD_Na.js → aiedit-DrIjSojo.js} +0 -0
- /package/client/dist/assets/{aiedit-BtRqRxML.js → aiedit-a-Z-g5iC.js} +0 -0
- /package/client/dist/assets/{analytics-C5dpiS0o.js → analytics-4-eskbtX.js} +0 -0
- /package/client/dist/assets/{analytics-CR1TJqUi2.js → analytics-4HvXb0Vn2.js} +0 -0
- /package/client/dist/assets/{analytics-rmHuJv0V.js → analytics-Bd9rHGjV.js} +0 -0
- /package/client/dist/assets/{analytics-BWMUZ2Pq2.js → analytics-Bpe41fnX2.js} +0 -0
- /package/client/dist/assets/{analytics-Bj-SmKHa.js → analytics-CpSTNfui.js} +0 -0
- /package/client/dist/assets/{analytics-CCprAqk3.js → analytics-DEe5BMz8.js} +0 -0
- /package/client/dist/assets/{analytics-DfNdXR0y2.js → analytics-DcOITHla2.js} +0 -0
- /package/client/dist/assets/{attachments-BjyMmrwE.js → attachments-CA2f3qL3.js} +0 -0
- /package/client/dist/assets/{attachments-DCZOL1RU.js → attachments-CHDbhKeH.js} +0 -0
- /package/client/dist/assets/{attachments-rNn1ekRy2.js → attachments-CKAY5aYG2.js} +0 -0
- /package/client/dist/assets/{attachments-BgBNSqQd.js → attachments-CgOQ9K-6.js} +0 -0
- /package/client/dist/assets/{attachments-DPCmkFZu2.js → attachments-D2jwJXQ42.js} +0 -0
- /package/client/dist/assets/{attachments-C9GTTJsx2.js → attachments-DlEQu56g2.js} +0 -0
- /package/client/dist/assets/{attachments-DL-bPy9E.js → attachments-jVdKzAXn.js} +0 -0
- /package/client/dist/assets/{browser-Cvg-QHvd2.js → browser-BQFDxrRq.js} +0 -0
- /package/client/dist/assets/{browser-rWKj-RR_2.js → browser-BbJ_q_OT2.js} +0 -0
- /package/client/dist/assets/{browser-Cc0iDqUi.js → browser-C4VJ5CW12.js} +0 -0
- /package/client/dist/assets/{browser-BPkhILSJ.js → browser-Cl-FwPXs.js} +0 -0
- /package/client/dist/assets/{browser-FRv8P02E.js → browser-Do2jf8_N.js} +0 -0
- /package/client/dist/assets/{browser-BqLqJVVT.js → browser-JsiohNmW.js} +0 -0
- /package/client/dist/assets/{browser-BijVYG0Y2.js → browser-kA31eYhu2.js} +0 -0
- /package/client/dist/assets/{chat-DdwZjT8K2.js → chat-BMWD_Rx12.js} +0 -0
- /package/client/dist/assets/{chat-Bom6bqK3.js → chat-BalviUAD.js} +0 -0
- /package/client/dist/assets/{chat-DsbwJhus.js → chat-CJddjN3D2.js} +0 -0
- /package/client/dist/assets/{chat-Dnwg-NpQ.js → chat-D5xEtsf3.js} +0 -0
- /package/client/dist/assets/{chat-vylxqR0P2.js → chat-Ds8GvCnv.js} +0 -0
- /package/client/dist/assets/{chat-BgL3zv6x2.js → chat-SaG-ng1x2.js} +0 -0
- /package/client/dist/assets/{chat-Bh3NdgJs.js → chat-n43nZGvn.js} +0 -0
- /package/client/dist/assets/{code-DzC1rMk12.js → code-Bit_z2Xd2.js} +0 -0
- /package/client/dist/assets/{code-BRcDlze52.js → code-BotxM43N2.js} +0 -0
- /package/client/dist/assets/{code-UPVvmAKM2.js → code-C9U6M5-e2.js} +0 -0
- /package/client/dist/assets/{code-BdMy3Ffq.js → code-CIPmDEd0.js} +0 -0
- /package/client/dist/assets/{code-DgeHz9CB.js → code-DUe1Bouz.js} +0 -0
- /package/client/dist/assets/{code-CwnBPPyA.js → code-F_KjccGf.js} +0 -0
- /package/client/dist/assets/{code-CeQ0FZpC.js → code-HDK4P6Rx.js} +0 -0
- /package/client/dist/assets/{commands-DkU0qliq.js → commands-BFQZc9AG.js} +0 -0
- /package/client/dist/assets/{commands-u-4eEHCN.js → commands-BMkC5-ki.js} +0 -0
- /package/client/dist/assets/{commands-CSDs9q_2.js → commands-BllkJauO.js} +0 -0
- /package/client/dist/assets/{commands-xHDDGfcP2.js → commands-CWX9WmDX2.js} +0 -0
- /package/client/dist/assets/{commands-C4Y3C-PF2.js → commands-CzFSoIiq2.js} +0 -0
- /package/client/dist/assets/{commands-JWMfGDmr.js → commands-KBbhsnw8.js} +0 -0
- /package/client/dist/assets/{commands-RHOchN4n2.js → commands-cKTaLwwV2.js} +0 -0
- /package/client/dist/assets/{common-DmqBPwZG.js → common-BfQD173j.js} +0 -0
- /package/client/dist/assets/{common-DhWTkocg.js → common-CF8iGXrG.js} +0 -0
- /package/client/dist/assets/{common-Bwafujnc2.js → common-Cfmwcrl52.js} +0 -0
- /package/client/dist/assets/{common-C-vU-j-I.js → common-D2cimsul.js} +0 -0
- /package/client/dist/assets/{common-C5HKSbbz.js → common-DH7fjyL1.js} +0 -0
- /package/client/dist/assets/{common-BM3LFLCE2.js → common-DZbXlmq72.js} +0 -0
- /package/client/dist/assets/{common-DiPTT7V_2.js → common-DtXmzX2o2.js} +0 -0
- /package/client/dist/assets/{explore-UvW1jEMa.js → explore-BEpIs343.js} +0 -0
- /package/client/dist/assets/{explore-CNBzrEPS.js → explore-BO_OinBD.js} +0 -0
- /package/client/dist/assets/{explore-C0k8HrkN.js → explore-Bhy7WWXv.js} +0 -0
- /package/client/dist/assets/{explore-B7fblXBb2.js → explore-CAc8Qzu32.js} +0 -0
- /package/client/dist/assets/{explore-D_mWBYCS2.js → explore-DCZL9gRN2.js} +0 -0
- /package/client/dist/assets/{explore-Dahc1lOx.js → explore-DW_YTkQh.js} +0 -0
- /package/client/dist/assets/{explore-BHtEpoNN2.js → explore-ELbrSZyQ2.js} +0 -0
- /package/client/dist/assets/{format-command-CuWuRvka.js → format-command-Db1viey1.js} +0 -0
- /package/client/dist/assets/{integrations-BNuauK3o.js → integrations-BQNkrfr7.js} +0 -0
- /package/client/dist/assets/{integrations-Cl_wD5AI.js → integrations-BdN2f6bP.js} +0 -0
- /package/client/dist/assets/{integrations-CHy8tnpV.js → integrations-BrIUJmVf.js} +0 -0
- /package/client/dist/assets/{integrations-Cy8nZ_dv2.js → integrations-Cyp7cSYv2.js} +0 -0
- /package/client/dist/assets/{integrations-4KZnYECp.js → integrations-DDR3OHdq.js} +0 -0
- /package/client/dist/assets/{integrations-7W266oqF2.js → integrations-DVxi8b0j2.js} +0 -0
- /package/client/dist/assets/{integrations-zMqY4VU12.js → integrations-RuNEX3La2.js} +0 -0
- /package/client/dist/assets/{jira-uNH1gETL2.js → jira-B5wszJ1-2.js} +0 -0
- /package/client/dist/assets/{jira-BHAKtc4B.js → jira-C5UjxHE4.js} +0 -0
- /package/client/dist/assets/{jira-D5efgwp_.js → jira-CQ7mrZde.js} +0 -0
- /package/client/dist/assets/{jira-bDs7Im2C.js → jira-DIRkZk9U.js} +0 -0
- /package/client/dist/assets/{jira-CPpgA-5z2.js → jira-DTYu3hJi2.js} +0 -0
- /package/client/dist/assets/{jira-DS7ownDy.js → jira-DtZpokjJ.js} +0 -0
- /package/client/dist/assets/{jira-Be369RIR2.js → jira-ZdKb-50F2.js} +0 -0
- /package/client/dist/assets/{jobs-CE8qeIYj.js → jobs-BFIdeDTw.js} +0 -0
- /package/client/dist/assets/{jobs-BgQzB0Sx2.js → jobs-BJ-Gcao22.js} +0 -0
- /package/client/dist/assets/{jobs-1GV2-4zd2.js → jobs-DAGiPVIS2.js} +0 -0
- /package/client/dist/assets/{jobs-26F9gGcE.js → jobs-DAU8C_tZ.js} +0 -0
- /package/client/dist/assets/{jobs-BqWui9_f.js → jobs-Da0GaaSc.js} +0 -0
- /package/client/dist/assets/{jobs-Wh7dn28q2.js → jobs-P3unxtxo2.js} +0 -0
- /package/client/dist/assets/{jobs-DzeuASRf.js → jobs-p360zdf1.js} +0 -0
- /package/client/dist/assets/{loops-CvOWsi5r.js → loops-Bi4ywYGn.js} +0 -0
- /package/client/dist/assets/{loops-BvqQ-bHR2.js → loops-BjK8lmoZ2.js} +0 -0
- /package/client/dist/assets/{loops-K5_HXfJa.js → loops-D3wdUkZV.js} +0 -0
- /package/client/dist/assets/{loops-D6D3XHjj.js → loops-DDs4KPu_.js} +0 -0
- /package/client/dist/assets/{loops-LIgO_-eM.js → loops-DKzYFcq6.js} +0 -0
- /package/client/dist/assets/{loops-DJNhp17p2.js → loops-Du-bNVpc2.js} +0 -0
- /package/client/dist/assets/{loops-vhJdbcz92.js → loops-DvGbSfxw2.js} +0 -0
- /package/client/dist/assets/{mcp-XJNRfxVp2.js → mcp-BG-OCJca2.js} +0 -0
- /package/client/dist/assets/{mcp-DtbxuH3V2.js → mcp-CbJ04oFS2.js} +0 -0
- /package/client/dist/assets/{mcp-sRmWE8WJ.js → mcp-CrWdq5db.js} +0 -0
- /package/client/dist/assets/{mcp-H-gnQUdN.js → mcp-Dd844F5_.js} +0 -0
- /package/client/dist/assets/{mcp-Dcl-XxkR.js → mcp-DfmzoT5t.js} +0 -0
- /package/client/dist/assets/{mcp-CjVKXAhD2.js → mcp-DyULurYS2.js} +0 -0
- /package/client/dist/assets/{mcp-N1nzp8oL.js → mcp-zSJ2TsJj.js} +0 -0
- /package/client/dist/assets/{nav-EI_OhEAo.js → nav-BFCUtHUQ2.js} +0 -0
- /package/client/dist/assets/{nav-CGy4yx3C.js → nav-BouzorQ5.js} +0 -0
- /package/client/dist/assets/{nav-40HzpIFb.js → nav-Bpr-K0tD.js} +0 -0
- /package/client/dist/assets/{nav-B6PkGfTk2.js → nav-CpFrRDXd2.js} +0 -0
- /package/client/dist/assets/{nav-CXEDcRdv2.js → nav-Dkv4Vlr_2.js} +0 -0
- /package/client/dist/assets/{nav-8yPhBn00.js → nav-GG6azCbh.js} +0 -0
- /package/client/dist/assets/{nav-BHqMLrS72.js → nav-prHkMqcr.js} +0 -0
- /package/client/dist/assets/{setup-BuQNKztt2.js → setup-1vN48B-m2.js} +0 -0
- /package/client/dist/assets/{setup-BwMovM9z2.js → setup-BmJ0Kx16.js} +0 -0
- /package/client/dist/assets/{setup-BXpw07r-.js → setup-DQWsu2TR.js} +0 -0
- /package/client/dist/assets/{setup-D84y3wx-.js → setup-JeBwoXoM.js} +0 -0
- /package/client/dist/assets/{setup-DU0ULX8q2.js → setup-T_ApeQDV2.js} +0 -0
- /package/client/dist/assets/{setup-BiduSX9d.js → setup-mXnzww4f.js} +0 -0
- /package/client/dist/assets/{setup-7J8TjG4O.js → setup-q0wRHUp1.js} +0 -0
- /package/client/dist/assets/{specs-CWtKcVDC.js → specs-BGRKhM8t.js} +0 -0
- /package/client/dist/assets/{specs-CBDvEYKv.js → specs-C6J2zrLM.js} +0 -0
- /package/client/dist/assets/{specs-D2clrt2c2.js → specs-CT8exGOy2.js} +0 -0
- /package/client/dist/assets/{specs-C9Sd7BhH2.js → specs-Ce5y2iFc2.js} +0 -0
- /package/client/dist/assets/{specs-CIPr9reA2.js → specs-DZDoZsDq.js} +0 -0
- /package/client/dist/assets/{specs-PozUNhqb.js → specs-DbtIzV8a.js} +0 -0
- /package/client/dist/assets/{specs-DTplt2XM.js → specs-GkiL-snY.js} +0 -0
- /package/client/dist/assets/{terminal-xGbCZJHg.js → terminal-BNSg2lR7.js} +0 -0
- /package/client/dist/assets/{terminal-De2Brd23.js → terminal-BTpE7iki.js} +0 -0
- /package/client/dist/assets/{terminal-BRm03iFA2.js → terminal-Cvabyzm42.js} +0 -0
- /package/client/dist/assets/{terminal-DxQWCqSD2.js → terminal-D0z12_sJ2.js} +0 -0
- /package/client/dist/assets/{terminal-Z7KsDM_n.js → terminal-DDk_bTw0.js} +0 -0
- /package/client/dist/assets/{terminal-cqfd3EDV.js → terminal-DEyt0prF.js} +0 -0
- /package/client/dist/assets/{terminal-WSAvHXoU.js → terminal-a6Uq8B0p.js} +0 -0
- /package/client/dist/assets/{tickets-C3b8cHhd2.js → tickets-BIxU86j12.js} +0 -0
- /package/client/dist/assets/{tickets-SuX_HkK4.js → tickets-C2BhwzwR.js} +0 -0
- /package/client/dist/assets/{tickets-DTHo9c3C.js → tickets-D1IeX2EW.js} +0 -0
- /package/client/dist/assets/{tickets-BoXom7l8.js → tickets-DZut8Ryt.js} +0 -0
- /package/client/dist/assets/{tickets-CWYFvx1W2.js → tickets-LfK6LzKl2.js} +0 -0
- /package/client/dist/assets/{tickets-CFXnJsYF.js → tickets-dQfsc7Ph.js} +0 -0
- /package/client/dist/assets/{tickets-B7zT4IRf.js → tickets-sVun0I6v.js} +0 -0
|
@@ -82,6 +82,7 @@ const file_story_1 = require("./file-story");
|
|
|
82
82
|
const providers_1 = require("./providers");
|
|
83
83
|
const pr_publisher_1 = require("./pr-publisher");
|
|
84
84
|
const active_pr_continuation_1 = require("./active-pr-continuation");
|
|
85
|
+
const rail_worktree_release_1 = require("./rail-worktree-release");
|
|
85
86
|
/**
|
|
86
87
|
* Ticket data for the conventional branch name (pr-naming): title/labels from
|
|
87
88
|
* the ticket-spec reader, the Jira key resolved with the authoritative
|
|
@@ -496,16 +497,33 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
496
497
|
// Build-settle: persist the per-unit branch outcomes + this launch's
|
|
497
498
|
// worktree ledger ids on the row (the deferred create-pr / discard actions
|
|
498
499
|
// reconstruct their inputs from these — nothing survives in memory), then
|
|
499
|
-
// hand the decision to the user. 0 succeeded units → nothing deliverable
|
|
500
|
-
//
|
|
500
|
+
// hand the decision to the user. 0 succeeded units → nothing deliverable;
|
|
501
|
+
// keep the implementation card visible as a failed job state with run-log
|
|
502
|
+
// chips instead of silently auto-discarding or leaving it at "building".
|
|
501
503
|
const worktreeIds = allocated.map((a) => a.ledgerId);
|
|
502
504
|
const anySucceeded = results.some((r) => r.succeeded);
|
|
503
505
|
const settledContinuation = anySucceeded ? launchContinuation : null;
|
|
506
|
+
let continuationPushFailed = false;
|
|
507
|
+
let continuationPushFailureReason = null;
|
|
508
|
+
if (settledContinuation) {
|
|
509
|
+
const pushed = await (0, pr_publisher_1.pushBranch)(exec, {
|
|
510
|
+
repoDir: baseRepo,
|
|
511
|
+
branch: settledContinuation.branch,
|
|
512
|
+
baseBranch: settledContinuation.baseBranch,
|
|
513
|
+
});
|
|
514
|
+
if (pushed.state === 'local-only') {
|
|
515
|
+
continuationPushFailed = true;
|
|
516
|
+
continuationPushFailureReason = pushed.reason;
|
|
517
|
+
console.warn(`[rail-isolated] existing PR follow-up push failed for ${settledContinuation.branch}: ${continuationPushFailureReason}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
504
520
|
const next = anySucceeded
|
|
505
521
|
? (settledContinuation
|
|
506
|
-
? (
|
|
522
|
+
? (continuationPushFailed
|
|
523
|
+
? 'pr_failed'
|
|
524
|
+
: (settledContinuation.isDraft === false ? 'pr_ready' : 'pr_draft'))
|
|
507
525
|
: 'on_review')
|
|
508
|
-
: '
|
|
526
|
+
: 'implementation_failed';
|
|
509
527
|
const patch = settledContinuation
|
|
510
528
|
? {
|
|
511
529
|
branches,
|
|
@@ -513,17 +531,20 @@ async function launchIsolatedRail(input, io = {}) {
|
|
|
513
531
|
branch: settledContinuation.branch,
|
|
514
532
|
prUrl: settledContinuation.prUrl,
|
|
515
533
|
prNumber: settledContinuation.prNumber,
|
|
516
|
-
prState: 'pr-created',
|
|
534
|
+
prState: continuationPushFailed ? 'local-only' : 'pr-created',
|
|
517
535
|
}
|
|
518
536
|
: { branches, worktreeIds };
|
|
519
537
|
if (prDeliveryId && (0, rail_pr_store_1.transitionDecision)(ctx.db, prDeliveryId, 'building', next, patch)) {
|
|
538
|
+
if (settledContinuation && !continuationPushFailed) {
|
|
539
|
+
await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: ctx.db, git, repoDir: baseRepo, worktreeIds });
|
|
540
|
+
}
|
|
520
541
|
broadcastPrState();
|
|
521
542
|
// Completion driver: refresh the origin conversation's card in place —
|
|
522
543
|
// on_review asks the question, discarded informs the outcome.
|
|
523
544
|
syncOriginCard('update');
|
|
524
545
|
}
|
|
525
546
|
if (!anySucceeded) {
|
|
526
|
-
//
|
|
547
|
+
// FAILED-IMPLEMENTATION CLEANUP (0 succeeded): unmount every worktree NOW rather
|
|
527
548
|
// than leaving it for a restart's reconcile sweep — a still-mounted
|
|
528
549
|
// worktree POISONS the next run of the same ticket: the worktree path is
|
|
529
550
|
// keyed by ticketId, so the next allocation silently reuses this
|
|
@@ -67,7 +67,9 @@ exports.executePrDecision = executePrDecision;
|
|
|
67
67
|
const path = __importStar(require("path"));
|
|
68
68
|
const artifact_registry_1 = require("./artifact-registry");
|
|
69
69
|
const worktree_manager_1 = require("./worktree-manager");
|
|
70
|
+
const pr_publisher_1 = require("./pr-publisher");
|
|
70
71
|
const rail_pr_delivery_1 = require("./rail-pr-delivery");
|
|
72
|
+
const rail_worktree_release_1 = require("./rail-worktree-release");
|
|
71
73
|
const pr_naming_1 = require("./pr-naming");
|
|
72
74
|
const pr_body_1 = require("./pr-body");
|
|
73
75
|
const jira_db_1 = require("./jira/jira-db");
|
|
@@ -105,7 +107,8 @@ function actionAllowed(action, row) {
|
|
|
105
107
|
return row.decision === 'pr_draft' && row.pr_url !== null;
|
|
106
108
|
case 'discard':
|
|
107
109
|
return row.decision === 'on_review' || row.decision === 'pr_draft' ||
|
|
108
|
-
row.decision === 'pr_ready' || row.decision === '
|
|
110
|
+
row.decision === 'pr_ready' || row.decision === 'implementation_failed' ||
|
|
111
|
+
row.decision === 'pr_failed';
|
|
109
112
|
case 'poll-merge':
|
|
110
113
|
return (row.decision === 'pr_draft' || row.decision === 'pr_ready') && row.pr_url !== null;
|
|
111
114
|
case 'merge-local':
|
|
@@ -171,6 +174,46 @@ function batchWorktreeRoot(slug) {
|
|
|
171
174
|
// Mirrors rail-isolated-launch's worktreesRoot — derivable, never stored.
|
|
172
175
|
return path.join((0, artifact_registry_1.resolveHome)(), '.specrails', 'projects', slug, 'worktrees');
|
|
173
176
|
}
|
|
177
|
+
async function resolveExistingPrDecisionAfterPush(deps, row) {
|
|
178
|
+
if (!row.pr_url)
|
|
179
|
+
return 'pr_ready';
|
|
180
|
+
const r = await deps.exec.run('gh', ['pr', 'view', row.pr_url, '--json', 'isDraft,state'], deps.project.path);
|
|
181
|
+
if (r.code !== 0)
|
|
182
|
+
return 'pr_ready';
|
|
183
|
+
try {
|
|
184
|
+
const parsed = JSON.parse(r.stdout);
|
|
185
|
+
if (parsed.isDraft === true)
|
|
186
|
+
return 'pr_draft';
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
/* fall through to the conservative published/open state */
|
|
190
|
+
}
|
|
191
|
+
return 'pr_ready';
|
|
192
|
+
}
|
|
193
|
+
async function retryExistingPrFollowupPush(deps, row) {
|
|
194
|
+
if (!row.branch || !row.pr_url)
|
|
195
|
+
return { status: 502, body: { error: 'push_failed', detail: 'missing PR branch/url' } };
|
|
196
|
+
const pushed = await (0, pr_publisher_1.pushBranch)(deps.exec, {
|
|
197
|
+
repoDir: deps.project.path,
|
|
198
|
+
branch: row.branch,
|
|
199
|
+
baseBranch: row.base_branch,
|
|
200
|
+
});
|
|
201
|
+
if (pushed.state === 'local-only') {
|
|
202
|
+
const conflict = casTransition(deps, row, 'pr_failed', { prState: 'local-only' });
|
|
203
|
+
if (conflict)
|
|
204
|
+
return conflict;
|
|
205
|
+
finalizeTransition(deps, row.id);
|
|
206
|
+
return { status: 200, body: { ok: true, decision: 'pr_failed', prUrl: row.pr_url, detail: pushed.reason } };
|
|
207
|
+
}
|
|
208
|
+
const next = await resolveExistingPrDecisionAfterPush(deps, row);
|
|
209
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
210
|
+
const conflict = casTransition(deps, row, next, { prState: 'pr-created' });
|
|
211
|
+
if (conflict)
|
|
212
|
+
return conflict;
|
|
213
|
+
await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds });
|
|
214
|
+
const after = finalizeTransition(deps, row.id);
|
|
215
|
+
return { status: 200, body: { ok: true, decision: next, prUrl: after?.prUrl ?? row.pr_url, prState: 'pr-created' } };
|
|
216
|
+
}
|
|
174
217
|
/**
|
|
175
218
|
* Load the PR's ticket data (title/labels/description) from the ticket store,
|
|
176
219
|
* with the Jira key resolved PER TICKET: the authoritative `jira_links` row
|
|
@@ -240,6 +283,9 @@ async function findRecoverableBranch(deps, unit, baseBranch) {
|
|
|
240
283
|
return null;
|
|
241
284
|
}
|
|
242
285
|
async function runCreatePr(deps, row) {
|
|
286
|
+
if (row.decision === 'pr_failed' && row.pr_url && row.branch) {
|
|
287
|
+
return retryExistingPrFollowupPush(deps, row);
|
|
288
|
+
}
|
|
243
289
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
244
290
|
// Pre-flight: every succeeded unit branch must actually exist before we push
|
|
245
291
|
// it — a missing ref can only degrade to local-only again, so a retry on a
|
|
@@ -363,6 +409,9 @@ async function runCreatePr(deps, row) {
|
|
|
363
409
|
const conflict = casTransition(deps, row, next, patch);
|
|
364
410
|
if (conflict)
|
|
365
411
|
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
|
+
}
|
|
366
415
|
const after = finalizeTransition(deps, row.id);
|
|
367
416
|
// Tickets stay on_review — a draft PR is still awaiting the engineer's merge.
|
|
368
417
|
return {
|
|
@@ -380,17 +429,25 @@ async function runPublish(deps, row) {
|
|
|
380
429
|
const r = await deps.exec.run('gh', ['pr', 'ready', row.pr_url], deps.project.path);
|
|
381
430
|
if (r.code !== 0)
|
|
382
431
|
return ghFailed(r); // no transition — the draft is still a draft
|
|
432
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
383
433
|
const conflict = casTransition(deps, row, 'pr_ready');
|
|
384
434
|
if (conflict)
|
|
385
435
|
return conflict;
|
|
436
|
+
await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds });
|
|
386
437
|
finalizeTransition(deps, row.id);
|
|
387
438
|
return { status: 200, body: { ok: true, decision: 'pr_ready', prUrl: row.pr_url } };
|
|
388
439
|
}
|
|
389
440
|
async function runDiscard(deps, row) {
|
|
390
441
|
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
442
|
+
const implementationFailed = row.decision === 'implementation_failed';
|
|
391
443
|
// 1. Close the PR (drops its head branch) when one exists. Best-effort — a
|
|
392
444
|
// gh failure must never leave the discard half-done.
|
|
393
|
-
|
|
445
|
+
//
|
|
446
|
+
// Implementation-failed rows are different: the implement run produced no
|
|
447
|
+
// deliverable update. If the row belongs to an existing-PR continuation it may
|
|
448
|
+
// still carry that PR's url/branch from launch time; clearing the failed card
|
|
449
|
+
// must not close or delete the user's existing review branch.
|
|
450
|
+
if (row.pr_url && !implementationFailed) {
|
|
394
451
|
try {
|
|
395
452
|
const r = await deps.exec.run('gh', ['pr', 'close', row.pr_url, '--delete-branch'], deps.project.path);
|
|
396
453
|
if (r.code !== 0) {
|
|
@@ -404,12 +461,13 @@ async function runDiscard(deps, row) {
|
|
|
404
461
|
// 2. Remove the launch's worktrees (still on disk unless a restart already
|
|
405
462
|
// swept them) and close their ledger rows — the reconcile-style removal,
|
|
406
463
|
// branch deletion deferred to step 3 (a checked-out branch can't be -D'd).
|
|
407
|
-
const branchSet = new Set(snap.branches.map((b) => b.branch));
|
|
464
|
+
const branchSet = new Set(implementationFailed ? [] : snap.branches.map((b) => b.branch));
|
|
408
465
|
for (const wtId of snap.worktreeIds) {
|
|
409
466
|
const wt = (0, rail_worktrees_store_1.getRailWorktree)(deps.db, wtId);
|
|
410
467
|
if (!wt)
|
|
411
468
|
continue;
|
|
412
|
-
|
|
469
|
+
if (!implementationFailed)
|
|
470
|
+
branchSet.add(wt.branch);
|
|
413
471
|
await (0, worktree_manager_1.removeWorktree)(deps.git, {
|
|
414
472
|
repoDir: deps.project.path, worktreePath: wt.worktree_path, branch: wt.branch, deleteBranch: false,
|
|
415
473
|
}).catch(() => { });
|
|
@@ -421,22 +479,24 @@ async function runDiscard(deps, row) {
|
|
|
421
479
|
// best-effort from the same ticket data the create-pr path names it from
|
|
422
480
|
// (the delivered head is already covered by row.branch). Missing branches
|
|
423
481
|
// are tolerated; the integration branch is NEVER deleted.
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
482
|
+
if (!implementationFailed) {
|
|
483
|
+
if (row.branch)
|
|
484
|
+
branchSet.add(row.branch);
|
|
485
|
+
const succeededUnits = snap.branches.filter((b) => b.succeeded);
|
|
486
|
+
if (succeededUnits.length > 1) {
|
|
487
|
+
try {
|
|
488
|
+
branchSet.add((0, pr_naming_1.batchBranchNameFor)(loadPrTicketData(deps, succeededUnits.map((b) => b.ticketId))));
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
/* best-effort defense-in-depth only */
|
|
492
|
+
}
|
|
430
493
|
}
|
|
431
|
-
|
|
432
|
-
|
|
494
|
+
for (const branch of branchSet) {
|
|
495
|
+
if (!branch || branch === row.base_branch)
|
|
496
|
+
continue;
|
|
497
|
+
await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
|
|
433
498
|
}
|
|
434
499
|
}
|
|
435
|
-
for (const branch of branchSet) {
|
|
436
|
-
if (!branch || branch === row.base_branch)
|
|
437
|
-
continue;
|
|
438
|
-
await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
|
|
439
|
-
}
|
|
440
500
|
const conflict = casTransition(deps, row, 'discarded');
|
|
441
501
|
if (conflict)
|
|
442
502
|
return conflict;
|
|
@@ -467,11 +527,29 @@ async function runPollMerge(deps, row) {
|
|
|
467
527
|
// Not merged (OPEN/CLOSED/unknown) → observation only, nothing changes.
|
|
468
528
|
return { status: 200, body: { ok: true, decision: row.decision, merged: false } };
|
|
469
529
|
}
|
|
530
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
531
|
+
const branchSet = new Set(snap.branches.map((b) => b.branch));
|
|
532
|
+
if (row.branch)
|
|
533
|
+
branchSet.add(row.branch);
|
|
534
|
+
const succeededUnits = snap.branches.filter((b) => b.succeeded);
|
|
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
|
+
}
|
|
542
|
+
}
|
|
543
|
+
await (0, rail_worktree_release_1.releaseRailWorktrees)({ db: deps.db, git: deps.git, repoDir: deps.project.path, worktreeIds: snap.worktreeIds, state: 'merged' });
|
|
544
|
+
for (const branch of branchSet) {
|
|
545
|
+
if (!branch || branch === row.base_branch)
|
|
546
|
+
continue;
|
|
547
|
+
await deps.git.run(['branch', '-D', branch], deps.project.path).catch(() => { });
|
|
548
|
+
}
|
|
470
549
|
const conflict = casTransition(deps, row, 'merged');
|
|
471
550
|
if (conflict)
|
|
472
551
|
return conflict;
|
|
473
552
|
finalizeTransition(deps, row.id);
|
|
474
|
-
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
475
553
|
const changed = applyTicketTransition(deps, snap.ticketIds, 'done');
|
|
476
554
|
try {
|
|
477
555
|
deps.jiraSyncManager?.onRailMerged(changed, row.id, row.pr_url);
|
|
@@ -6,6 +6,7 @@ exports.createPrDelivery = createPrDelivery;
|
|
|
6
6
|
exports.getPrDelivery = getPrDelivery;
|
|
7
7
|
exports.getActivePrDeliveryByRail = getActivePrDeliveryByRail;
|
|
8
8
|
exports.listActivePrDeliveries = listActivePrDeliveries;
|
|
9
|
+
exports.reconcileFailedBuildingPrDeliveries = reconcileFailedBuildingPrDeliveries;
|
|
9
10
|
exports.transitionDecision = transitionDecision;
|
|
10
11
|
exports.toPrDeliverySnapshot = toPrDeliverySnapshot;
|
|
11
12
|
exports.toRailPrStateMessage = toRailPrStateMessage;
|
|
@@ -19,6 +20,7 @@ exports.ACTIVE_PR_DECISIONS = new Set([
|
|
|
19
20
|
'on_review',
|
|
20
21
|
'pr_draft',
|
|
21
22
|
'pr_ready',
|
|
23
|
+
'implementation_failed',
|
|
22
24
|
'pr_failed',
|
|
23
25
|
]);
|
|
24
26
|
function isTerminalPrDecision(s) {
|
|
@@ -60,6 +62,40 @@ function listActivePrDeliveries(db) {
|
|
|
60
62
|
ORDER BY rail_index, created_at DESC, rowid DESC`)
|
|
61
63
|
.all();
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Recover implementation cards stranded at `building` after the loop process
|
|
67
|
+
* already settled all associated run ids without a success. This covers server
|
|
68
|
+
* restarts and the settle gap where loop_runs/worktrees mark failure but the
|
|
69
|
+
* PR-delivery row never receives its final card state.
|
|
70
|
+
*/
|
|
71
|
+
function reconcileFailedBuildingPrDeliveries(db) {
|
|
72
|
+
const rows = db
|
|
73
|
+
.prepare(`SELECT * FROM rail_pr_deliveries WHERE decision = 'building' ORDER BY created_at ASC, rowid ASC`)
|
|
74
|
+
.all();
|
|
75
|
+
const reconciled = [];
|
|
76
|
+
for (const row of rows) {
|
|
77
|
+
const runIds = parseJsonArray(row.run_ids ?? '[]');
|
|
78
|
+
if (runIds.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
const uniqueRunIds = [...new Set(runIds)];
|
|
81
|
+
const placeholders = uniqueRunIds.map(() => '?').join(',');
|
|
82
|
+
const runs = db
|
|
83
|
+
.prepare(`SELECT id, status, final_outcome FROM loop_runs WHERE id IN (${placeholders})`)
|
|
84
|
+
.all(...uniqueRunIds);
|
|
85
|
+
if (runs.length !== uniqueRunIds.length)
|
|
86
|
+
continue;
|
|
87
|
+
if (runs.some((run) => run.status !== 'completed'))
|
|
88
|
+
continue;
|
|
89
|
+
if (runs.some((run) => run.final_outcome === 'success'))
|
|
90
|
+
continue;
|
|
91
|
+
if (transitionDecision(db, row.id, 'building', 'implementation_failed')) {
|
|
92
|
+
const updated = getPrDelivery(db, row.id);
|
|
93
|
+
if (updated)
|
|
94
|
+
reconciled.push(updated);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return reconciled;
|
|
98
|
+
}
|
|
63
99
|
// Column allow-list — patch keys are interpolated into the SET clause, so gate
|
|
64
100
|
// them to prevent any future caller injecting SQL via an object key (values
|
|
65
101
|
// are always parameterized). Mirrors loop-runs-store's COUNTER_COLUMNS.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.releaseRailWorktrees = releaseRailWorktrees;
|
|
4
|
+
const rail_worktrees_store_1 = require("./rail-worktrees-store");
|
|
5
|
+
const worktree_manager_1 = require("./worktree-manager");
|
|
6
|
+
/** Remove Specrails-managed linked worktrees while keeping their local branches.
|
|
7
|
+
* PR deliveries use this once the work has a durable remote branch/PR: the
|
|
8
|
+
* branch remains available for local checkout, but the internal worktree stops
|
|
9
|
+
* blocking `git switch <branch>` in the user's main checkout. */
|
|
10
|
+
async function releaseRailWorktrees(input) {
|
|
11
|
+
const state = input.state ?? 'released';
|
|
12
|
+
for (const wtId of input.worktreeIds) {
|
|
13
|
+
const wt = (0, rail_worktrees_store_1.getRailWorktree)(input.db, wtId);
|
|
14
|
+
if (!wt)
|
|
15
|
+
continue;
|
|
16
|
+
await (0, worktree_manager_1.removeWorktree)(input.git, {
|
|
17
|
+
repoDir: input.repoDir,
|
|
18
|
+
worktreePath: wt.worktree_path,
|
|
19
|
+
branch: wt.branch,
|
|
20
|
+
deleteBranch: false,
|
|
21
|
+
}).catch(() => { });
|
|
22
|
+
if (!(0, rail_worktrees_store_1.isTerminalMergeState)(wt.merge_state))
|
|
23
|
+
(0, rail_worktrees_store_1.updateRailWorktreeState)(input.db, wt.id, state);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -12,7 +12,7 @@ exports.deleteRailWorktree = deleteRailWorktree;
|
|
|
12
12
|
exports.listRailWorktreesForTicket = listRailWorktreesForTicket;
|
|
13
13
|
exports.railWorktreeBranchExistsForTicket = railWorktreeBranchExistsForTicket;
|
|
14
14
|
/** States after which the worktree is done (eligible for cleanup/reconciliation). */
|
|
15
|
-
exports.TERMINAL_MERGE_STATES = new Set(['merged', 'needs-review', 'failed']);
|
|
15
|
+
exports.TERMINAL_MERGE_STATES = new Set(['merged', 'needs-review', 'failed', 'released']);
|
|
16
16
|
function isTerminalMergeState(s) {
|
|
17
17
|
return exports.TERMINAL_MERGE_STATES.has(s);
|
|
18
18
|
}
|
|
@@ -46,7 +46,7 @@ function listRailWorktrees(db, railIndex) {
|
|
|
46
46
|
* process (mark failed + sweep). */
|
|
47
47
|
function listNonTerminalRailWorktrees(db) {
|
|
48
48
|
return db
|
|
49
|
-
.prepare(`SELECT * FROM rail_worktrees WHERE merge_state NOT IN ('merged','needs-review','failed') ORDER BY rail_index, ticket_id`)
|
|
49
|
+
.prepare(`SELECT * FROM rail_worktrees WHERE merge_state NOT IN ('merged','needs-review','failed','released') ORDER BY rail_index, ticket_id`)
|
|
50
50
|
.all();
|
|
51
51
|
}
|
|
52
52
|
function deleteRailWorktree(db, id) {
|
|
@@ -23,6 +23,8 @@ const loop_effect_1 = require("./loop-effect");
|
|
|
23
23
|
const rail_pr_decision_1 = require("./rail-pr-decision");
|
|
24
24
|
const rail_isolated_launch_1 = require("./rail-isolated-launch");
|
|
25
25
|
const worktree_manager_1 = require("./worktree-manager");
|
|
26
|
+
const rail_worktree_release_1 = require("./rail-worktree-release");
|
|
27
|
+
const project_git_1 = require("./project-git");
|
|
26
28
|
const pr_publisher_1 = require("./pr-publisher");
|
|
27
29
|
const ids_1 = require("./ids");
|
|
28
30
|
const agent_chat_registry_1 = require("./agent-chat-registry");
|
|
@@ -36,7 +38,7 @@ const VALID_REASONING_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
|
36
38
|
function prDeliveryContinuesTickets(delivery, ticketIds) {
|
|
37
39
|
if (delivery.decision !== 'pr_draft' && delivery.decision !== 'pr_ready')
|
|
38
40
|
return false;
|
|
39
|
-
if (!delivery.prUrl || !delivery.branch)
|
|
41
|
+
if (!delivery.prUrl || !delivery.branch || delivery.prState !== 'pr-created')
|
|
40
42
|
return false;
|
|
41
43
|
const covered = new Set(delivery.ticketIds);
|
|
42
44
|
return ticketIds.length > 0 && ticketIds.every((id) => covered.has(id));
|
|
@@ -51,6 +53,11 @@ function emitPrDeliveryUpdate(c, prDeliveryId) {
|
|
|
51
53
|
(0, agent_chat_registry_1.getAgentChatManager)()?.updatePrDecisionCard(row.origin_conversation_id, (0, rail_pr_store_1.toPrDecisionCardEnvelope)(c.project.id, snap));
|
|
52
54
|
}
|
|
53
55
|
}
|
|
56
|
+
function reconcileAndEmitFailedPrDeliveries(c) {
|
|
57
|
+
for (const row of (0, rail_pr_store_1.reconcileFailedBuildingPrDeliveries)(c.db)) {
|
|
58
|
+
emitPrDeliveryUpdate(c, row.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
54
61
|
function createRailsRouter() {
|
|
55
62
|
const router = (0, express_1.Router)({ mergeParams: true });
|
|
56
63
|
function ctx(req) {
|
|
@@ -114,6 +121,7 @@ function createRailsRouter() {
|
|
|
114
121
|
// (non-terminal) delivery per rail slot, so a refreshed client re-renders
|
|
115
122
|
// the decision surface without waiting for a broadcast. The store lists
|
|
116
123
|
// newest-first within each rail — keep the first per index.
|
|
124
|
+
reconcileAndEmitFailedPrDeliveries(c);
|
|
117
125
|
const prDeliveries = {};
|
|
118
126
|
for (const row of (0, rail_pr_store_1.listActivePrDeliveries)(c.db)) {
|
|
119
127
|
if (!(row.rail_index in prDeliveries))
|
|
@@ -674,15 +682,43 @@ function createRailsRouter() {
|
|
|
674
682
|
if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'building', { runIds: loopRunIds })) {
|
|
675
683
|
emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
|
|
676
684
|
}
|
|
677
|
-
void Promise.allSettled(sharedRunSettles).then(() => {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (
|
|
683
|
-
|
|
685
|
+
void Promise.allSettled(sharedRunSettles).then((settled) => {
|
|
686
|
+
void (async () => {
|
|
687
|
+
const current = (0, rail_pr_store_1.getPrDelivery)(c.db, fallbackPrDeliveryId);
|
|
688
|
+
if (current?.decision !== 'building')
|
|
689
|
+
return;
|
|
690
|
+
if (current.pr_state === 'pr-created' && current.pr_url && current.branch) {
|
|
691
|
+
const succeeded = settled.every((s) => s.status === 'fulfilled' && s.value === true);
|
|
692
|
+
if (!succeeded) {
|
|
693
|
+
if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'implementation_failed')) {
|
|
694
|
+
emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
|
|
695
|
+
}
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
await (0, worktree_manager_1.commitWorktree)(worktree_manager_1.defaultGitRunner, c.project.path, `specrails: PR follow-up (${loopRunIds.join(', ')})`);
|
|
699
|
+
const pushed = await (0, pr_publisher_1.pushBranch)(pr_publisher_1.defaultExec, {
|
|
700
|
+
repoDir: c.project.path,
|
|
701
|
+
branch: current.branch,
|
|
702
|
+
baseBranch: current.base_branch,
|
|
703
|
+
});
|
|
704
|
+
if (pushed.state === 'local-only') {
|
|
705
|
+
if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_failed', { prState: 'local-only' })) {
|
|
706
|
+
emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
|
|
707
|
+
}
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_ready', { prState: 'pr-created' })) {
|
|
711
|
+
emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
|
|
712
|
+
}
|
|
684
713
|
}
|
|
685
|
-
}
|
|
714
|
+
})().catch((err) => {
|
|
715
|
+
console.error('[rails-router] existing PR fallback push failed:', err);
|
|
716
|
+
if (fallbackPrDeliveryId) {
|
|
717
|
+
if ((0, rail_pr_store_1.transitionDecision)(c.db, fallbackPrDeliveryId, 'building', 'pr_failed', { prState: 'local-only' })) {
|
|
718
|
+
emitPrDeliveryUpdate(c, fallbackPrDeliveryId);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
686
722
|
});
|
|
687
723
|
}
|
|
688
724
|
}
|
|
@@ -883,5 +919,55 @@ function createRailsRouter() {
|
|
|
883
919
|
res.status(500).json({ error: 'pr-decision failed', detail: err.message });
|
|
884
920
|
}
|
|
885
921
|
});
|
|
922
|
+
// POST /rails/pr-checkout — move the user's main checkout to the delivered PR
|
|
923
|
+
// branch. This is intentionally separate from PR decisions: it does not accept,
|
|
924
|
+
// publish, discard, or merge anything. It only releases Specrails-managed
|
|
925
|
+
// worktrees for this delivery (keeping branches) and then checks out the PR
|
|
926
|
+
// branch in the real project repo so the user can test/edit locally.
|
|
927
|
+
router.post('/pr-checkout', async (req, res) => {
|
|
928
|
+
const c = ctx(req);
|
|
929
|
+
const { prDeliveryId } = req.body ?? {};
|
|
930
|
+
if (typeof prDeliveryId !== 'string' || !prDeliveryId) {
|
|
931
|
+
res.status(400).json({ error: 'prDeliveryId is required' });
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const row = (0, rail_pr_store_1.getPrDelivery)(c.db, prDeliveryId);
|
|
935
|
+
if (!row) {
|
|
936
|
+
res.status(404).json({ error: 'Unknown prDeliveryId' });
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const snap = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
|
|
940
|
+
if (!snap.branch || !snap.prUrl) {
|
|
941
|
+
res.status(409).json({ error: 'checkout_unavailable', detail: 'delivery has no PR branch' });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
const info = await (0, project_git_1.getProjectGitInfo)(c.project.path);
|
|
946
|
+
if (!info.git) {
|
|
947
|
+
res.status(409).json({ error: 'checkout_unavailable', detail: 'project is not a git repository' });
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (info.dirty) {
|
|
951
|
+
res.status(409).json({ error: 'checkout_dirty', detail: 'Working tree has uncommitted changes. Commit or stash them before checkout.' });
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
await (0, rail_worktree_release_1.releaseRailWorktrees)({
|
|
955
|
+
db: c.db,
|
|
956
|
+
git: worktree_manager_1.defaultGitRunner,
|
|
957
|
+
repoDir: c.project.path,
|
|
958
|
+
worktreeIds: snap.worktreeIds,
|
|
959
|
+
});
|
|
960
|
+
const result = await (0, project_git_1.checkoutProjectReviewBranch)(c.project.path, snap.branch);
|
|
961
|
+
if (!result.ok) {
|
|
962
|
+
res.status(409).json({ error: 'checkout_failed', detail: result.error });
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
res.json({ ok: true, git: await (0, project_git_1.getProjectGitInfo)(c.project.path) });
|
|
966
|
+
}
|
|
967
|
+
catch (err) {
|
|
968
|
+
console.error('[rails-router] pr-checkout error:', err);
|
|
969
|
+
res.status(500).json({ error: 'pr-checkout failed', detail: err.message });
|
|
970
|
+
}
|
|
971
|
+
});
|
|
886
972
|
return router;
|
|
887
973
|
}
|