panopticon-cli 0.6.4 → 0.6.6
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/README.md +2 -2
- package/dist/{agents-DfYify9s.js → agents-CfFDs52G.js} +14 -14
- package/dist/{agents-DfYify9s.js.map → agents-CfFDs52G.js.map} +1 -1
- package/dist/{agents-BKsVoIc9.js → agents-D_2oRFVf.js} +1 -1
- package/dist/{archive-planning-BJrZ3tmN.js → archive-planning-D97ziGec.js} +3 -3
- package/dist/{archive-planning-BJrZ3tmN.js.map → archive-planning-D97ziGec.js.map} +1 -1
- package/dist/{archive-planning-C3m3hfa5.js → archive-planning-DK90wn9Q.js} +1 -1
- package/dist/{browser-Cvdznzc0.js → browser-CX7jXfXX.js} +1 -1
- package/dist/{browser-Cvdznzc0.js.map → browser-CX7jXfXX.js.map} +1 -1
- package/dist/{clean-planning-DvhZAUv4.js → clean-planning-D_lz4aQq.js} +2 -2
- package/dist/{clean-planning-DvhZAUv4.js.map → clean-planning-D_lz4aQq.js.map} +1 -1
- package/dist/clean-planning-x1S-JdmO.js +2 -0
- package/dist/cli/index.js +291 -760
- package/dist/cli/index.js.map +1 -1
- package/dist/{close-issue-Dr7yZmrr.js → close-issue-CaFE0stN.js} +11 -7
- package/dist/close-issue-CaFE0stN.js.map +1 -0
- package/dist/close-issue-CjcfZI9s.js +2 -0
- package/dist/compact-beads-B0_qE1w3.js +2 -0
- package/dist/{compact-beads-BCOtIIRl.js → compact-beads-CjFkteSU.js} +2 -2
- package/dist/{compact-beads-BCOtIIRl.js.map → compact-beads-CjFkteSU.js.map} +1 -1
- package/dist/{config-CRzMQRgA.js → config-BQNKsi9G.js} +2 -2
- package/dist/{config-CRzMQRgA.js.map → config-BQNKsi9G.js.map} +1 -1
- package/dist/{config-BYgUzQ21.js → config-agyKgF5C.js} +1 -1
- package/dist/{config-yaml-BgOACZAB.js → config-yaml-DGbLSMCa.js} +1 -1
- package/dist/{config-yaml-BgOACZAB.js.map → config-yaml-DGbLSMCa.js.map} +1 -1
- package/dist/{config-yaml-fdyvyL0S.js → config-yaml-Dqt4FWQH.js} +1 -1
- package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js → acceptance-criteria-Dk9hhiYj.js} +1 -1
- package/dist/dashboard/{acceptance-criteria-e5iiHlRx.js.map → acceptance-criteria-Dk9hhiYj.js.map} +1 -1
- package/dist/dashboard/{agent-enrichment-C67LJBgD.js → agent-enrichment-DdO7ZqjI.js} +11 -7
- package/dist/dashboard/agent-enrichment-DdO7ZqjI.js.map +1 -0
- package/dist/dashboard/{agent-enrichment-Cq0P1cNZ.js → agent-enrichment-dLeGE1fX.js} +1 -1
- package/dist/dashboard/{agents-YyO6t5Xa.js → agents-DCpQQ_W5.js} +14 -14
- package/dist/dashboard/{agents-YyO6t5Xa.js.map → agents-DCpQQ_W5.js.map} +1 -1
- package/dist/dashboard/{agents-BVBVCyat.js → agents-Dgh2TjSp.js} +1 -1
- package/dist/dashboard/{archive-planning-h-hAjk0P.js → archive-planning-BmW9UDTr.js} +3 -3
- package/dist/dashboard/{archive-planning-h-hAjk0P.js.map → archive-planning-BmW9UDTr.js.map} +1 -1
- package/dist/dashboard/{archive-planning-CScs1MOC.js → archive-planning-C3Ebf9yC.js} +1 -1
- package/dist/dashboard/{beads-qNB0yAHV.js → beads-Bv-AdX7G.js} +3 -3
- package/dist/dashboard/{beads-qNB0yAHV.js.map → beads-Bv-AdX7G.js.map} +1 -1
- package/dist/dashboard/{beads-D_FRedEJ.js → beads-By6-X07V.js} +1 -1
- package/dist/dashboard/clean-planning-D60L8rPY.js +2 -0
- package/dist/dashboard/{clean-planning-qafw99vY.js → clean-planning-VEJu5suh.js} +2 -2
- package/dist/dashboard/{clean-planning-qafw99vY.js.map → clean-planning-VEJu5suh.js.map} +1 -1
- package/dist/dashboard/close-issue-C2KeSKKJ.js +2 -0
- package/dist/dashboard/{close-issue-DfIggeZD.js → close-issue-DtKdsSTm.js} +11 -7
- package/dist/dashboard/close-issue-DtKdsSTm.js.map +1 -0
- package/dist/dashboard/compact-beads-C7BN5N11.js +2 -0
- package/dist/dashboard/{compact-beads-Dt0qTqsC.js → compact-beads-D8Vt3qyv.js} +2 -2
- package/dist/dashboard/{compact-beads-Dt0qTqsC.js.map → compact-beads-D8Vt3qyv.js.map} +1 -1
- package/dist/dashboard/{config-CUREjHP7.js → config-CDkGjnwy.js} +2 -2
- package/dist/dashboard/{config-CUREjHP7.js.map → config-CDkGjnwy.js.map} +1 -1
- package/dist/dashboard/{config-BeI3uy-8.js → config-CTXkBATQ.js} +1 -1
- package/dist/dashboard/{database-CozA13Wy.js → database-DhqASALP.js} +1 -1
- package/dist/dashboard/{database-C0y0hXBx.js → database-cxmQryoh.js} +2 -2
- package/dist/dashboard/{database-C0y0hXBx.js.map → database-cxmQryoh.js.map} +1 -1
- package/dist/dashboard/{dist-src-oG2iHzgI.js → dist-src-DTm11oQr.js} +1 -1
- package/dist/dashboard/{dist-src-oG2iHzgI.js.map → dist-src-DTm11oQr.js.map} +1 -1
- package/dist/dashboard/{event-store-D7kLBd07.js → event-store-VWWUmOfn.js} +1 -1
- package/dist/dashboard/{event-store-O9q0Gweh.js → event-store-vSmAA3Zp.js} +9 -4
- package/dist/dashboard/event-store-vSmAA3Zp.js.map +1 -0
- package/dist/dashboard/{factory-BnLdiQW-.js → factory-C8nhLGHB.js} +3 -3
- package/dist/dashboard/{factory-BnLdiQW-.js.map → factory-C8nhLGHB.js.map} +1 -1
- package/dist/dashboard/{feedback-writer-DyovUANg.js → feedback-writer-CudSe1WK.js} +2 -2
- package/dist/dashboard/{feedback-writer-DyovUANg.js.map → feedback-writer-CudSe1WK.js.map} +1 -1
- package/dist/dashboard/{feedback-writer-gSUv_W0h.js → feedback-writer-Wgv1cd1r.js} +1 -1
- package/dist/dashboard/{git-utils-BJRioREj.js → git-utils-C1m4SwAe.js} +1 -1
- package/dist/dashboard/{git-utils-BJRioREj.js.map → git-utils-C1m4SwAe.js.map} +1 -1
- package/dist/dashboard/{git-utils-BtCRddq3.js → git-utils-DQI8EYoj.js} +1 -1
- package/dist/dashboard/{github-app-XO-LBUGk.js → github-app-DClWjjHr.js} +1 -1
- package/dist/dashboard/{github-app-XO-LBUGk.js.map → github-app-DClWjjHr.js.map} +1 -1
- package/dist/dashboard/{health-events-db-584nYgJB.js → health-events-db-BMXQfInV.js} +1 -1
- package/dist/dashboard/{health-events-db-B3ChzN65.js → health-events-db-Do4NrOhC.js} +2 -2
- package/dist/dashboard/{health-events-db-B3ChzN65.js.map → health-events-db-Do4NrOhC.js.map} +1 -1
- package/dist/dashboard/{hooks-CKhs3N68.js → hooks-CB4T47NC.js} +1 -1
- package/dist/dashboard/{hooks-CErbP8Oq.js → hooks-CjqXOlNb.js} +2 -2
- package/dist/dashboard/{hooks-CErbP8Oq.js.map → hooks-CjqXOlNb.js.map} +1 -1
- package/dist/dashboard/hume-CA2pftu_.js +3 -0
- package/dist/dashboard/{hume-CX_U3Qha.js → hume-JsAlMOJC.js} +2 -2
- package/dist/dashboard/{hume-CX_U3Qha.js.map → hume-JsAlMOJC.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-B57kGDUV.js → inspect-agent-7eour7EA.js} +3 -3
- package/dist/dashboard/{inspect-agent-B57kGDUV.js.map → inspect-agent-7eour7EA.js.map} +1 -1
- package/dist/dashboard/{io-yGovuG4U.js → io-CWlFW78i.js} +1 -1
- package/dist/dashboard/{io-AJg-mzFi.js → io-DKS6359z.js} +1 -1
- package/dist/dashboard/{io-AJg-mzFi.js.map → io-DKS6359z.js.map} +1 -1
- package/dist/dashboard/issue-id-vwYJdsf8.js +62 -0
- package/dist/dashboard/issue-id-vwYJdsf8.js.map +1 -0
- package/dist/dashboard/{issue-service-singleton-DQK42EqH.js → issue-service-singleton-Co__-6kL.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js → issue-service-singleton-Wv4xBm3y.js} +7 -7
- package/dist/dashboard/{issue-service-singleton-sb2HkB9f.js.map → issue-service-singleton-Wv4xBm3y.js.map} +1 -1
- package/dist/dashboard/{label-cleanup-CZEsbtq9.js → label-cleanup-nVKTmIIW.js} +7 -4
- package/dist/dashboard/label-cleanup-nVKTmIIW.js.map +1 -0
- package/dist/dashboard/lifecycle-BcUmtkR4.js +7 -0
- package/dist/dashboard/{merge-agent-GLtMEsTu.js → merge-agent-CGN3TT0a.js} +1 -1
- package/dist/dashboard/{merge-agent-twroFuAh.js → merge-agent-yudQOPZc.js} +148 -46
- package/dist/dashboard/merge-agent-yudQOPZc.js.map +1 -0
- package/dist/dashboard/{paths-COdEvoXR.js → paths-BDyJ7BiV.js} +19 -2
- package/dist/dashboard/{paths-COdEvoXR.js.map → paths-BDyJ7BiV.js.map} +1 -1
- package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js → pipeline-notifier-CCSN-jar.js} +1 -1
- package/dist/dashboard/{pipeline-notifier-DM5AHG5Q.js.map → pipeline-notifier-CCSN-jar.js.map} +1 -1
- package/dist/dashboard/{plan-utils-BkCIhn3B.js → plan-utils-Bkcsqr_s.js} +3 -3
- package/dist/dashboard/{plan-utils-BkCIhn3B.js.map → plan-utils-Bkcsqr_s.js.map} +1 -1
- package/dist/dashboard/{prd-draft-D09Afalc.js → prd-draft-BD8oMkZ1.js} +2 -2
- package/dist/dashboard/{prd-draft-D09Afalc.js.map → prd-draft-BD8oMkZ1.js.map} +1 -1
- package/dist/dashboard/{projection-cache-DQ9zegkK.js → projection-cache-C0EL8s8h.js} +1 -1
- package/dist/dashboard/{projection-cache-DQ9zegkK.js.map → projection-cache-C0EL8s8h.js.map} +1 -1
- package/dist/dashboard/{projects-DyT3vSy-.js → projects-C5ozxjwP.js} +1 -1
- package/dist/dashboard/{projects-Cq3TWdPS.js → projects-CFVl4oHn.js} +25 -13
- package/dist/dashboard/projects-CFVl4oHn.js.map +1 -0
- package/dist/dashboard/{providers-Ck2sQd_F.js → providers-B5Y4H2Mg.js} +4 -4
- package/dist/dashboard/providers-B5Y4H2Mg.js.map +1 -0
- package/dist/dashboard/{providers-DVQnDekG.js → providers-csVZVPkE.js} +1 -1
- package/dist/dashboard/public/assets/{dist-CCJbQrSB.js → dist-BaQPC-c6.js} +1 -1
- package/dist/dashboard/public/assets/index-ByLmYGhW.js +212 -0
- package/dist/dashboard/public/assets/index-OEEbThNN.css +1 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/rally-6McpKKRa.js +3 -0
- package/dist/dashboard/{rally-Cwuae-4C.js → rally-YjFRxIiC.js} +2 -2
- package/dist/dashboard/{rally-Cwuae-4C.js.map → rally-YjFRxIiC.js.map} +1 -1
- package/dist/dashboard/{rally-api-DSUxm7EO.js → rally-api-C0WqCSkT.js} +1 -1
- package/dist/dashboard/{rally-api-DSUxm7EO.js.map → rally-api-C0WqCSkT.js.map} +1 -1
- package/dist/dashboard/{rally-api-CEH5KZi4.js → rally-api-DNttdCW4.js} +1 -1
- package/dist/dashboard/{remote-BHTTMpJJ.js → remote-Cigqjj3f.js} +2 -2
- package/dist/dashboard/{remote-BXo_iIku.js → remote-ObpNZ7hF.js} +2 -2
- package/dist/dashboard/{remote-BXo_iIku.js.map → remote-ObpNZ7hF.js.map} +1 -1
- package/dist/dashboard/{remote-agents-CTKVhFFY.js → remote-agents-Bf3GuM7t.js} +1 -1
- package/dist/dashboard/{remote-agents-C0_0LLNd.js → remote-agents-DFyjT1Le.js} +1 -1
- package/dist/dashboard/{remote-agents-C0_0LLNd.js.map → remote-agents-DFyjT1Le.js.map} +1 -1
- package/dist/dashboard/{review-status-CK3eBGyb.js → review-status-BtXqWBhS.js} +1 -1
- package/dist/dashboard/{review-status-CV55Tl-n.js → review-status-Bymwzh2i.js} +44 -4
- package/dist/dashboard/{review-status-CV55Tl-n.js.map → review-status-Bymwzh2i.js.map} +1 -1
- package/dist/dashboard/server.js +565 -265
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{settings-CuHV-wcv.js → settings-BHlDG7TK.js} +2 -2
- package/dist/dashboard/settings-BHlDG7TK.js.map +1 -0
- package/dist/dashboard/settings-XWvDcj-D.js +2 -0
- package/dist/dashboard/{shadow-engineering-BUeZunaE.js → shadow-engineering-lIn1W_95.js} +1 -1
- package/dist/dashboard/{shadow-engineering-BUeZunaE.js.map → shadow-engineering-lIn1W_95.js.map} +1 -1
- package/dist/dashboard/{shadow-state-DHQ-kASN.js → shadow-state-BIexcxkv.js} +1 -1
- package/dist/dashboard/{shadow-state-DHQ-kASN.js.map → shadow-state-BIexcxkv.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js → spawn-planning-session-33Jf-d5T.js} +6 -6
- package/dist/dashboard/{spawn-planning-session-8FFAqLdK.js.map → spawn-planning-session-33Jf-d5T.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-U0Lqpjen.js → spawn-planning-session-D5hrVdWM.js} +1 -1
- package/dist/dashboard/{specialist-context-ColzlmGE.js → specialist-context-DGukHSn8.js} +6 -6
- package/dist/dashboard/{specialist-context-ColzlmGE.js.map → specialist-context-DGukHSn8.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-BhmDpFIq.js → specialist-logs-CIw4qfTy.js} +1 -1
- package/dist/dashboard/{specialists-C6s3U6tX.js → specialists-B_zrayaP.js} +37 -36
- package/dist/dashboard/specialists-B_zrayaP.js.map +1 -0
- package/dist/dashboard/{specialists-Cny632-T.js → specialists-Cp-PgspS.js} +1 -1
- package/dist/dashboard/{test-agent-queue-tqI4VDsu.js → test-agent-queue-ypF_ecHo.js} +4 -4
- package/dist/dashboard/{test-agent-queue-tqI4VDsu.js.map → test-agent-queue-ypF_ecHo.js.map} +1 -1
- package/dist/dashboard/{tldr-daemon-BNFyS7W_.js → tldr-daemon-B_oLRD8z.js} +2 -2
- package/dist/dashboard/{tldr-daemon-BNFyS7W_.js.map → tldr-daemon-B_oLRD8z.js.map} +1 -1
- package/dist/dashboard/{tldr-daemon-A6JqC59u.js → tldr-daemon-Cfs0bXTi.js} +1 -1
- package/dist/dashboard/{tmux-DYGAVJfb.js → tmux-BzxdKItf.js} +1 -1
- package/dist/dashboard/{tmux-IlN1Slv-.js → tmux-LwG0tHhU.js} +2 -2
- package/dist/dashboard/{tmux-IlN1Slv-.js.map → tmux-LwG0tHhU.js.map} +1 -1
- package/dist/dashboard/{tracker-config-BzNLnmcE.js → tracker-config-BP59uH4V.js} +1 -1
- package/dist/dashboard/{tracker-config-CNM_5rEf.js → tracker-config-e7ph1QqT.js} +2 -2
- package/dist/dashboard/{tracker-config-CNM_5rEf.js.map → tracker-config-e7ph1QqT.js.map} +1 -1
- package/dist/dashboard/{tunnel-D2BkwU7k.js → tunnel-0RzzuXPf.js} +1 -1
- package/dist/dashboard/{tunnel-Dub2hiAA.js → tunnel-DldbBPWL.js} +2 -2
- package/dist/dashboard/{tunnel-Dub2hiAA.js.map → tunnel-DldbBPWL.js.map} +1 -1
- package/dist/dashboard/{types-CWA-o4UN.js → types-RKZjGE5N.js} +1 -1
- package/dist/dashboard/{types-CWA-o4UN.js.map → types-RKZjGE5N.js.map} +1 -1
- package/dist/dashboard/{vtt-parser-BAXygRf0.js → vtt-parser-99vFekRQ.js} +1 -1
- package/dist/dashboard/{vtt-parser-BAXygRf0.js.map → vtt-parser-99vFekRQ.js.map} +1 -1
- package/dist/dashboard/{work-agent-prompt-JYq_OugP.js → work-agent-prompt-fCg67nyo.js} +65 -10
- package/dist/dashboard/{work-agent-prompt-JYq_OugP.js.map → work-agent-prompt-fCg67nyo.js.map} +1 -1
- package/dist/dashboard/{work-type-router-Cxp8_ur2.js → work-type-router-CWVW2Wk_.js} +1 -1
- package/dist/dashboard/{work-type-router-Cxp8_ur2.js.map → work-type-router-CWVW2Wk_.js.map} +1 -1
- package/dist/dashboard/{work-type-router-Com2amST.js → work-type-router-Di5gCQwh.js} +1 -1
- package/dist/dashboard/{workflows-N1UTipYl.js → workflows-BSMipN07.js} +35 -17
- package/dist/dashboard/workflows-BSMipN07.js.map +1 -0
- package/dist/dashboard/workflows-DaYWQIS2.js +2 -0
- package/dist/dashboard/{workspace-config-cmp5_ipD.js → workspace-config-DVDR-Ukh.js} +1 -1
- package/dist/dashboard/workspace-config-DVDR-Ukh.js.map +1 -0
- package/dist/dashboard/{workspace-manager-CjpWPgzL.js → workspace-manager-BYfzs_t2.js} +1 -1
- package/dist/dashboard/{workspace-manager-D_y9ZmW_.js → workspace-manager-C7OfT62A.js} +44 -24
- package/dist/dashboard/workspace-manager-C7OfT62A.js.map +1 -0
- package/dist/{dns-BKzHm-2q.js → dns-D_aKQJjb.js} +1 -1
- package/dist/{dns-DZwOWvVO.js → dns-Yxq4NNS7.js} +1 -1
- package/dist/{dns-DZwOWvVO.js.map → dns-Yxq4NNS7.js.map} +1 -1
- package/dist/{factory-DFu3IT4r.js → factory-BRBGw6OB.js} +1 -1
- package/dist/{factory-DfzczxN1.js → factory-DzsOiZVc.js} +3 -3
- package/dist/{factory-DfzczxN1.js.map → factory-DzsOiZVc.js.map} +1 -1
- package/dist/{feedback-writer-CwdnOkPO.js → feedback-writer-ygXN5F9N.js} +2 -2
- package/dist/{feedback-writer-CwdnOkPO.js.map → feedback-writer-ygXN5F9N.js.map} +1 -1
- package/dist/{github-app-CHKwxOeQ.js → github-app-DykduJ0X.js} +1 -1
- package/dist/{github-app-CHKwxOeQ.js.map → github-app-DykduJ0X.js.map} +1 -1
- package/dist/hume-9nv1VmMV.js +3 -0
- package/dist/{hume-DnV-tDsh.js → hume-DoCbph2h.js} +2 -2
- package/dist/{hume-DnV-tDsh.js.map → hume-DoCbph2h.js.map} +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/issue-id-CAcekoIw.js +62 -0
- package/dist/issue-id-CAcekoIw.js.map +1 -0
- package/dist/{label-cleanup-31ElPqqv.js → label-cleanup-C8R9Rspn.js} +7 -4
- package/dist/label-cleanup-C8R9Rspn.js.map +1 -0
- package/dist/{manifest-DL0oDbpv.js → manifest-B4ghOD-V.js} +1 -1
- package/dist/{manifest-DL0oDbpv.js.map → manifest-B4ghOD-V.js.map} +1 -1
- package/dist/{merge-agent-VQH9z9t8.js → merge-agent-DlUiUanN.js} +86 -33
- package/dist/merge-agent-DlUiUanN.js.map +1 -0
- package/dist/{paths-lMaxrYtT.js → paths-CDJ_HsbN.js} +19 -2
- package/dist/{paths-lMaxrYtT.js.map → paths-CDJ_HsbN.js.map} +1 -1
- package/dist/{pipeline-notifier-OJ-d3Y60.js → pipeline-notifier-XgDdCdvT.js} +1 -1
- package/dist/{pipeline-notifier-OJ-d3Y60.js.map → pipeline-notifier-XgDdCdvT.js.map} +1 -1
- package/dist/{projects-CvLepaxC.js → projects-Bk-5QhFQ.js} +25 -13
- package/dist/projects-Bk-5QhFQ.js.map +1 -0
- package/dist/{projects-DMWmPeIU.js → projects-DhU7rAVN.js} +1 -1
- package/dist/{providers-DcCPZ5K4.js → providers-DSU1vfQF.js} +4 -4
- package/dist/providers-DSU1vfQF.js.map +1 -0
- package/dist/rally-DdPvGa-w.js +3 -0
- package/dist/{rally-uUUZXp1h.js → rally-Dy00NElU.js} +1 -1
- package/dist/{rally-uUUZXp1h.js.map → rally-Dy00NElU.js.map} +1 -1
- package/dist/{remote-CkLBqLJc.js → remote-CYiOJg0q.js} +2 -2
- package/dist/{remote-CkLBqLJc.js.map → remote-CYiOJg0q.js.map} +1 -1
- package/dist/{remote-agents-C5Bd2fgt.js → remote-agents-CZXrUF4f.js} +1 -1
- package/dist/{remote-agents-C5Bd2fgt.js.map → remote-agents-CZXrUF4f.js.map} +1 -1
- package/dist/{remote-agents-BTzD-wMQ.js → remote-agents-ycHHVsgf.js} +1 -1
- package/dist/{remote-workspace-Dxghqiti.js → remote-workspace-CA33UuVI.js} +4 -4
- package/dist/{remote-workspace-Dxghqiti.js.map → remote-workspace-CA33UuVI.js.map} +1 -1
- package/dist/{review-status-2TdtHNcs.js → review-status-D6H2WOw8.js} +1 -1
- package/dist/{review-status-Bm1bWNEa.js → review-status-DEDvCKMP.js} +44 -4
- package/dist/{review-status-Bm1bWNEa.js.map → review-status-DEDvCKMP.js.map} +1 -1
- package/dist/{tracker-C_62ukEq.js → settings-BcWPTrua.js} +7 -199
- package/dist/settings-BcWPTrua.js.map +1 -0
- package/dist/shadow-state-BZzxfEGw.js +2 -0
- package/dist/{shadow-state-CFFHf05M.js → shadow-state-CE3dQfll.js} +1 -1
- package/dist/{shadow-state-CFFHf05M.js.map → shadow-state-CE3dQfll.js.map} +1 -1
- package/dist/{specialist-context-BdNFsfMG.js → specialist-context-BAUWL1Fl.js} +6 -6
- package/dist/{specialist-context-BdNFsfMG.js.map → specialist-context-BAUWL1Fl.js.map} +1 -1
- package/dist/{specialist-logs-CLztE_bE.js → specialist-logs-DQKKQV9B.js} +1 -1
- package/dist/{specialists-aUoUVWsN.js → specialists-Bfb9ATzw.js} +1 -1
- package/dist/{specialists-DEKqgkxp.js → specialists-D7Kj5o6s.js} +35 -34
- package/dist/specialists-D7Kj5o6s.js.map +1 -0
- package/dist/sync-DMfgd389.js +693 -0
- package/dist/sync-DMfgd389.js.map +1 -0
- package/dist/sync-TL6y-8K6.js +2 -0
- package/dist/{tldr-daemon-BCEFPItr.js → tldr-daemon-CFx4LXAl.js} +2 -2
- package/dist/{tldr-daemon-BCEFPItr.js.map → tldr-daemon-CFx4LXAl.js.map} +1 -1
- package/dist/{tldr-daemon-xBAx4cBE.js → tldr-daemon-D_EooADG.js} +1 -1
- package/dist/{tmux-DN6H886Y.js → tmux-CBtui_Cl.js} +1 -1
- package/dist/{tmux-CKdNxxJx.js → tmux-D6Ah4I8z.js} +2 -2
- package/dist/{tmux-CKdNxxJx.js.map → tmux-D6Ah4I8z.js.map} +1 -1
- package/dist/tracker-BhYYvU3p.js +198 -0
- package/dist/tracker-BhYYvU3p.js.map +1 -0
- package/dist/{tracker-utils-CVU2W1sX.js → tracker-utils-ChQyut8w.js} +34 -12
- package/dist/tracker-utils-ChQyut8w.js.map +1 -0
- package/dist/{traefik-DHgBoWXX.js → traefik-C80EbDu_.js} +4 -4
- package/dist/{traefik-DHgBoWXX.js.map → traefik-C80EbDu_.js.map} +1 -1
- package/dist/{traefik-BR-edbZv.js → traefik-CgHl7Bge.js} +1 -1
- package/dist/{tunnel-BZO9Q5oe.js → tunnel-DXOJ1wMM.js} +1 -1
- package/dist/{tunnel-Bl1qNSyQ.js → tunnel-DzXEPwIc.js} +2 -2
- package/dist/{tunnel-Bl1qNSyQ.js.map → tunnel-DzXEPwIc.js.map} +1 -1
- package/dist/{types-DewGdaIP.js → types-BhJj1SP1.js} +1 -1
- package/dist/{types-DewGdaIP.js.map → types-BhJj1SP1.js.map} +1 -1
- package/dist/{work-type-router-CS2BB1vS.js → work-type-router-CHjciPyS.js} +3 -3
- package/dist/{work-type-router-CS2BB1vS.js.map → work-type-router-CHjciPyS.js.map} +1 -1
- package/dist/{workspace-config-CNXOpKuj.js → workspace-config-fUafvYMp.js} +1 -1
- package/dist/workspace-config-fUafvYMp.js.map +1 -0
- package/dist/workspace-manager-B9jS4Dsq.js +3 -0
- package/dist/{workspace-manager-CncdZkIy.js → workspace-manager-DuLhnzJV.js} +112 -27
- package/dist/workspace-manager-DuLhnzJV.js.map +1 -0
- package/package.json +2 -1
- package/scripts/post-merge-deploy.sh +25 -5
- package/scripts/record-cost-event.js +57 -7
- package/scripts/record-cost-event.js.map +1 -1
- package/skills/pan-help/SKILL.md +1 -1
- package/skills/pan-sync/SKILL.md +6 -6
- package/skills/workspace-add-repo/skill.md +46 -0
- package/templates/claude-md/sections/warnings.md +15 -2
- package/dist/clean-planning-sZXvy3Y5.js +0 -2
- package/dist/close-issue-Dml437qV.js +0 -2
- package/dist/close-issue-Dr7yZmrr.js.map +0 -1
- package/dist/compact-beads-iu218JcO.js +0 -2
- package/dist/dashboard/agent-enrichment-C67LJBgD.js.map +0 -1
- package/dist/dashboard/clean-planning-DCu3cOTu.js +0 -2
- package/dist/dashboard/close-issue-DfIggeZD.js.map +0 -1
- package/dist/dashboard/close-issue-DwdwYtar.js +0 -2
- package/dist/dashboard/compact-beads-DXY2fK2s.js +0 -2
- package/dist/dashboard/event-store-O9q0Gweh.js.map +0 -1
- package/dist/dashboard/hume-MZndNDVU.js +0 -3
- package/dist/dashboard/label-cleanup-CZEsbtq9.js.map +0 -1
- package/dist/dashboard/lifecycle-ZTYdrr2O.js +0 -7
- package/dist/dashboard/merge-agent-twroFuAh.js.map +0 -1
- package/dist/dashboard/projects-Cq3TWdPS.js.map +0 -1
- package/dist/dashboard/providers-Ck2sQd_F.js.map +0 -1
- package/dist/dashboard/public/assets/index-CpSmB2ts.css +0 -1
- package/dist/dashboard/public/assets/index-yarWhi0M.js +0 -214
- package/dist/dashboard/rally-CQ1OBJrJ.js +0 -3
- package/dist/dashboard/settings-CuHV-wcv.js.map +0 -1
- package/dist/dashboard/settings-DMeGBRsk.js +0 -2
- package/dist/dashboard/specialists-C6s3U6tX.js.map +0 -1
- package/dist/dashboard/workflows-B2ARUpOa.js +0 -2
- package/dist/dashboard/workflows-N1UTipYl.js.map +0 -1
- package/dist/dashboard/workspace-config-cmp5_ipD.js.map +0 -1
- package/dist/dashboard/workspace-manager-D_y9ZmW_.js.map +0 -1
- package/dist/hume-BjmwmJ9E.js +0 -3
- package/dist/label-cleanup-31ElPqqv.js.map +0 -1
- package/dist/merge-agent-VQH9z9t8.js.map +0 -1
- package/dist/projects-CvLepaxC.js.map +0 -1
- package/dist/providers-DcCPZ5K4.js.map +0 -1
- package/dist/rally-DR9x8--6.js +0 -3
- package/dist/shadow-state-p3jpGRPJ.js +0 -2
- package/dist/specialists-DEKqgkxp.js.map +0 -1
- package/dist/tracker-C_62ukEq.js.map +0 -1
- package/dist/tracker-utils-CVU2W1sX.js.map +0 -1
- package/dist/workspace-config-CNXOpKuj.js.map +0 -1
- package/dist/workspace-manager-CncdZkIy.js.map +0 -1
- package/dist/workspace-manager-Cx0r2Jnv.js +0 -3
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"merge-agent-twroFuAh.js","names":["execAsync"],"sources":["../../src/lib/tracker-utils.ts","../../src/lib/cloister/validation.ts","../../src/lib/cloister/merge-agent.ts"],"sourcesContent":["/**\n * Shared tracker utilities for resolving issue IDs to their tracker type\n * (GitHub or Linear) based on GITHUB_REPOS configuration.\n *\n * Eliminates hardcoded prefix checks like `issueId.startsWith('PAN-')`.\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { loadProjectsConfig, getIssuePrefix } from './projects.js';\n\nexport interface GitHubRepoConfig {\n owner: string;\n repo: string;\n prefix: string;\n}\n\nexport interface GitHubIssueResolution {\n isGitHub: true;\n owner: string;\n repo: string;\n prefix: string;\n number: number;\n}\n\nexport interface NonGitHubResolution {\n isGitHub: false;\n}\n\nexport type IssueResolution = GitHubIssueResolution | NonGitHubResolution;\n\n/**\n * Parse GitHub repos from GITHUB_REPOS env var and projects.yaml.\n * Priority: GITHUB_REPOS env var first, then auto-derive from projects.yaml.\n * Format for env var: \"owner/repo:PREFIX,owner2/repo2:PREFIX2\"\n */\nexport function parseGitHubRepos(): GitHubRepoConfig[] {\n const repos: GitHubRepoConfig[] = [];\n\n // 1. Check GITHUB_REPOS env var\n const envFile = join(homedir(), '.panopticon.env');\n if (existsSync(envFile)) {\n const content = readFileSync(envFile, 'utf-8');\n const reposMatch = content.match(/GITHUB_REPOS=(.+)/);\n if (reposMatch) {\n repos.push(...reposMatch[1].trim().split(',').map(r => {\n const [repoPath, prefix] = r.trim().split(':');\n const [owner, repo] = (repoPath || '').split('/');\n return { owner: owner || '', repo: repo || '', prefix: (prefix || '').toUpperCase() };\n }).filter(r => r.owner && r.repo && r.prefix));\n }\n }\n\n // 2. Auto-derive from projects.yaml (if no explicit GITHUB_REPOS)\n if (repos.length === 0) {\n try {\n const { projects } = loadProjectsConfig();\n for (const [key, project] of Object.entries(projects)) {\n if (project.github_repo) {\n const [owner, repo] = project.github_repo.split('/');\n // Derive prefix: linear_team if set, otherwise uppercase project key\n const prefix = getIssuePrefix(project) || key.toUpperCase().replace(/-/g, '');\n if (owner && repo && prefix) {\n repos.push({ owner, repo, prefix: prefix.toUpperCase() });\n }\n }\n }\n } catch { /* ignore */ }\n }\n\n return repos;\n}\n\n/**\n * Extract the prefix from an issue ID (e.g., \"CLI\" from \"CLI-1\", \"PAN\" from \"PAN-42\").\n */\nexport function extractIssuePrefix(issueId: string): string {\n return issueId.split('-')[0].toUpperCase();\n}\n\n/**\n * Resolve an issue ID to its GitHub repo config, or determine it's not a GitHub issue.\n *\n * Checks the issue prefix against all prefixes configured in GITHUB_REPOS.\n * Returns the matching repo config with parsed issue number, or { isGitHub: false }.\n */\nexport function resolveGitHubIssue(issueId: string): IssueResolution {\n const prefix = extractIssuePrefix(issueId);\n const repos = parseGitHubRepos();\n\n for (const repoConfig of repos) {\n if (repoConfig.prefix === prefix) {\n const number = parseInt(issueId.split('-')[1], 10);\n if (!isNaN(number)) {\n return { isGitHub: true, ...repoConfig, number };\n }\n }\n }\n\n return { isGitHub: false };\n}\n\n/**\n * Check if an issue ID belongs to a GitHub-tracked project.\n */\nexport function isGitHubIssue(issueId: string): boolean {\n return resolveGitHubIssue(issueId).isGitHub;\n}\n\nexport type TrackerTypeResolution = 'github' | 'rally' | 'linear';\n\n/**\n * Resolve the tracker type for an issue ID by checking projects.yaml configuration.\n *\n * Resolution order:\n * 1. GitHub — prefix matches a configured github_repo project\n * 2. Rally — prefix matches a project with rally_project but no linear_team / github_repo\n * 3. Linear — fallback (matches linear_team or unknown prefix)\n */\nexport function resolveTrackerType(issueId: string): TrackerTypeResolution {\n // Check GitHub first (existing logic)\n if (resolveGitHubIssue(issueId).isGitHub) {\n return 'github';\n }\n\n // Check if the issue prefix matches a Rally-only project\n const prefix = extractIssuePrefix(issueId);\n try {\n const { projects } = loadProjectsConfig();\n for (const [key, project] of Object.entries(projects)) {\n const projectPrefix = getIssuePrefix(project) || key.toUpperCase().replace(/-/g, '');\n if (projectPrefix?.toUpperCase() === prefix) {\n // Prefix matches — determine tracker by what's configured\n if (project.github_repo) return 'github';\n if (project.rally_project) return 'rally';\n return 'linear';\n }\n }\n } catch { /* ignore config errors */ }\n\n // Default to Linear for unknown prefixes\n return 'linear';\n}\n","/**\n * Merge Validation - Validation utilities for merge completeness\n *\n * Validates that merged code:\n * - Has no conflict markers\n * - Builds successfully\n * - Passes all tests\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\nimport type { QualityGateConfig, TemplatePlaceholders } from '../workspace-config.js';\nimport { replacePlaceholders } from '../workspace-config.js';\nimport { loadConfig } from '../config.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Context for validation execution\n */\nexport interface ValidationContext {\n /** Project root path */\n projectPath: string;\n /** Issue ID for logging */\n issueId?: string;\n /** Custom validation script path (defaults to scripts/validate-merge.sh) */\n validationScript?: string;\n /** Baseline test failure count for comparison mode (pre-existing failures) */\n baselineTestFailures?: number;\n}\n\n/**\n * Detailed validation failure information\n */\nexport interface ValidationFailure {\n /** Type of failure: conflict, build, or test */\n type: 'conflict' | 'build' | 'test';\n /** Files affected (for conflicts) */\n files?: string[];\n /** Error message or output */\n message: string;\n}\n\n/**\n * Result of validation execution\n */\nexport interface ValidationResult {\n /** Overall validation success */\n success: boolean;\n /** Validation passed (or skipped — check `skipped` to distinguish) */\n valid: boolean;\n /** Validation was skipped (no validation script found) */\n skipped?: boolean;\n /** Conflict markers detected */\n conflictMarkersFound: boolean;\n /** Build result */\n buildPassed: boolean | null; // null if not run\n /** Test result */\n testsPassed: boolean | null; // null if not run\n /** List of failures */\n failures: ValidationFailure[];\n /** Raw validation output */\n output: string;\n /** Error message if validation script itself failed */\n error?: string;\n}\n\n/**\n * Parse validation script output to extract structured results\n */\nfunction parseValidationOutput(output: string, exitCode: number): ValidationResult {\n const lines = output.split('\\n');\n\n const failures: ValidationFailure[] = [];\n let conflictMarkersFound = false;\n let buildPassed: boolean | null = null;\n let testsPassed: boolean | null = null;\n\n // Track what stage we're in\n let inConflictCheck = false;\n let inBuildCheck = false;\n let inTestCheck = false;\n\n const conflictFiles: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Detect stages\n if (trimmed.startsWith('Checking for conflict markers')) {\n inConflictCheck = true;\n inBuildCheck = false;\n inTestCheck = false;\n } else if (trimmed.startsWith('Running build')) {\n inConflictCheck = false;\n inBuildCheck = true;\n inTestCheck = false;\n } else if (trimmed.startsWith('Running tests')) {\n inConflictCheck = false;\n inBuildCheck = false;\n inTestCheck = true;\n }\n\n // Parse conflict markers\n if (inConflictCheck) {\n if (trimmed.startsWith('ERROR: Conflict')) {\n conflictMarkersFound = true;\n } else if (trimmed.includes('/') && !trimmed.startsWith('ERROR')) {\n // File path listed\n conflictFiles.push(trimmed);\n } else if (trimmed.startsWith('✓ No conflict markers found')) {\n conflictMarkersFound = false;\n }\n }\n\n // Parse build result\n if (inBuildCheck) {\n if (trimmed.startsWith('✓ Build passed')) {\n buildPassed = true;\n } else if (trimmed.startsWith('ERROR: Build failed') ||\n trimmed.includes('VALIDATION FAILED: Build errors detected')) {\n buildPassed = false;\n } else if (trimmed.includes('skipping build check')) {\n buildPassed = null; // Not applicable\n }\n }\n\n // Parse test result\n if (inTestCheck) {\n if (trimmed.startsWith('✓ Tests passed')) {\n testsPassed = true;\n } else if (trimmed.startsWith('ERROR: Tests failed') ||\n trimmed.includes('VALIDATION FAILED: Test failures detected')) {\n testsPassed = false;\n } else if (trimmed.includes('skipping test check')) {\n testsPassed = null; // Not applicable\n }\n }\n }\n\n // Build failures list\n if (conflictMarkersFound) {\n failures.push({\n type: 'conflict',\n files: conflictFiles.length > 0 ? conflictFiles : undefined,\n message: 'Conflict markers detected in merged code',\n });\n }\n\n if (buildPassed === false) {\n failures.push({\n type: 'build',\n message: 'Build failed after merge',\n });\n }\n\n if (testsPassed === false) {\n failures.push({\n type: 'test',\n message: 'Tests failed after merge',\n });\n }\n\n // Determine overall validity\n const valid = exitCode === 0 &&\n !conflictMarkersFound &&\n (buildPassed === null || buildPassed === true) &&\n (testsPassed === null || testsPassed === true);\n\n return {\n success: true, // Script ran successfully\n valid,\n conflictMarkersFound,\n buildPassed,\n testsPassed,\n failures,\n output,\n };\n}\n\n/**\n * Run merge validation on a project\n *\n * @param context - Validation context\n * @returns Promise resolving to validation result\n */\nexport async function runMergeValidation(\n context: ValidationContext\n): Promise<ValidationResult> {\n const { projectPath, validationScript } = context;\n\n // Determine validation script path\n const scriptPath = validationScript || join(projectPath, 'scripts', 'validate-merge.sh');\n\n // No validation script = skip validation (specialist already ran build + tests)\n if (!existsSync(scriptPath)) {\n console.log(`[validation] No validation script at ${scriptPath}, skipping (specialist already validated)`);\n return {\n success: true,\n valid: true,\n skipped: true,\n conflictMarkersFound: false,\n buildPassed: null,\n testsPassed: null,\n failures: [],\n output: '',\n };\n }\n\n console.log(`[validation] Running validation script: ${scriptPath}`);\n console.log(`[validation] Project path: ${projectPath}`);\n\n try {\n // Run validation script\n // Pass baseline failures as env var for baseline comparison mode\n const env = { ...process.env };\n if (context.baselineTestFailures !== undefined) {\n env.BASELINE_FAILURES = String(context.baselineTestFailures);\n console.log(`[validation] Baseline comparison mode: ${context.baselineTestFailures} pre-existing failures`);\n }\n\n const { stdout, stderr } = await execAsync(\n `bash \"${scriptPath}\" \"${projectPath}\"`,\n {\n cwd: projectPath,\n env,\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs\n timeout: 10 * 60 * 1000, // 10 minute timeout\n }\n );\n\n const output = stdout + stderr;\n\n console.log(`[validation] ✓ Validation passed`);\n\n return parseValidationOutput(output, 0);\n } catch (error: any) {\n // Validation script exited with non-zero code (validation failed)\n const exitCode = error.code || 1;\n const output = (error.stdout || '') + (error.stderr || '');\n\n console.log(`[validation] ✗ Validation failed (exit code ${exitCode})`);\n\n // Parse the output to understand what failed\n const result = parseValidationOutput(output, exitCode);\n\n return result;\n }\n}\n\n/**\n * Auto-revert a merge if validation fails\n *\n * Uses ORIG_HEAD which git sets automatically at merge time to the commit\n * HEAD pointed to right before the merge. This is always correct regardless\n * of commits added between task start and merge execution.\n *\n * @param projectPath - Project root path\n * @returns Promise resolving to success status\n */\nexport async function autoRevertMerge(projectPath: string): Promise<boolean> {\n console.log(`[validation] Auto-reverting merge in ${projectPath}`);\n\n try {\n // Get current commit before revert (for logging)\n const { stdout: beforeCommit } = await execAsync('git rev-parse HEAD', {\n cwd: projectPath,\n });\n\n // Use ORIG_HEAD — git sets this to pre-merge HEAD at merge time.\n // Handles fast-forwards, multi-commit merges, and any commits\n // added to main between task start and merge execution.\n await execAsync('git reset --hard ORIG_HEAD', {\n cwd: projectPath,\n });\n\n // Get new HEAD after revert (for logging)\n const { stdout: afterCommit } = await execAsync('git rev-parse HEAD', {\n cwd: projectPath,\n });\n\n console.log(\n `[validation] ✓ Auto-revert successful: ${beforeCommit.trim()} -> ${afterCommit.trim()} (via ORIG_HEAD)`\n );\n\n return true;\n } catch (error: any) {\n console.error(`[validation] ✗ Auto-revert failed:`, error.message);\n return false;\n }\n}\n\n/**\n * Result of a single quality gate execution\n */\nexport interface QualityGateResult {\n /** Gate name from projects.yaml */\n name: string;\n /** Whether the gate passed */\n passed: boolean;\n /** Whether the gate was required */\n required: boolean;\n /** Gate output (stdout + stderr) */\n output: string;\n /** Duration in milliseconds */\n durationMs: number;\n /** Error message if gate failed */\n error?: string;\n}\n\n/**\n * Options for running quality gates\n */\nexport interface QualityGateRunOptions {\n /** Whether the workspace is remote (SSH) */\n isRemote?: boolean;\n /** VM name for SSH connections (required when isRemote is true) */\n vmName?: string;\n /** Template placeholders for resolving container names (e.g., {{FEATURE_FOLDER}}) */\n placeholders?: TemplatePlaceholders;\n}\n\n/**\n * Default quality gates used when no quality_gates config exists in projects.yaml.\n * Runs typecheck → lint → test sequentially (bail on first failure).\n */\nexport const DEFAULT_GATES: Record<string, QualityGateConfig> = {\n typecheck: { command: 'npm run typecheck 2>&1' },\n lint: { command: 'npm run lint 2>&1' },\n test: { command: 'npm test 2>&1' },\n};\n\n/**\n * Run all quality gates for a project\n *\n * Executes each gate in declaration order, stopping on first required failure.\n * Returns results for all gates that were run.\n *\n * Supports both local and remote (SSH) workspaces. For remote workspaces,\n * commands are wrapped with SSH and run on the specified VM.\n *\n * @param gates - Quality gate configs from projects.yaml (or DEFAULT_GATES)\n * @param projectPath - Project root (or workspace root)\n * @param phase - Which phase to run ('pre_push' or 'post_push')\n * @param opts - Optional remote workspace options\n * @returns Array of gate results\n */\nexport async function runQualityGates(\n gates: Record<string, QualityGateConfig>,\n projectPath: string,\n phase: 'pre_push' | 'post_push' = 'pre_push',\n opts: QualityGateRunOptions = {}\n): Promise<QualityGateResult[]> {\n if (opts.isRemote && !opts.vmName) {\n throw new Error('Remote workspace requires vmName');\n }\n if (opts.isRemote && opts.vmName) {\n // Validate vmName and projectPath to prevent shell injection.\n // Both are controlled by Panopticon config, but explicit validation\n // catches any accidental or malicious values before they reach the shell.\n if (!/^[a-z0-9][a-z0-9-]*$/.test(opts.vmName)) {\n throw new Error(`Invalid vmName for SSH: ${opts.vmName}`);\n }\n if (!/^[a-zA-Z0-9/_\\-.]+$/.test(projectPath)) {\n throw new Error(`Workspace path contains unsafe characters: ${projectPath}`);\n }\n }\n const results: QualityGateResult[] = [];\n\n for (const [name, gate] of Object.entries(gates)) {\n const gatePhase = gate.phase || 'pre_push';\n if (gatePhase !== phase) continue;\n\n const required = gate.required !== false; // default true\n const cwd = gate.path ? join(projectPath, gate.path) : projectPath;\n\n console.log(`[quality-gate] Running \"${name}\" (${required ? 'required' : 'optional'}) in ${cwd}`);\n const startTime = Date.now();\n\n if (gate.type === 'http_health') {\n // HTTP health check gate\n const result = await runHttpHealthGate(name, gate, required);\n results.push(result);\n if (!result.passed && required) {\n console.log(`[quality-gate] ✗ Required gate \"${name}\" failed — stopping`);\n break;\n }\n continue;\n }\n\n // Command gate (default)\n\n // For remote workspaces, build and validate the SSH command BEFORE entering\n // the try/catch so validation errors propagate as real errors (not gate failures).\n const isRemote = opts.isRemote && opts.vmName;\n let resolvedCommand: string;\n if (isRemote) {\n // Validate cwd (which may include gate.path) — not just the base projectPath.\n // A gate.path like \"frontend;rm -rf /\" would produce an unsafe cwd after join.\n if (!/^[a-zA-Z0-9/_\\-.]+$/.test(cwd)) {\n throw new Error(`Gate \"${name}\" path resolves to unsafe characters for SSH: ${cwd}`);\n }\n // Validate gate.command doesn't contain double quotes — a \" in the command would\n // end the SSH double-quoted string and allow local command injection:\n // ssh host \"cd /path && legit; injected\" ← breaks when command contains \"\n if (gate.command.includes('\"')) {\n throw new Error(`Gate \"${name}\" command contains double quotes which are unsafe in SSH context`);\n }\n const flyAppName = loadConfig().remote?.fly?.app ?? 'pan-workspaces';\n resolvedCommand = `fly ssh console -a ${flyAppName} -C \"cd ${cwd} && ${gate.command}\"`;\n } else if (gate.container && gate.container_name) {\n // Run inside Docker container — resolve container name from placeholders\n let containerName = gate.container_name;\n if (opts.placeholders) {\n containerName = replacePlaceholders(containerName, opts.placeholders);\n }\n // Use -w to set working directory inside the container.\n // The container mounts workspace code at /workspaces/feature/<subdir>,\n // so map the gate.path (e.g., 'fe') to the container's working directory.\n const containerWorkdir = gate.path ? `/workspaces/feature/${gate.path}` : '/workspaces/feature';\n // Pass gate.env as -e flags so env vars reach the container process\n const envFlags = gate.env\n ? Object.entries(gate.env).map(([k, v]) => `-e ${k}=\"${v}\"`).join(' ')\n : '';\n resolvedCommand = `docker exec ${envFlags} -w \"${containerWorkdir}\" \"${containerName}\" ${gate.command}`;\n console.log(`[quality-gate] Running in container: ${containerName} (workdir: ${containerWorkdir})`);\n } else {\n resolvedCommand = gate.command;\n }\n\n try {\n // When running in container, don't set host cwd (irrelevant)\n const useHostCwd = !isRemote && !(gate.container && gate.container_name);\n const env = { ...process.env, ...gate.env };\n const { stdout, stderr } = await execAsync(resolvedCommand, {\n cwd: useHostCwd ? cwd : undefined,\n env,\n maxBuffer: 10 * 1024 * 1024, // 10MB\n timeout: 5 * 60 * 1000, // 5 minute timeout per gate\n });\n\n const durationMs = Date.now() - startTime;\n console.log(`[quality-gate] ✓ \"${name}\" passed (${durationMs}ms)`);\n results.push({\n name,\n passed: true,\n required,\n output: (stdout + stderr).slice(-2000), // keep last 2KB\n durationMs,\n });\n } catch (error: any) {\n const durationMs = Date.now() - startTime;\n const output = ((error.stdout || '') + (error.stderr || '')).slice(-2000);\n console.log(`[quality-gate] ✗ \"${name}\" failed (${durationMs}ms): ${error.message?.slice(0, 200)}`);\n results.push({\n name,\n passed: false,\n required,\n output,\n durationMs,\n error: error.message?.slice(0, 500),\n });\n\n if (required) {\n console.log(`[quality-gate] ✗ Required gate \"${name}\" failed — stopping`);\n break;\n }\n }\n }\n\n const passed = results.filter(r => r.passed).length;\n const failed = results.filter(r => !r.passed).length;\n console.log(`[quality-gate] Complete: ${passed} passed, ${failed} failed out of ${results.length} gates`);\n\n return results;\n}\n\n/**\n * Run an HTTP health check gate (for post-push deployment verification)\n */\nasync function runHttpHealthGate(\n name: string,\n gate: QualityGateConfig,\n required: boolean\n): Promise<QualityGateResult> {\n const url = gate.url;\n if (!url) {\n return {\n name,\n passed: false,\n required,\n output: '',\n durationMs: 0,\n error: 'http_health gate missing url',\n };\n }\n\n const waitSeconds = gate.wait || 120;\n const expectStatus = gate.expect_status || 200;\n const startTime = Date.now();\n\n console.log(`[quality-gate] Waiting ${waitSeconds}s for deployment, then checking ${url}`);\n\n // Wait for deployment\n await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));\n\n try {\n const { stdout } = await execAsync(\n `curl -sL -o /dev/null -w '%{http_code}' --max-time 30 '${url}'`,\n { timeout: 60 * 1000 }\n );\n\n const statusCode = parseInt(stdout.trim(), 10);\n const passed = statusCode === expectStatus;\n const durationMs = Date.now() - startTime;\n\n console.log(`[quality-gate] Health check ${url}: ${statusCode} (expected ${expectStatus}) — ${passed ? 'PASS' : 'FAIL'}`);\n\n return {\n name,\n passed,\n required,\n output: `HTTP ${statusCode} from ${url}`,\n durationMs,\n error: passed ? undefined : `Expected HTTP ${expectStatus}, got ${statusCode}`,\n };\n } catch (error: any) {\n return {\n name,\n passed: false,\n required,\n output: error.message || '',\n durationMs: Date.now() - startTime,\n error: `Health check failed: ${error.message?.slice(0, 200)}`,\n };\n }\n}\n","/**\n * Merge Agent - Automatic merge conflict resolution using Claude Code\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'fs';\nimport { writeFile } from 'fs/promises';\nimport { join, dirname, basename, relative } from 'path';\nimport { fileURLToPath } from 'url';\nimport { spawn, exec } from 'child_process';\nimport { promisify } from 'util';\nimport { sendKeysAsync, sessionExists } from '../tmux.js';\n\nconst execAsync = promisify(exec);\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nimport {\n PANOPTICON_HOME,\n} from '../paths.js';\nimport { resolveGitHubIssue } from '../tracker-utils.js';\n\nimport {\n getSessionId,\n recordWake,\n getTmuxSessionName,\n wakeSpecialist,\n spawnEphemeralSpecialist,\n isRunning,\n} from './specialists.js';\nimport { resolveProjectFromIssue } from '../projects.js';\nimport { runMergeValidation, autoRevertMerge, runQualityGates } from './validation.js';\nimport { loadProjectsConfig } from '../projects.js';\nimport { cleanupStaleLocks } from '../git-utils.js';\n\nconst SPECIALISTS_DIR = join(PANOPTICON_HOME, 'specialists');\nconst MERGE_HISTORY_DIR = join(SPECIALISTS_DIR, 'merge-agent');\nconst MERGE_HISTORY_FILE = join(MERGE_HISTORY_DIR, 'history.jsonl');\n\n/**\n * Context for a merge conflict resolution request\n */\nexport interface MergeConflictContext {\n projectPath: string;\n sourceBranch: string;\n targetBranch: string;\n conflictFiles: string[];\n issueId: string;\n testCommand?: string;\n}\n\n/**\n * Result of merge agent execution\n */\nexport interface MergeResult {\n success: boolean;\n resolvedFiles?: string[];\n failedFiles?: string[];\n testsStatus?: 'PASS' | 'FAIL' | 'SKIP';\n validationStatus?: 'PASS' | 'FAIL' | 'NOT_RUN';\n reason?: string;\n notes?: string;\n output?: string;\n}\n\n/**\n * Merge history entry\n */\ninterface MergeHistoryEntry {\n timestamp: string;\n issueId: string;\n sourceBranch: string;\n targetBranch: string;\n conflictFiles: string[];\n result: MergeResult;\n sessionId?: string;\n}\n\n/**\n * Timeout for merge agent in milliseconds (15 minutes)\n */\nconst MERGE_TIMEOUT_MS = 15 * 60 * 1000;\n\n/**\n * Build the prompt for merge-agent\n */\nfunction buildMergePrompt(context: MergeConflictContext): string {\n const templatePath = join(__dirname, 'prompts', 'merge-agent.md');\n\n if (!existsSync(templatePath)) {\n throw new Error(`Merge agent prompt template not found at ${templatePath}`);\n }\n\n const template = readFileSync(templatePath, 'utf-8');\n\n // Replace template variables\n const prompt = template\n .replace(/\\{\\{projectPath\\}\\}/g, context.projectPath)\n .replace(/\\{\\{sourceBranch\\}\\}/g, context.sourceBranch)\n .replace(/\\{\\{targetBranch\\}\\}/g, context.targetBranch)\n .replace(/\\{\\{issueId\\}\\}/g, context.issueId)\n .replace(\n /\\{\\{conflictFiles\\}\\}/g,\n context.conflictFiles.map((f) => ` - ${f}`).join('\\n')\n )\n .replace(/\\{\\{testCommand\\}\\}/g, context.testCommand || 'skip')\n .replace(/\\{\\{apiUrl\\}\\}/g, process.env.DASHBOARD_URL || `http://localhost:${process.env.API_PORT || process.env.PORT || '3011'}`);\n\n // Wrap in orchestration markers for context delineation\n return `<!-- panopticon:orchestration-context-start -->\\n${prompt}\\n<!-- panopticon:orchestration-context-end -->`;\n}\n\n/**\n * Detect test command from project structure\n */\nfunction detectTestCommand(projectPath: string): string {\n // Check for package.json (Node.js)\n const packageJsonPath = join(projectPath, 'package.json');\n if (existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n if (packageJson.scripts?.test) {\n return 'npm test';\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n // Check for pom.xml (Java/Maven)\n if (existsSync(join(projectPath, 'pom.xml'))) {\n return 'mvn test';\n }\n\n // Check for Cargo.toml (Rust)\n if (existsSync(join(projectPath, 'Cargo.toml'))) {\n return 'cargo test';\n }\n\n // Check for pytest (Python)\n if (existsSync(join(projectPath, 'pytest.ini')) || existsSync(join(projectPath, 'setup.py'))) {\n return 'pytest';\n }\n\n // No test command detected\n return 'skip';\n}\n\n/**\n * Notify TLDR daemon to reindex changed files after merge\n */\nexport async function notifyTldrDaemon(projectPath: string, sourceBranch: string): Promise<void> {\n try {\n console.log(`[merge-agent] Notifying TLDR daemon to reindex changed files...`);\n\n // Check if TLDR daemon is available\n const venvPath = join(projectPath, '.venv');\n if (!existsSync(venvPath)) {\n console.log(`[merge-agent] No .venv found, skipping TLDR notification`);\n return;\n }\n\n // Get changed files from the merge\n const { stdout } = await execAsync(`git diff --name-only HEAD~1 HEAD`, {\n cwd: projectPath,\n encoding: 'utf-8'\n });\n\n const changedFiles = stdout\n .trim()\n .split('\\n')\n .filter(f => f.trim().length > 0)\n .filter(f => {\n // Only include source code files (skip docs, configs, etc)\n const ext = f.split('.').pop()?.toLowerCase();\n return ext && ['ts', 'js', 'tsx', 'jsx', 'py', 'java', 'go', 'rs', 'cpp', 'c', 'h'].includes(ext);\n });\n\n if (changedFiles.length === 0) {\n console.log(`[merge-agent] No source files changed, skipping TLDR notification`);\n return;\n }\n\n console.log(`[merge-agent] Found ${changedFiles.length} changed source files to reindex`);\n\n // Get TLDR daemon service\n const { getTldrDaemonService } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonService(projectPath, venvPath);\n\n // Check if daemon is running\n const status = await tldrService.getStatus();\n if (!status.running) {\n console.log(`[merge-agent] TLDR daemon not running, skipping notification`);\n return;\n }\n\n // Trigger warm to reindex (this will update the index incrementally)\n console.log(`[merge-agent] Triggering TLDR index warm...`);\n await tldrService.warm(true); // background mode\n\n console.log(`[merge-agent] ✓ TLDR daemon notified to reindex`);\n logActivity('tldr_notified', `Notified TLDR daemon to reindex ${changedFiles.length} files`);\n } catch (error: any) {\n // Non-fatal - log warning and continue\n console.warn(`[merge-agent] Failed to notify TLDR daemon: ${error.message}`);\n logActivity('tldr_notify_error', `TLDR notification failed: ${error.message}`);\n }\n}\n\n/**\n * Post-merge cleanup: move PRD, close PR, move issue to Done, report merge, compact beads.\n *\n * Moves the issue to Done on the tracker so it appears in the Done column.\n * Does NOT tear down the workspace or apply the closed-out label — the human\n * close-out ceremony handles that separately.\n *\n * IDEMPOTENT: Safe to call multiple times for the same issueId. Tracks completed\n * issues and returns immediately on re-entry. This is defense-in-depth against\n * the infinite loop that burned 24,626 Linear API calls (PAN-328).\n */\n\n// Defense-in-depth: track issues that have completed postMergeLifecycle.\n// Prevents re-execution even if caller guards fail. Persists for server lifetime.\nconst _completedPostMerge = new Set<string>();\n\n// Circuit breaker for issue tracker close operations.\n// After MAX_CLOSE_RETRIES consecutive failures, stop trying to close the issue\n// on the tracker. The issue can be closed manually via the dashboard close-out ceremony.\nconst _closeIssueFailures = new Map<string, number>();\nconst MAX_CLOSE_RETRIES = 3;\n\nexport async function postMergeLifecycle(issueId: string, projectPath: string, sourceBranch?: string, options?: { skipDeploy?: boolean }): Promise<void> {\n // Guard 1: skip if already completed (defense-in-depth against infinite loops)\n if (_completedPostMerge.has(issueId)) {\n console.log(`[merge-agent] postMergeLifecycle already completed for ${issueId}, skipping`);\n return;\n }\n\n // Step 0: Write pending lifecycle file and spawn detached deploy script.\n // The deploy script rebuilds dist/, kills this server, and starts a fresh process.\n // The fresh process reads the pending file on startup and runs the lifecycle steps\n // with correct module chunk references (no ERR_MODULE_NOT_FOUND after merge).\n //\n // Skip this step when we ARE the fresh process (called from processPendingLifecycle) —\n // dynamic imports already resolve correctly and spawning again would create an infinite loop.\n if (!options?.skipDeploy) {\n const pendingFile = join(PANOPTICON_HOME, 'pending-post-merge.json');\n const repoRoot = __dirname.includes('/src/')\n ? __dirname.replace(/\\/src\\/.*$/, '')\n : __dirname.replace(/\\/dist\\/.*$/, '').replace(/\\/lib\\/.*$/, '');\n const deployScript = join(repoRoot, 'scripts', 'post-merge-deploy.sh');\n\n try {\n const pendingData = JSON.stringify({\n issueId,\n projectPath,\n sourceBranch: sourceBranch ?? '',\n timestamp: Date.now(),\n });\n await writeFile(pendingFile, pendingData, 'utf-8');\n console.log(`[merge-agent] Wrote pending lifecycle file: ${pendingFile}`);\n\n const child = spawn(deployScript, [repoRoot, issueId, projectPath, sourceBranch ?? ''], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n console.log(`[merge-agent] Spawned detached deploy script (pid ${child.pid}) — server will restart with new build`);\n return;\n } catch (err: any) {\n console.warn(`[merge-agent] Failed to spawn deploy script: ${err.message}. Falling through to in-process lifecycle (may fail on stale chunks).`);\n }\n }\n\n console.log(`[merge-agent] Running post-merge cleanup for ${issueId}`);\n\n // 1. Move PRD from active to completed (via lifecycle module)\n try {\n const { movePrd } = await import('../lifecycle/archive-planning.js');\n const prdResult = await movePrd({ issueId, projectPath });\n if (prdResult.success && !prdResult.skipped) {\n console.log(`[merge-agent] ✓ ${prdResult.details?.join('; ')}`);\n logActivity('prd_moved', `Moved ${issueId} PRD to completed directory`);\n } else if (prdResult.skipped) {\n console.log(`[merge-agent] PRD move skipped: ${prdResult.details?.join('; ')}`);\n } else {\n console.warn(`[merge-agent] PRD move failed: ${prdResult.error}`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not move PRD: ${err}`);\n }\n\n // 2. Remove ephemeral planning artifacts from main (via lifecycle module)\n try {\n const { cleanPlanningArtifacts } = await import('../lifecycle/clean-planning.js');\n const cleanResult = await cleanPlanningArtifacts({ issueId, projectPath });\n if (cleanResult.success && !cleanResult.skipped) {\n console.log(`[merge-agent] ✓ ${cleanResult.details?.join('; ')}`);\n logActivity('planning_artifacts_cleaned', cleanResult.details?.join('; ') || 'Planning artifacts removed');\n } else if (cleanResult.skipped) {\n console.log(`[merge-agent] Planning artifact cleanup skipped: ${cleanResult.details?.join('; ')}`);\n } else {\n console.warn(`[merge-agent] Planning artifact cleanup failed: ${cleanResult.error}`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not clean planning artifacts: ${err}`);\n }\n\n // 3. Clean up workflow labels + apply 'merged' label (non-fatal)\n // MUST run BEFORE closing the issue — once closed on GitHub, label edits fail silently.\n // This was the root cause of in-review labels persisting after merge (PAN-453 incident).\n try {\n const { cleanupMergedLabels } = await import('../lifecycle/label-cleanup.js');\n const ghResolved = resolveGitHubIssue(issueId);\n const labelCtx = ghResolved.isGitHub\n ? { issueId, projectPath, github: { owner: ghResolved.owner, repo: ghResolved.repo, number: ghResolved.number } }\n : { issueId, projectPath };\n const labelResult = await cleanupMergedLabels(labelCtx);\n if (labelResult.success && !labelResult.skipped) {\n console.log(`[merge-agent] ✓ ${labelResult.details?.join('; ')}`);\n logActivity('labels_cleaned', labelResult.details?.join('; ') || 'Labels cleaned');\n } else if (labelResult.skipped) {\n console.log(`[merge-agent] Label cleanup skipped: ${labelResult.details?.join('; ')}`);\n } else {\n console.warn(`[merge-agent] Label cleanup failed (non-fatal): ${labelResult.error}`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not clean labels: ${err}`);\n }\n\n // 3b. Close issue on tracker (fire-and-forget with circuit breaker)\n // This is decoupled from the merge lifecycle: failure to close the issue on the\n // tracker does NOT block the merge or cause retries. The close-out ceremony handles\n // any issues that weren't auto-closed.\n closeIssueWithCircuitBreaker(issueId, projectPath);\n\n // 4. Compact old beads (via lifecycle module)\n try {\n const { compactBeads } = await import('../lifecycle/compact-beads.js');\n const beadsResult = await compactBeads({ issueId, projectPath });\n if (beadsResult.success && !beadsResult.skipped) {\n console.log(`[merge-agent] ✓ ${beadsResult.details?.join('; ')}`);\n logActivity('beads_compaction_complete', beadsResult.details?.join('; ') || 'Beads compacted');\n }\n } catch (err) {\n console.warn(`[merge-agent] Beads compaction failed: ${err}`);\n }\n\n // 5. Kill work agent tmux session to free resources (non-fatal)\n // Stopped agents with live tmux sessions leak memory (Claude + MCP processes stay resident)\n try {\n const { getAgentState, saveAgentState } = await import('../agents.js');\n const { killSession, sessionExists } = await import('../tmux.js');\n const agentId = `agent-${issueId.toLowerCase()}`;\n const agentState = getAgentState(agentId);\n if (agentState && sessionExists(agentId)) {\n killSession(agentId);\n agentState.status = 'stopped';\n saveAgentState(agentState);\n console.log(`[merge-agent] ✓ Killed work agent session ${agentId} to free resources`);\n logActivity('agent_session_killed', `Freed resources: killed tmux session for ${agentId}`);\n }\n // Also kill planning agent if it exists\n const planningId = `planning-${issueId.toLowerCase()}`;\n if (sessionExists(planningId)) {\n killSession(planningId);\n console.log(`[merge-agent] ✓ Killed planning agent session ${planningId}`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not kill agent sessions: ${err}`);\n }\n\n // 6. Stop Docker containers + networks to prevent network pool exhaustion (non-fatal)\n // Orphaned Docker networks accumulate when workspaces are merged but containers are never\n // torn down, eventually exhausting Docker's address pool and blocking new workspace creation.\n try {\n const { findWorkspacePath } = await import('../lifecycle/archive-planning.js');\n const { stopWorkspaceDocker } = await import('../workspace-manager.js');\n const issueLower = issueId.toLowerCase();\n const workspacePath = findWorkspacePath(projectPath, issueLower);\n if (workspacePath) {\n const projName = basename(projectPath);\n const dockerResult = await stopWorkspaceDocker(workspacePath, projName, issueLower);\n if (dockerResult.containersFound) {\n console.log(`[merge-agent] ✓ Stopped Docker containers: ${dockerResult.steps.join('; ')}`);\n logActivity('docker_cleanup', `Stopped Docker for ${issueId}: ${dockerResult.steps.join('; ')}`);\n }\n }\n } catch (err) {\n console.warn(`[merge-agent] Docker cleanup failed (non-fatal): ${err}`);\n }\n\n // Mark completed BEFORE logging — prevents re-entry even if the log line triggers something\n _completedPostMerge.add(issueId);\n\n console.log(`[merge-agent] Post-merge cleanup completed for ${issueId}. Issue moved to Done — awaiting close-out.`);\n logActivity('merge_complete', `Merged ${issueId}. Issue moved to Done — awaiting close-out.`);\n}\n\n/**\n * Close issue on tracker with circuit breaker protection.\n * Fire-and-forget: runs asynchronously, never blocks the caller.\n * Stops retrying after MAX_CLOSE_RETRIES consecutive failures per issue.\n */\nfunction closeIssueWithCircuitBreaker(issueId: string, projectPath: string): void {\n const failures = _closeIssueFailures.get(issueId) || 0;\n if (failures >= MAX_CLOSE_RETRIES) {\n console.log(`[merge-agent] Circuit breaker open for ${issueId} issue close (${failures} failures). Will be closed during close-out ceremony.`);\n return;\n }\n\n // Fire-and-forget — errors are caught and logged, never propagated\n (async () => {\n try {\n const { closeIssue } = await import('../lifecycle/close-issue.js');\n const ghResolved = resolveGitHubIssue(issueId);\n const ctx = ghResolved.isGitHub\n ? { issueId, projectPath, github: { owner: ghResolved.owner, repo: ghResolved.repo, number: ghResolved.number } }\n : { issueId, projectPath };\n const results = await closeIssue(ctx, { applyLabel: false, comment: 'Merged to main via Panopticon merge-agent' });\n\n let anyFailure = false;\n for (const r of results) {\n if (r.success && !r.skipped) {\n console.log(`[merge-agent] ✓ ${r.details?.join('; ')}`);\n logActivity(r.step, r.details?.join('; ') || r.step);\n } else if (!r.skipped) {\n console.warn(`[merge-agent] ✗ ${r.step} failed: ${r.error}`);\n anyFailure = true;\n }\n }\n\n if (anyFailure) {\n const newCount = (_closeIssueFailures.get(issueId) || 0) + 1;\n _closeIssueFailures.set(issueId, newCount);\n if (newCount >= MAX_CLOSE_RETRIES) {\n console.warn(`[merge-agent] Circuit breaker tripped for ${issueId} after ${newCount} failures. Issue close deferred to close-out ceremony.`);\n }\n } else {\n // Success — clear failure counter\n _closeIssueFailures.delete(issueId);\n }\n } catch (err) {\n const newCount = (_closeIssueFailures.get(issueId) || 0) + 1;\n _closeIssueFailures.set(issueId, newCount);\n console.warn(`[merge-agent] Could not move issue to Done (attempt ${newCount}/${MAX_CLOSE_RETRIES}): ${err}`);\n }\n })();\n}\n\n/**\n * Reset postMergeLifecycle completion tracking for an issue (used by reopen).\n */\nexport function resetPostMergeState(issueId: string): void {\n _completedPostMerge.delete(issueId);\n _closeIssueFailures.delete(issueId);\n}\n\n/**\n * Parse result markers from agent output\n */\nfunction parseAgentOutput(output: string): MergeResult {\n const lines = output.split('\\n');\n\n let mergeResult: 'SUCCESS' | 'FAILURE' | null = null;\n let resolvedFiles: string[] = [];\n let failedFiles: string[] = [];\n let testsStatus: 'PASS' | 'FAIL' | 'SKIP' | null = null;\n let validationStatus: 'PASS' | 'FAIL' | null = null;\n let reason = '';\n let notes = '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Match MERGE_RESULT\n if (trimmed.startsWith('MERGE_RESULT:')) {\n const value = trimmed.substring('MERGE_RESULT:'.length).trim();\n if (value === 'SUCCESS' || value === 'FAILURE') {\n mergeResult = value;\n }\n }\n\n // Match RESOLVED_FILES\n if (trimmed.startsWith('RESOLVED_FILES:')) {\n const value = trimmed.substring('RESOLVED_FILES:'.length).trim();\n resolvedFiles = value\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n }\n\n // Match FAILED_FILES\n if (trimmed.startsWith('FAILED_FILES:')) {\n const value = trimmed.substring('FAILED_FILES:'.length).trim();\n failedFiles = value\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n }\n\n // Match TESTS\n if (trimmed.startsWith('TESTS:')) {\n const value = trimmed.substring('TESTS:'.length).trim();\n if (value === 'PASS' || value === 'FAIL' || value === 'SKIP') {\n testsStatus = value;\n }\n }\n\n // Match VALIDATION\n if (trimmed.startsWith('VALIDATION:')) {\n const value = trimmed.substring('VALIDATION:'.length).trim();\n if (value === 'PASS' || value === 'FAIL') {\n validationStatus = value;\n }\n }\n\n // Match REASON\n if (trimmed.startsWith('REASON:')) {\n reason = trimmed.substring('REASON:'.length).trim();\n }\n\n // Match NOTES\n if (trimmed.startsWith('NOTES:')) {\n notes = trimmed.substring('NOTES:'.length).trim();\n }\n }\n\n // Build result\n if (mergeResult === 'SUCCESS') {\n return {\n success: true,\n resolvedFiles,\n testsStatus: testsStatus || 'SKIP',\n validationStatus: validationStatus || 'NOT_RUN',\n notes,\n output,\n };\n } else if (mergeResult === 'FAILURE') {\n return {\n success: false,\n failedFiles,\n validationStatus: validationStatus || 'NOT_RUN',\n reason,\n notes,\n output,\n };\n } else {\n // No structured result markers found - try to detect human-readable format\n // Agents sometimes output \"MERGE TASK COMPLETE\" instead of \"MERGE_RESULT: SUCCESS\"\n const lowerOutput = output.toLowerCase();\n\n // Check for success indicators\n const successIndicators = [\n 'merge task complete',\n 'successfully merged',\n 'merge complete',\n 'pushed merge commit',\n 'successfully merged and pushed',\n ];\n\n const failureIndicators = [\n 'merge failed',\n 'merge task failed',\n 'could not merge',\n 'conflict not resolved',\n ];\n\n const hasSuccessIndicator = successIndicators.some(i => lowerOutput.includes(i));\n const hasFailureIndicator = failureIndicators.some(i => lowerOutput.includes(i));\n\n if (hasSuccessIndicator && !hasFailureIndicator) {\n // Extract test status from output if mentioned\n let detectedTestStatus: 'PASS' | 'FAIL' | 'SKIP' = 'SKIP';\n if (lowerOutput.includes('tests: pass') || lowerOutput.includes('tests passed') ||\n output.match(/\\d+ passed/)) {\n detectedTestStatus = 'PASS';\n } else if (lowerOutput.includes('tests: fail') || lowerOutput.includes('tests failed')) {\n detectedTestStatus = 'FAIL';\n }\n\n console.log('[merge-agent] Detected success from human-readable output');\n return {\n success: true,\n testsStatus: detectedTestStatus,\n validationStatus: 'PASS',\n notes: 'Detected from human-readable output (agent did not use structured format)',\n output,\n };\n }\n\n if (hasFailureIndicator) {\n console.log('[merge-agent] Detected failure from human-readable output');\n return {\n success: false,\n validationStatus: 'NOT_RUN',\n reason: 'Detected merge failure from agent output',\n output,\n };\n }\n\n // Truly unrecognized output\n return {\n success: false,\n validationStatus: 'NOT_RUN',\n reason: 'Agent did not report result in expected format',\n output,\n };\n }\n}\n\n/**\n * Get conflict files from git status (async)\n */\nasync function getConflictFiles(projectPath: string): Promise<string[]> {\n try {\n const { stdout: status } = await execAsync('git diff --name-only --diff-filter=U', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n\n return status\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n } catch (error) {\n console.error('Failed to get conflict files:', error);\n return [];\n }\n}\n\n/**\n * Log merge to history\n */\nfunction logMergeHistory(context: MergeConflictContext, result: MergeResult, sessionId?: string): void {\n // Ensure history directory exists\n if (!existsSync(MERGE_HISTORY_DIR)) {\n mkdirSync(MERGE_HISTORY_DIR, { recursive: true });\n }\n\n const entry: MergeHistoryEntry = {\n timestamp: new Date().toISOString(),\n issueId: context.issueId,\n sourceBranch: context.sourceBranch,\n targetBranch: context.targetBranch,\n conflictFiles: context.conflictFiles,\n result: {\n ...result,\n output: undefined, // Don't store full output in history\n },\n sessionId,\n };\n\n appendFileSync(MERGE_HISTORY_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n}\n\n/**\n * Log activity to the dashboard activity log\n */\nfunction logActivity(action: string, details: string): void {\n const ACTIVITY_LOG = '/tmp/panopticon-activity.log';\n try {\n const entry = {\n timestamp: new Date().toISOString(),\n source: 'merge-agent',\n action,\n details,\n };\n appendFileSync(ACTIVITY_LOG, JSON.stringify(entry) + '\\n');\n } catch {\n // Non-fatal\n }\n}\n\n/**\n * Capture tmux output and look for result markers (async)\n */\nasync function captureTmuxOutput(sessionName: string): Promise<string> {\n try {\n const { stdout } = await execAsync(`tmux capture-pane -t \"${sessionName}\" -p`, { encoding: 'utf-8' });\n return stdout;\n } catch {\n return '';\n }\n}\n\n/**\n * Check if specialist-merge-agent tmux session is running (async)\n */\nasync function isMergeAgentRunning(): Promise<boolean> {\n try {\n await execAsync(`tmux has-session -t specialist-merge-agent 2>/dev/null`, { encoding: 'utf-8' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Send a message to an agent's tmux session (async)\n */\nasync function sendMessageToAgent(issueId: string, message: string): Promise<boolean> {\n // Agent sessions are typically named agent-{issueId} (lowercase)\n const sessionName = `agent-${issueId.toLowerCase()}`;\n\n try {\n // Check if session exists\n if (!sessionExists(sessionName)) {\n console.log(`[merge-agent] Could not send message to ${sessionName} (session does not exist)`);\n return false;\n }\n\n // Send the message using centralized sendKeys\n await sendKeysAsync(sessionName, message);\n\n console.log(`[merge-agent] Sent message to ${sessionName}`);\n logActivity('agent_message', `Sent to ${sessionName}: ${message.slice(0, 100)}...`);\n return true;\n } catch {\n console.log(`[merge-agent] Could not send message to ${sessionName} (session may not exist)`);\n return false;\n }\n}\n\n/**\n * Spawn merge-agent to resolve conflicts using the tmux session\n *\n * @param context - Merge conflict context\n * @returns Promise that resolves with merge result\n */\nexport async function spawnMergeAgent(context: MergeConflictContext): Promise<MergeResult> {\n console.log(`[merge-agent] Starting conflict resolution for ${context.issueId}`);\n logActivity('merge_start', `Starting merge for ${context.issueId}: ${context.conflictFiles.join(', ')}`);\n\n // Detect test command if not provided\n if (!context.testCommand) {\n context.testCommand = detectTestCommand(context.projectPath);\n }\n\n const tmuxSession = getTmuxSessionName('merge-agent');\n console.log(`[merge-agent] Using tmux session: ${tmuxSession}`);\n console.log(`[merge-agent] Test command: ${context.testCommand}`);\n\n // Check if merge-agent session is running\n if (!(await isMergeAgentRunning())) {\n console.log(`[merge-agent] Session not running, cannot proceed`);\n logActivity('merge_error', `Session ${tmuxSession} not running`);\n return {\n success: false,\n reason: `Specialist ${tmuxSession} is not running. Start Cloister first.`,\n };\n }\n\n // Build prompt\n const prompt = buildMergePrompt(context);\n\n try {\n // Send prompt to tmux session using centralized sendKeys\n console.log(`[merge-agent] Sending task to ${tmuxSession}...`);\n await sendKeysAsync(tmuxSession, prompt);\n\n // Record wake event\n recordWake('merge-agent');\n logActivity('merge_task_sent', `Task sent to ${tmuxSession}`);\n\n console.log(`[merge-agent] Task sent, waiting for completion...`);\n\n // Poll for result with timeout\n const startTime = Date.now();\n const POLL_INTERVAL = 5000; // 5 seconds\n let lastOutput = '';\n\n while (Date.now() - startTime < MERGE_TIMEOUT_MS) {\n await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));\n\n const output = await captureTmuxOutput(tmuxSession);\n\n // Check if we have new output with result markers\n if (output !== lastOutput) {\n lastOutput = output;\n const lowerOutput = output.toLowerCase();\n\n // Look for result markers in the output (structured or human-readable)\n const hasStructuredResult = output.includes('MERGE_RESULT:');\n const hasHumanReadableResult =\n lowerOutput.includes('merge task complete') ||\n lowerOutput.includes('successfully merged') ||\n lowerOutput.includes('merge complete') ||\n lowerOutput.includes('merge failed') ||\n lowerOutput.includes('merge task failed');\n\n if (hasStructuredResult || hasHumanReadableResult) {\n console.log(`[merge-agent] Found result markers in output (structured: ${hasStructuredResult}, human-readable: ${hasHumanReadableResult})`);\n\n const result = parseAgentOutput(output);\n\n // If agent reports success, run post-merge validation\n if (result.success) {\n console.log(`[merge-agent] Agent reported success, running post-merge validation...`);\n logActivity('merge_validation_start', `Running validation for ${context.issueId}`);\n\n // Extract baseline failure count from agent output for baseline comparison\n // Agent output contains a table like: │ Failed │ 18 │ 18 │ 0 ✅ │\n const baselineMatch = output.match(/Failed\\s*│\\s*(\\d+)\\s*│/);\n const baselineTestFailures = baselineMatch ? parseInt(baselineMatch[1], 10) : undefined;\n if (baselineTestFailures !== undefined) {\n console.log(`[merge-agent] Extracted baseline failure count from agent: ${baselineTestFailures}`);\n }\n\n const validationResult = await runMergeValidation({\n projectPath: context.projectPath,\n issueId: context.issueId,\n baselineTestFailures,\n });\n\n if (validationResult.valid) {\n // Validation passed — now run quality gates if configured\n console.log(`[merge-agent] ✓ Validation passed`);\n\n const gateResults = await runProjectQualityGates(context.projectPath, 'pre_push');\n const failedRequired = gateResults.filter(g => !g.passed && g.required);\n if (failedRequired.length > 0) {\n const failedNames = failedRequired.map(g => g.name).join(', ');\n console.log(`[merge-agent] ✗ Quality gates failed: ${failedNames}`);\n logActivity('merge_quality_gate_fail', `Quality gates failed for ${context.issueId}: ${failedNames}`);\n\n const revertSuccess = await autoRevertMerge(context.projectPath);\n const revertNote = revertSuccess\n ? 'Merge auto-reverted to clean state'\n : 'WARNING: Auto-revert failed - manual cleanup required';\n\n const failedResult: MergeResult = {\n success: false,\n validationStatus: 'FAIL',\n reason: `Quality gate(s) failed: ${failedNames}. ${revertNote}`,\n notes: result.notes,\n output,\n };\n logMergeHistory(context, failedResult);\n return failedResult;\n }\n\n logActivity('merge_success', `Merge and validation completed for ${context.issueId}`);\n\n // Update result with validation status\n result.validationStatus = 'PASS';\n logMergeHistory(context, result);\n\n // Run post-merge cleanup (move PRD, update issue status)\n await postMergeLifecycle(context.issueId, context.projectPath, context.sourceBranch);\n\n // Notify TLDR daemon to reindex changed files\n await notifyTldrDaemon(context.projectPath, context.sourceBranch);\n\n return result;\n } else {\n // Validation failed - auto-revert\n console.log(`[merge-agent] ✗ Validation failed:`, validationResult.failures);\n logActivity('merge_validation_fail', `Validation failed for ${context.issueId}: ${validationResult.failures.map(f => f.type).join(', ')}`);\n\n // Revert to ORIG_HEAD (set by git at merge time)\n const revertSuccess = await autoRevertMerge(context.projectPath);\n\n const failureReason = validationResult.failures.map(f => `${f.type}: ${f.message}`).join('; ');\n const revertNote = revertSuccess\n ? 'Merge auto-reverted to clean state'\n : 'WARNING: Auto-revert failed - manual cleanup required';\n\n console.log(`[merge-agent] ${revertNote}`);\n logActivity('merge_auto_revert', revertNote);\n\n // Return failure with validation details\n const failedResult: MergeResult = {\n success: false,\n validationStatus: 'FAIL',\n reason: `Validation failed: ${failureReason}. ${revertNote}`,\n notes: result.notes,\n output,\n };\n\n logMergeHistory(context, failedResult);\n return failedResult;\n }\n } else {\n // Agent reported failure\n logActivity('merge_failure', `Merge failed for ${context.issueId}: ${result.reason}`);\n logMergeHistory(context, result);\n return result;\n }\n }\n }\n\n // Log progress periodically\n const elapsed = Math.round((Date.now() - startTime) / 1000);\n if (elapsed % 30 === 0) {\n console.log(`[merge-agent] Still working... (${elapsed}s elapsed)`);\n }\n }\n\n // Timeout\n console.log(`[merge-agent] Timeout after ${MERGE_TIMEOUT_MS / 1000} seconds`);\n logActivity('merge_timeout', `Merge timed out for ${context.issueId}`);\n\n return {\n success: false,\n reason: `Timeout after ${MERGE_TIMEOUT_MS / 60000} minutes`,\n output: lastOutput,\n };\n } catch (error: any) {\n console.error(`[merge-agent] Failed:`, error);\n logActivity('merge_error', `Error: ${error.message}`);\n\n const result: MergeResult = {\n success: false,\n reason: error.message || 'Unknown error',\n };\n\n logMergeHistory(context, result);\n return result;\n }\n}\n\n/**\n * Attempt merge and handle result (clean merge, conflicts, or failure)\n *\n * This function:\n * 1. Attempts to merge sourceBranch into current branch\n * 2. If clean merge: commits and optionally runs tests\n * 3. If conflicts: spawns merge-agent to resolve them\n * 4. If failure: returns error\n *\n * @param projectPath - Project root path\n * @param sourceBranch - Feature branch to merge\n * @param targetBranch - Target branch (usually main)\n * @param issueId - Issue identifier\n * @returns Promise that resolves with merge result\n */\nexport async function spawnMergeAgentForBranches(\n projectPath: string,\n sourceBranch: string,\n targetBranch: string,\n issueId: string,\n options?: { skipDoneReport?: boolean }\n): Promise<MergeResult> {\n console.log(`[merge-agent] Waking specialist for merge of ${sourceBranch} into ${targetBranch}`);\n logActivity('merge_attempt', `Waking specialist for merge: ${sourceBranch} -> ${targetBranch}`);\n\n // Pre-flight checks (quick validation before waking specialist)\n try {\n // 1. Check for and clean up stale git lock files\n console.log(`[merge-agent] Checking for stale git lock files...`);\n const lockCleanup = await cleanupStaleLocks(projectPath);\n\n if (lockCleanup.found.length > 0) {\n console.log(`[merge-agent] Found ${lockCleanup.found.length} lock file(s)`);\n\n if (lockCleanup.removed.length > 0) {\n console.log(`[merge-agent] ✓ Cleaned up ${lockCleanup.removed.length} stale lock file(s):`);\n lockCleanup.removed.forEach(f => console.log(` - ${f}`));\n logActivity('git_lock_cleanup', `Removed ${lockCleanup.removed.length} stale lock file(s)`);\n }\n\n if (lockCleanup.errors.length > 0) {\n console.warn(`[merge-agent] ⚠️ Failed to clean up some locks:`, lockCleanup.errors);\n if (lockCleanup.errors.some(e => e.error.includes('Git processes are running'))) {\n const message = 'Git processes are still running - cannot safely start merge';\n console.error(`[merge-agent] ${message}`);\n logActivity('merge_blocked', message);\n return { success: false, reason: message };\n }\n }\n }\n\n // 2. Check that source branch is pushed to remote\n try {\n const { stdout: remoteBranches } = await execAsync(`git ls-remote --heads origin ${sourceBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n\n if (!remoteBranches.trim()) {\n const message = `Branch ${sourceBranch} is not pushed to remote.`;\n console.error(`[merge-agent] ${message}`);\n logActivity('merge_blocked', message);\n // Write feedback file and send short reference\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n const blockMsg = `# Merge Blocked\\n\\nBranch \"${sourceBranch}\" is not pushed to remote.\\n\\n## Required Action\\n\\nRun: \\`git push -u origin ${sourceBranch}\\``;\n const fileResult = await writeFeedbackFile({\n issueId,\n specialist: 'merge-agent',\n outcome: 'blocked',\n summary: `Branch ${sourceBranch} not pushed`,\n markdownBody: blockMsg,\n });\n if (fileResult.success) {\n await sendMessageToAgent(issueId, `SPECIALIST FEEDBACK: merge-agent reported BLOCKED for ${issueId}.\\nRead and address: ${fileResult.relativePath}`);\n } else {\n console.error(`[merge-agent] Failed to write feedback file for ${issueId}: ${fileResult.error}`);\n }\n return { success: false, reason: message };\n }\n } catch {\n const message = `Cannot verify remote branch ${sourceBranch}.`;\n console.error(`[merge-agent] ${message}`);\n logActivity('merge_blocked', message);\n return { success: false, reason: message };\n }\n\n // NOTE: We don't check for uncommitted changes in the main repo here.\n // The merge happens via git merge which will fail if there are conflicts.\n // Uncommitted changes in main are the user's own work and shouldn't block\n // merging a feature branch. The dashboard server already checks the\n // workspace for uncommitted changes before initiating the merge.\n } catch (error: any) {\n return { success: false, reason: `Pre-flight check failed: ${error.message}` };\n }\n\n // 3. No-op check: if sourceBranch is already an ancestor of targetBranch, skip the merge\n try {\n await execAsync(`git fetch origin ${sourceBranch} ${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n let isAlreadyMerged = false;\n try {\n await execAsync(\n `git merge-base --is-ancestor origin/${sourceBranch} origin/${targetBranch}`,\n { cwd: projectPath, encoding: 'utf-8' }\n );\n isAlreadyMerged = true;\n } catch (e: any) {\n // exit code 1 means not an ancestor — proceed with merge\n // any other exit code is a real error; propagate it\n if (e.code !== 1) {\n throw e;\n }\n }\n if (isAlreadyMerged) {\n const message = `Branch ${sourceBranch} is already integrated into ${targetBranch} — no merge needed`;\n console.log(`[merge-agent] ${message}`);\n logActivity('merge_skipped', message);\n return { success: true, reason: message };\n }\n } catch (ancestorErr: any) {\n console.warn(`[merge-agent] Ancestor check failed: ${ancestorErr.message} (continuing)`);\n }\n\n // Record current HEAD to detect when merge happens (polling compares against this)\n const { stdout: headBeforeRaw } = await execAsync('git rev-parse HEAD', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const headBefore = headBeforeRaw.trim();\n\n // Stash any uncommitted changes so the merge starts from a clean state\n // We restore the stash after completion (success or rollback)\n let stashCreated = false;\n try {\n const { stdout: statusOut } = await execAsync('git status --porcelain', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n if (statusOut.trim()) {\n await execAsync('git stash push -u -m \"Pre-merge stash for ' + issueId + '\"', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n stashCreated = true;\n console.log(`[merge-agent] Stashed uncommitted changes before merge`);\n }\n } catch (stashErr: any) {\n console.warn(`[merge-agent] Failed to stash: ${stashErr.message} (continuing anyway)`);\n }\n\n // Build the task prompt for the merge-agent specialist\n const apiPort = process.env.API_PORT || process.env.PORT || '3011';\n const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${apiPort}`;\n const skipDoneReport = options?.skipDoneReport ?? false;\n\n // When called from the polyrepo merge loop, the server manages overall status.\n // The merge-agent should NOT call /api/specialists/done — doing so would\n // prematurely set the issue's overall mergeStatus to 'merged' after one repo,\n // even if other repos haven't been merged yet.\n const doneReportInstructions = skipDoneReport\n ? `DO NOT call /api/specialists/done — the server manages status for this merge.\n After pushing, simply STOP. If you need to rollback, rollback and STOP.`\n : `Then report by calling the Panopticon API:\n curl -s -X POST ${apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"merge\",\"issueId\":\"${issueId}\",\"status\":\"<passed or failed>\",\"notes\":\"<reason if failed>\"}'\n\nCRITICAL: You MUST call the /api/specialists/done endpoint whether you succeed or fail.`;\n\n const taskPrompt = `MERGE TASK for ${issueId}:\n\nPROJECT: ${projectPath}\nSOURCE BRANCH: ${sourceBranch}\nTARGET BRANCH: ${targetBranch}\n\nINSTRUCTIONS:\n\nPHASE 1 — SYNC & BASELINE (before merge):\n1. cd ${projectPath}\n2. git checkout ${targetBranch}\n3. git fetch origin ${targetBranch}\n4. Sync local ${targetBranch} with origin/${targetBranch}:\n Run: git rev-list --left-right --count ${targetBranch}...origin/${targetBranch}\n (Output: \"LOCAL_AHEAD REMOTE_AHEAD\". If REMOTE_AHEAD > 0, local is behind origin.)\n If local is behind origin (REMOTE_AHEAD > 0):\n a. git rebase origin/${targetBranch}\n (Replays local commits on top of origin — preserves linear history, no merge commits, no data loss)\n b. If rebase conflicts: abort with git rebase --abort, then STOP — human intervention needed.\n c. If rebase succeeds: continue to next step\n If local is up-to-date or ahead-only (REMOTE_AHEAD = 0): continue to next step\n5. Run tests on the CURRENT ${targetBranch} to establish a baseline:\n - Use the Task tool with subagent_type=\"Bash\" to run: npm test 2>&1 || true\n - Record the number of passing and failing tests as BASELINE_PASS and BASELINE_FAIL\n - This baseline is critical — you will compare post-merge results against it\n\nPHASE 2 — MERGE:\n6. git merge ${sourceBranch}\n7. If clean merge: the merge commit is auto-created (or fast-forward). Skip to Phase 3.\n8. If conflicts:\n a. Immediately abort: git merge --abort\n b. ROLLBACK — report FAILURE with note \"Merge conflicts detected — work agent must rebase before merge\"\n c. Do NOT attempt to manually resolve conflicts. The work agent or human must handle this.\n\nPHASE 3 — VERIFY:\n9. Build the project to verify no compile errors:\n - Use the Task tool with subagent_type=\"Bash\" to run the build command\n - For Node.js: NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build\n - For Java/Maven: ./mvnw compile\n - Check package.json or pom.xml to determine the right command\n10. Run tests using the Task tool with subagent_type=\"Bash\":\n - For Node.js: npm test\n - Record the number of passing and failing tests as MERGE_PASS and MERGE_FAIL\n\nPHASE 4 — DECIDE:\n11. Compare results:\n - If build failed: ROLLBACK (go to step 12)\n - If MERGE_FAIL > BASELINE_FAIL (NEW test failures introduced): ROLLBACK (go to step 12)\n - If MERGE_FAIL <= BASELINE_FAIL (no new failures): PUSH (go to step 13)\n - Pre-existing failures on ${targetBranch} are NOT a reason to rollback\n12. ROLLBACK: git reset --hard ORIG_HEAD\n (ORIG_HEAD is set by git at merge time — always points to pre-merge state)\n ${doneReportInstructions.includes('DO NOT') ? 'Then STOP.' : `Then report failure by calling the Panopticon API:\n curl -s -X POST ${apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"merge\",\"issueId\":\"${issueId}\",\"status\":\"failed\",\"notes\":\"<reason for rollback>\"}'\n Then STOP.`}\n13. PUSH: git push origin ${targetBranch}\n If push is rejected (non-fast-forward / \"tip of your current branch is behind\"):\n a. git fetch origin ${targetBranch}\n b. git rebase origin/${targetBranch}\n (Replay on top of any new remote commits — safe, no data loss)\n c. If rebase conflicts: abort with git rebase --abort, ROLLBACK (go to step 12)\n d. If rebase succeeds: retry git push origin ${targetBranch}\n e. If push fails again after one retry: ROLLBACK (go to step 12)\n ${doneReportInstructions}\n\nCRITICAL: You MUST complete this merge. The approve operation is waiting.\n\nWHY USE SUBAGENTS FOR BUILD/TEST:\n- Subagents have isolated context and won't pollute your working memory\n- Build and test output can be verbose - subagents handle this cleanly\n- If tests fail, the subagent returns a clear summary\n\nDO NOT:\n- Delete the feature branch (locally or remotely)\n- Clean up workspaces\n- Use git push --force or --force-with-lease — NEVER force-push under any circumstances\n- Skip the build step - compile errors after merge are common\n- Skip the baseline test run — without it you cannot distinguish new failures from pre-existing ones\n- Use HEAD~1 for rollback — use ORIG_HEAD which git sets automatically at merge time\n- Run git stash — the TypeScript layer handles stash/restore automatically\n- Do anything beyond the sync, merge, build, test, and push steps above\n\nReport any issues or conflicts you encountered.`;\n\n // Resolve project key for per-project ephemeral lifecycle (PAN-300)\n const resolvedProject = resolveProjectFromIssue(issueId);\n const mergeProjectKey = resolvedProject?.projectKey ?? null;\n const mergeSession = getTmuxSessionName('merge-agent', mergeProjectKey ?? undefined);\n\n if (!resolvedProject) {\n console.warn(`[merge-agent] Could not resolve project for ${issueId} — falling back to global specialist. Check projects.yaml configuration.`);\n }\n\n // Wait for the per-project merge-agent to be idle before sending a new task.\n // Only applies to the per-project ephemeral path — the legacy wakeSpecialist\n // path manages its own ready-wait internally via waitForReady: true.\n // Without this, sending a task to a busy specialist causes Claude's\n // \"Interrupted\" behavior — the running tool gets cancelled and the\n // previous merge is abandoned mid-flight.\n if (mergeProjectKey) {\n const { getAgentRuntimeState, saveAgentRuntimeState } = await import('../agents.js');\n const IDLE_POLL_INTERVAL = 3000; // 3 seconds\n const IDLE_MAX_WAIT = 360000; // 6 minutes (slightly longer than specialist timeout)\n const idleStart = Date.now();\n\n while (Date.now() - idleStart < IDLE_MAX_WAIT) {\n const state = getAgentRuntimeState(mergeSession);\n if (!state || state.state === 'idle' || state.state === 'suspended') {\n break; // Specialist is idle, safe to send\n }\n // Dead-session check: if runtime.json says active but tmux session is gone,\n // the specialist died without resetting state. Reset to idle and proceed immediately.\n try {\n await execAsync(`tmux has-session -t \"${mergeSession}\" 2>/dev/null`);\n } catch {\n // tmux has-session exits non-zero when the session does not exist\n console.log(`[merge-agent] Specialist session ${mergeSession} is dead (state was ${state.state}), resetting to idle`);\n saveAgentRuntimeState(mergeSession, { state: 'idle', lastActivity: new Date().toISOString() });\n break;\n }\n console.log(`[merge-agent] Specialist busy (state: ${state.state}, issue: ${state.currentIssue}), waiting...`);\n await new Promise(resolve => setTimeout(resolve, IDLE_POLL_INTERVAL));\n }\n\n // Final check after loop\n const finalState = getAgentRuntimeState(mergeSession);\n if (finalState && finalState.state !== 'idle' && finalState.state !== 'suspended') {\n console.warn(`[merge-agent] Specialist still busy after ${IDLE_MAX_WAIT / 1000}s, proceeding anyway`);\n }\n }\n\n // Wake the merge-agent specialist using per-project ephemeral lifecycle when possible\n let wakeResult: { success: boolean; message: string; tmuxSession?: string; error?: string };\n if (mergeProjectKey) {\n console.log(`[merge-agent] Using per-project ephemeral specialist for ${issueId} (${mergeProjectKey})`);\n wakeResult = await spawnEphemeralSpecialist(mergeProjectKey, 'merge-agent', {\n issueId,\n branch: sourceBranch,\n workspace: projectPath,\n promptOverride: taskPrompt,\n });\n } else {\n console.log(`[merge-agent] Project resolution failed, falling back to legacy global specialist for ${issueId}`);\n wakeResult = await wakeSpecialist('merge-agent', taskPrompt, {\n waitForReady: true,\n startIfNotRunning: true,\n issueId,\n });\n }\n\n if (!wakeResult.success) {\n console.error(`[merge-agent] Failed to wake specialist: ${wakeResult.message}`);\n logActivity('merge_error', `Failed to wake specialist: ${wakeResult.message}`);\n return {\n success: false,\n reason: `Failed to wake merge-agent specialist: ${wakeResult.message}`,\n };\n }\n\n console.log(`[merge-agent] Specialist woken, waiting for merge completion...`);\n logActivity('merge_specialist_woken', `Specialist woken, task sent`);\n\n // Poll for merge completion (check if HEAD has changed and been pushed)\n const POLL_INTERVAL = 5000; // 5 seconds\n const MAX_WAIT = 15 * 60 * 1000; // 15 minutes (match MERGE_TIMEOUT_MS)\n const startTime = Date.now();\n\n while (Date.now() - startTime < MAX_WAIT) {\n await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));\n\n try {\n // Check if we're still on target branch\n const { stdout: currentBranchRaw } = await execAsync('git branch --show-current', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const currentBranch = currentBranchRaw.trim();\n\n if (currentBranch !== targetBranch) {\n // Specialist might still be working, continue polling\n continue;\n }\n\n // Check if HEAD has changed (merge happened)\n const { stdout: currentHeadRaw } = await execAsync('git rev-parse HEAD', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const currentHead = currentHeadRaw.trim();\n\n if (currentHead !== headBefore) {\n // HEAD changed — the merge happened (could be merge commit OR fast-forward)\n // For merge commits: message contains \"merge\" or branch name\n // For fast-forward: message is the original commit message (no \"merge\" keyword)\n // In BOTH cases, HEAD changing means the merge is done — verify it's pushed\n {\n // Verify it's pushed — fetch first to refresh stale tracking refs\n // (the push happens in the merge-agent's tmux session, which may not\n // update the tracking ref visible to this process)\n try {\n await execAsync(`git fetch origin ${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: 10000,\n }).catch(() => {}); // Non-fatal — fall through to rev-parse\n const { stdout: remoteHeadRaw } = await execAsync(`git rev-parse origin/${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const remoteHead = remoteHeadRaw.trim();\n\n if (remoteHead === currentHead) {\n console.log(`[merge-agent] Merge completed and pushed, running validation...`);\n logActivity('merge_validation_start', `Running post-merge validation for ${issueId}`);\n\n // Extract baseline from specialist output if available\n let specialistBaseline: number | undefined;\n try {\n const specialistOutput = await captureTmuxOutput(mergeSession);\n const baselineMatch = specialistOutput.match(/Failed\\s*│\\s*(\\d+)\\s*│/);\n specialistBaseline = baselineMatch ? parseInt(baselineMatch[1], 10) : undefined;\n if (specialistBaseline !== undefined) {\n console.log(`[merge-agent] Extracted baseline from specialist: ${specialistBaseline}`);\n }\n } catch { /* ignore */ }\n\n // Run validation\n const validationResult = await runMergeValidation({\n projectPath,\n issueId,\n baselineTestFailures: specialistBaseline,\n });\n\n if (validationResult.valid) {\n // Validation passed — now run quality gates if configured\n const skipNote = validationResult.skipped ? ' (no validation script, specialist already validated)' : '';\n console.log(`[merge-agent] ✓ Merge validation passed${skipNote}`);\n\n const gateResults = await runProjectQualityGates(projectPath, 'pre_push');\n const failedRequired = gateResults.filter(g => !g.passed && g.required);\n if (failedRequired.length > 0) {\n const failedNames = failedRequired.map(g => g.name).join(', ');\n console.log(`[merge-agent] ✗ Quality gates failed: ${failedNames}`);\n logActivity('merge_quality_gate_fail', `Quality gates failed for ${issueId}: ${failedNames}`);\n\n const revertSuccess = await autoRevertMerge(projectPath);\n const revertNote = revertSuccess\n ? 'Merge auto-reverted to clean state'\n : 'WARNING: Auto-revert failed';\n\n return {\n success: false,\n validationStatus: 'FAIL',\n reason: `Quality gate(s) failed: ${failedNames}. ${revertNote}`,\n };\n }\n\n logActivity('merge_complete', `Merge completed by specialist${skipNote}`);\n\n // Run post-merge cleanup (move PRD, update issue status)\n await postMergeLifecycle(issueId, projectPath, sourceBranch);\n\n // Restore stashed changes\n if (stashCreated) {\n try {\n await execAsync('git stash pop', { cwd: projectPath, encoding: 'utf-8' });\n console.log(`[merge-agent] ✓ Restored stashed changes after successful merge`);\n } catch (popErr: any) {\n console.warn(`[merge-agent] ⚠ Failed to restore stash after merge: ${popErr.message}`);\n }\n }\n\n return {\n success: true,\n validationStatus: 'PASS',\n testsStatus: 'SKIP', // Specialist ran tests, we trust the result\n notes: 'Merge completed by merge-agent specialist and validation passed',\n };\n } else {\n // Validation failed - auto-revert\n console.log(`[merge-agent] ✗ Validation failed:`, validationResult.failures);\n logActivity('merge_validation_fail', `Validation failed: ${validationResult.failures.map(f => f.type).join(', ')}`);\n\n // Revert to ORIG_HEAD (set by git at merge time)\n const revertSuccess = await autoRevertMerge(projectPath);\n\n // Force push to revert the remote as well\n if (revertSuccess) {\n try {\n await execAsync(`git push --force-with-lease origin ${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n console.log(`[merge-agent] ✓ Auto-revert pushed to remote`);\n logActivity('merge_auto_revert', 'Merge auto-reverted and pushed to remote');\n } catch (pushError: any) {\n console.error(`[merge-agent] ✗ Failed to push revert: ${pushError.message}`);\n logActivity('merge_revert_push_fail', 'Auto-revert successful but push failed');\n }\n }\n\n // Restore stashed changes after revert\n if (stashCreated) {\n try {\n await execAsync('git stash pop', { cwd: projectPath, encoding: 'utf-8' });\n console.log(`[merge-agent] ✓ Restored stashed changes after revert`);\n } catch (popErr: any) {\n console.warn(`[merge-agent] ⚠ Failed to restore stash after revert: ${popErr.message}`);\n }\n }\n\n const failureReason = validationResult.failures.map(f => `${f.type}: ${f.message}`).join('; ');\n const revertNote = revertSuccess\n ? 'Merge auto-reverted and force-pushed to remote'\n : 'WARNING: Auto-revert failed - manual cleanup required';\n\n return {\n success: false,\n validationStatus: 'FAIL',\n reason: `Validation failed: ${failureReason}. ${revertNote}`,\n notes: 'Merge completed but validation failed, auto-reverted',\n };\n }\n }\n } catch {\n // Remote check failed, but local merge is done\n console.log(`[merge-agent] Merge completed locally, push status unknown`);\n }\n\n // Local merge done but not pushed yet - keep polling\n console.log(`[merge-agent] Merge commit detected, waiting for push...`);\n }\n }\n\n // Check if merge-agent is still running\n if (!(await isRunning('merge-agent', mergeProjectKey ?? undefined))) {\n console.error(`[merge-agent] Specialist stopped unexpectedly — checking for stranded merge commit`);\n logActivity('merge_error', 'Specialist stopped unexpectedly');\n\n // Salvage: if the specialist merged locally but died before pushing, push it ourselves\n const salvageResult = await salvageStrandedMerge(projectPath, targetBranch, headBefore, issueId, logActivity);\n if (salvageResult) return salvageResult;\n\n return {\n success: false,\n reason: 'merge-agent specialist stopped before completing the merge',\n };\n }\n\n } catch (pollError: any) {\n console.warn(`[merge-agent] Poll error: ${pollError.message}`);\n // Continue polling\n }\n }\n\n // Timeout — same salvage check\n console.error(`[merge-agent] Timeout waiting for merge completion — checking for stranded merge commit`);\n logActivity('merge_timeout', 'Timeout waiting for specialist to complete merge');\n\n const salvageResult = await salvageStrandedMerge(projectPath, targetBranch, headBefore, issueId, logActivity);\n if (salvageResult) return salvageResult;\n\n return {\n success: false,\n reason: 'Timeout waiting for merge-agent specialist to complete merge (15 minutes)',\n };\n}\n\n/**\n * Rebase a feature branch onto a base branch and push, using the merge-agent\n * specialist for conflict resolution.\n *\n * Used by the PR-based merge flow: triggerMerge() calls this to prepare the\n * feature branch, then calls `gh pr merge --squash` once the rebase is done.\n */\nexport async function spawnRebaseAgentForBranch(\n workspacePath: string,\n featureBranch: string,\n baseBranch: string,\n issueId: string,\n): Promise<MergeResult> {\n console.log(`[merge-agent] Starting rebase of ${featureBranch} onto ${baseBranch} for ${issueId}`);\n logActivity('rebase_start', `Rebasing ${featureBranch} onto ${baseBranch} for ${issueId}`);\n\n // Pre-flight: verify feature branch is pushed to remote\n try {\n const { stdout: remoteBranches } = await execAsync(\n `git ls-remote --heads origin ${featureBranch}`,\n { cwd: workspacePath, encoding: 'utf-8' },\n );\n if (!remoteBranches.trim()) {\n const message = `Branch ${featureBranch} is not pushed to remote`;\n console.error(`[merge-agent] ${message}`);\n return { success: false, reason: message };\n }\n } catch {\n const message = `Cannot verify remote branch ${featureBranch}`;\n console.error(`[merge-agent] ${message}`);\n return { success: false, reason: message };\n }\n\n // Record current remote HEAD of feature branch to detect rebase completion\n let headBefore: string;\n try {\n await execAsync(`git fetch origin ${featureBranch}`, { cwd: workspacePath, encoding: 'utf-8' });\n const { stdout } = await execAsync(`git rev-parse origin/${featureBranch}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n headBefore = stdout.trim();\n } catch (err: any) {\n return { success: false, reason: `Failed to get remote HEAD: ${err.message}` };\n }\n\n // Build rebase task prompt\n const apiPort = process.env.API_PORT || process.env.PORT || '3011';\n const apiUrl = process.env.DASHBOARD_URL || `http://localhost:${apiPort}`;\n\n const taskPrompt = `REBASE TASK for ${issueId}:\n\nWORKSPACE: ${workspacePath}\nFEATURE BRANCH: ${featureBranch}\nBASE BRANCH: ${baseBranch}\n\nINSTRUCTIONS:\n\n1. cd ${workspacePath}\n2. git fetch origin ${baseBranch}\n3. Check if rebase is needed:\n \\`\\`\\`bash\n BEHIND=$(git rev-list --count HEAD..origin/${baseBranch})\n echo \"Commits behind origin/${baseBranch}: $BEHIND\"\n \\`\\`\\`\n4. If BEHIND is 0: skip rebase entirely — branch is already up to date. Go to step 7.\n5. If BEHIND > 0: Remove .planning/ first (ephemeral artifacts always conflict), then rebase:\n \\`\\`\\`bash\n git rm -rf .planning/ 2>/dev/null && git commit -m \"chore: remove planning artifacts before rebase\" 2>/dev/null\n git rebase origin/${baseBranch}\n \\`\\`\\`\n6. If rebase has conflicts:\n a. Immediately abort: git rebase --abort\n b. Report FAILURE — do NOT attempt to resolve conflicts manually\n7. git push --force-with-lease origin ${featureBranch}\n8. Merge the PR via GitHub CLI (this is the ACTUAL merge to main):\n \\`\\`\\`bash\n gh pr merge ${featureBranch} --squash\n \\`\\`\\`\n Do NOT use --auto or --admin flags. Panopticon reports commit statuses via GitHub App,\n so branch protection checks will pass automatically.\n If this fails, report FAILURE — do NOT report success without a merged PR.\n9. Report completion by calling the Panopticon API:\n curl -s -X POST ${apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"merge\",\"issueId\":\"${issueId}\",\"status\":\"passed\",\"notes\":\"Rebased and merged PR via gh pr merge --squash\"}'\n\nIMPORTANT:\n- Work ONLY in ${workspacePath} — do NOT modify the main repo\n- Do NOT run git merge locally — use gh pr merge --squash to merge via GitHub\n- Do NOT run build or tests — CI handles validation after PR merge\n- Use --force-with-lease (never --force) for the push\n- The PR MUST be merged via gh pr merge before reporting success\n- Report completion immediately after the PR merge\n\nIF REBASE FAILS (conflicts):\nAfter aborting, report failure so the work agent can fix it:\n\\`\\`\\`bash\ncurl -s -X POST ${apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"merge\",\"issueId\":\"${issueId}\",\"status\":\"failed\",\"notes\":\"Rebase conflicts with main — work agent must run: git fetch origin main && git rebase origin/main, resolve conflicts, then resubmit\"}'\n\\`\\`\\`\n\nIF gh pr merge FAILS:\nReport failure — do NOT report success:\n\\`\\`\\`bash\ncurl -s -X POST ${apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"merge\",\"issueId\":\"${issueId}\",\"status\":\"failed\",\"notes\":\"Rebase succeeded but gh pr merge --squash failed\"}'\n\\`\\`\\`\n\nCRITICAL: You MUST call the /api/specialists/done endpoint whether you succeed or fail.\nCRITICAL: Success means the PR is MERGED on GitHub. Rebase alone is NOT success.`;\n\n // Resolve project for per-project ephemeral specialist\n const resolvedProject = resolveProjectFromIssue(issueId);\n const mergeProjectKey = resolvedProject?.projectKey ?? null;\n const mergeSession = getTmuxSessionName('merge-agent', mergeProjectKey ?? undefined);\n\n if (!resolvedProject) {\n console.warn(`[merge-agent] Could not resolve project for ${issueId} — using global specialist`);\n }\n\n // Wait for specialist to be idle (same as spawnMergeAgentForBranches)\n if (mergeProjectKey) {\n const { getAgentRuntimeState, saveAgentRuntimeState } = await import('../agents.js');\n const IDLE_POLL_INTERVAL = 3000;\n const IDLE_MAX_WAIT = 360000;\n const idleStart = Date.now();\n\n while (Date.now() - idleStart < IDLE_MAX_WAIT) {\n const state = getAgentRuntimeState(mergeSession);\n if (!state || state.state === 'idle' || state.state === 'suspended') break;\n try {\n await execAsync(`tmux has-session -t \"${mergeSession}\" 2>/dev/null`);\n } catch {\n saveAgentRuntimeState(mergeSession, { state: 'idle', lastActivity: new Date().toISOString() });\n break;\n }\n await new Promise(resolve => setTimeout(resolve, IDLE_POLL_INTERVAL));\n }\n }\n\n // Wake the merge-agent specialist\n let wakeResult: { success: boolean; message: string };\n if (mergeProjectKey) {\n wakeResult = await spawnEphemeralSpecialist(mergeProjectKey, 'merge-agent', {\n issueId,\n branch: featureBranch,\n workspace: workspacePath,\n promptOverride: taskPrompt,\n });\n } else {\n wakeResult = await wakeSpecialist('merge-agent', taskPrompt, {\n waitForReady: true,\n startIfNotRunning: true,\n issueId,\n });\n }\n\n if (!wakeResult.success) {\n return {\n success: false,\n reason: `Failed to wake merge-agent specialist: ${wakeResult.message}`,\n };\n }\n\n console.log(`[merge-agent] Rebase specialist woken for ${issueId}, polling for completion...`);\n\n // Poll for rebase completion: remote feature branch HEAD should change after rebase + push\n const POLL_INTERVAL = 5000;\n const MAX_WAIT = 10 * 60 * 1000; // 10 minutes\n\n const startTime = Date.now();\n while (Date.now() - startTime < MAX_WAIT) {\n await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));\n\n try {\n await execAsync(`git fetch origin ${featureBranch}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 10000,\n }).catch(() => {});\n\n const { stdout: remoteHeadRaw } = await execAsync(\n `git rev-parse origin/${featureBranch}`,\n { cwd: workspacePath, encoding: 'utf-8' },\n );\n const remoteHead = remoteHeadRaw.trim();\n\n if (remoteHead !== headBefore) {\n console.log(`[merge-agent] Rebase complete for ${issueId}, new remote HEAD: ${remoteHead}`);\n logActivity('rebase_complete', `Rebase completed for ${issueId}`);\n return { success: true, reason: 'Rebase completed successfully' };\n }\n } catch {\n // Continue polling\n }\n\n // Check if specialist stopped\n if (!(await isRunning('merge-agent', mergeProjectKey ?? undefined))) {\n // Final check: maybe rebase succeeded just before specialist stopped\n try {\n await execAsync(`git fetch origin ${featureBranch}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n }).catch(() => {});\n const { stdout } = await execAsync(`git rev-parse origin/${featureBranch}`, {\n cwd: workspacePath,\n encoding: 'utf-8',\n });\n if (stdout.trim() !== headBefore) {\n console.log(`[merge-agent] Rebase detected after specialist stopped for ${issueId}`);\n return { success: true, reason: 'Rebase completed (detected after specialist stopped)' };\n }\n } catch {}\n\n return {\n success: false,\n reason: 'merge-agent specialist stopped before completing rebase',\n };\n }\n }\n\n logActivity('rebase_timeout', `Rebase timed out for ${issueId}`);\n return {\n success: false,\n reason: 'Timeout waiting for rebase to complete (10 minutes)',\n };\n}\n\n/**\n * Salvage a stranded merge commit — if the specialist merged locally but died\n * before pushing, detect the unpushed merge and push it ourselves.\n *\n * Returns a success result if salvaged, or null if nothing to salvage.\n */\nasync function salvageStrandedMerge(\n projectPath: string,\n targetBranch: string,\n headBefore: string,\n issueId: string,\n logActivity: (action: string, detail: string) => void,\n): Promise<{ success: boolean; reason?: string } | null> {\n try {\n const { stdout: currentHeadRaw } = await execAsync('git rev-parse HEAD', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const currentHead = currentHeadRaw.trim();\n\n if (currentHead === headBefore) {\n // No local merge happened — nothing to salvage\n return null;\n }\n\n // Local HEAD changed — check if it's ahead of remote\n await execAsync(`git fetch origin ${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: 10000,\n }).catch(() => {});\n\n const { stdout: remoteHeadRaw } = await execAsync(`git rev-parse origin/${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n\n if (remoteHeadRaw.trim() === currentHead) {\n // Already pushed (maybe by another process)\n console.log(`[merge-agent] Salvage check: merge already pushed`);\n return { success: true };\n }\n\n // Stranded merge detected — push it\n console.log(`[merge-agent] SALVAGING stranded merge for ${issueId}: local HEAD ${currentHead.slice(0, 8)} != remote ${remoteHeadRaw.trim().slice(0, 8)}`);\n logActivity('merge_salvage', `Pushing stranded merge commit ${currentHead.slice(0, 8)} for ${issueId}`);\n\n await execAsync(`git push origin ${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: 30000,\n });\n\n console.log(`[merge-agent] Salvage push successful for ${issueId}`);\n logActivity('merge_salvage_success', `Stranded merge pushed successfully`);\n return { success: true };\n } catch (error: any) {\n console.error(`[merge-agent] Salvage failed: ${error.message}`);\n logActivity('merge_salvage_failed', `Salvage push failed: ${error.message}`);\n return null;\n }\n}\n\n/**\n * Result of syncing main into a workspace branch\n */\nexport interface SyncMainResult {\n success: boolean;\n alreadyUpToDate?: boolean;\n commitCount?: number;\n changedFiles?: string[];\n conflictFiles?: string[];\n reason?: string;\n}\n\n/**\n * Scan workspace for leftover git conflict markers (async)\n */\nexport async function scanForConflictMarkers(projectPath: string): Promise<string[]> {\n try {\n // git diff --check exits non-zero and prints filenames when conflict markers exist\n const { stdout } = await execAsync('git diff --check 2>&1 || true', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const files = stdout\n .split('\\n')\n .filter(line => line.includes('leftover conflict marker'))\n .map(line => line.split(':')[0].trim())\n .filter(f => f.length > 0);\n return [...new Set(files)];\n } catch {\n return [];\n }\n}\n\n/**\n * Sync the latest main branch into a workspace's feature branch.\n *\n * This performs a `git merge origin/main` in the workspace. If the merge is clean\n * it returns immediately. If conflicts arise, the merge-agent specialist is woken\n * to resolve them. The merge is never pushed — this is a local workspace operation.\n *\n * Auto-commits any uncommitted changes before merging (with safety verification).\n */\nexport async function syncMainIntoWorkspace(\n projectPath: string,\n issueId: string,\n): Promise<SyncMainResult> {\n console.log(`[sync-main] Starting sync of main into workspace for ${issueId}`);\n logActivity('sync_main_start', `Starting sync for ${issueId}`);\n\n // Pre-flight: auto-commit uncommitted changes before merge\n try {\n const { stdout: statusOut } = await execAsync('git status --porcelain', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n if (statusOut.trim()) {\n console.log(`[sync-main] Uncommitted changes detected, auto-committing...`);\n logActivity('sync_main_auto_commit', `Auto-committing uncommitted changes before sync`);\n try {\n await execAsync('git add -A && git commit -m \"WIP: auto-commit before sync with main\"', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n console.log(`[sync-main] Auto-commit successful`);\n } catch (commitErr: any) {\n const message = `Failed to auto-commit uncommitted changes: ${commitErr.message}`;\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n\n // Verify commit succeeded — abort if uncommitted changes still exist\n const { stdout: postCommitStatus } = await execAsync('git status --porcelain', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n if (postCommitStatus.trim()) {\n const message = 'Uncommitted changes remain after auto-commit — aborting sync';\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n }\n } catch (error: any) {\n return { success: false, reason: `Failed to check git status: ${error.message}` };\n }\n\n // Clean up stale git lock files\n try {\n const lockCleanup = await cleanupStaleLocks(projectPath);\n if (lockCleanup.found.length > 0) {\n console.log(`[sync-main] Found ${lockCleanup.found.length} lock file(s)`);\n if (lockCleanup.removed.length > 0) {\n console.log(`[sync-main] Cleaned up ${lockCleanup.removed.length} stale lock file(s)`);\n logActivity('git_lock_cleanup', `Removed ${lockCleanup.removed.length} stale lock file(s)`);\n }\n if (lockCleanup.errors.some((e: { file: string; error: string }) => e.error.includes('Git processes are running'))) {\n const message = 'Git processes are still running — cannot safely start sync';\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n }\n } catch (lockErr: any) {\n console.warn(`[sync-main] Lock cleanup warning: ${lockErr.message} (continuing)`);\n }\n\n // Fetch latest main\n try {\n console.log(`[sync-main] Fetching origin/main...`);\n await execAsync('git fetch origin main', { cwd: projectPath, encoding: 'utf-8' });\n } catch (error: any) {\n return { success: false, reason: `Failed to fetch origin/main: ${error.message}` };\n }\n\n // Attempt the merge\n let mergeOutput = '';\n let hasConflicts = false;\n try {\n const result = await execAsync('git merge origin/main', { cwd: projectPath, encoding: 'utf-8' });\n mergeOutput = (result.stdout || '') + (result.stderr || '');\n } catch (error: any) {\n mergeOutput = (error.stdout || '') + (error.stderr || '');\n hasConflicts = true;\n }\n\n // Already up to date?\n if (mergeOutput.includes('Already up to date') || mergeOutput.includes('Already up-to-date')) {\n console.log(`[sync-main] Already up to date`);\n logActivity('sync_main_noop', `${issueId} already up to date with main`);\n return { success: true, alreadyUpToDate: true };\n }\n\n if (!hasConflicts) {\n // Clean merge — collect stats\n console.log(`[sync-main] Clean merge completed`);\n logActivity('sync_main_success', `Clean merge of main into ${issueId}`);\n\n let changedFiles: string[] = [];\n let commitCount = 0;\n try {\n const { stdout: diffFiles } = await execAsync(\n 'git diff --name-only ORIG_HEAD HEAD 2>/dev/null || git diff --name-only HEAD~1 HEAD',\n { cwd: projectPath, encoding: 'utf-8' },\n );\n changedFiles = diffFiles.trim().split('\\n').filter(f => f.length > 0);\n } catch { /* non-fatal */ }\n try {\n const { stdout: logOut } = await execAsync(\n 'git log ORIG_HEAD..HEAD --oneline 2>/dev/null || echo \"\"',\n { cwd: projectPath, encoding: 'utf-8' },\n );\n commitCount = logOut.trim().split('\\n').filter(l => l.length > 0).length;\n } catch { /* non-fatal */ }\n\n return { success: true, commitCount, changedFiles };\n }\n\n // Conflict case — delegate to merge-agent specialist\n const conflictFiles = await getConflictFiles(projectPath);\n console.log(`[sync-main] ${conflictFiles.length} conflict(s), waking merge-agent...`);\n logActivity('sync_main_conflicts', `${conflictFiles.length} conflict(s) in ${issueId}: ${conflictFiles.join(', ')}`);\n\n const workspaceBranch = await execAsync('git branch --show-current', { cwd: projectPath, encoding: 'utf-8' })\n .then(r => r.stdout.trim())\n .catch(() => `feature/${issueId.toLowerCase()}`);\n\n // Build prompt from template\n const promptPath = join(__dirname, 'prompts', 'sync-main.md');\n let taskPrompt: string;\n try {\n const template = readFileSync(promptPath, 'utf-8');\n taskPrompt = template\n .replace(/{{projectPath}}/g, projectPath)\n .replace(/{{workspaceBranch}}/g, workspaceBranch)\n .replace(/{{issueId}}/g, issueId)\n .replace(/{{conflictFiles}}/g, conflictFiles.map(f => `- ${f}`).join('\\n'));\n } catch (templateErr: any) {\n console.error(`[sync-main] Could not load sync-main.md template: ${templateErr.message}`);\n logActivity('sync_main_error', `Template load failed: ${templateErr.message}`);\n try { await execAsync('git merge --abort', { cwd: projectPath, encoding: 'utf-8' }); } catch {}\n return { success: false, conflictFiles, reason: 'Internal error: sync-main prompt template not found' };\n }\n\n // Wake the merge-agent specialist using per-project ephemeral lifecycle when possible\n const syncResolvedProject = resolveProjectFromIssue(issueId);\n const syncProjectKey = syncResolvedProject?.projectKey ?? null;\n let syncWakeResult: { success: boolean; message: string; tmuxSession?: string; error?: string };\n if (syncProjectKey) {\n syncWakeResult = await spawnEphemeralSpecialist(syncProjectKey, 'merge-agent', {\n issueId,\n branch: workspaceBranch,\n workspace: projectPath,\n promptOverride: taskPrompt,\n });\n } else {\n syncWakeResult = await wakeSpecialist('merge-agent', taskPrompt, {\n waitForReady: true,\n startIfNotRunning: true,\n issueId,\n });\n }\n\n if (!syncWakeResult.success) {\n try { await execAsync('git merge --abort', { cwd: projectPath, encoding: 'utf-8' }); } catch {}\n const message = `Failed to wake merge-agent specialist: ${syncWakeResult.message}`;\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_error', message);\n return { success: false, conflictFiles, reason: message };\n }\n\n console.log(`[sync-main] Specialist woken, waiting for conflict resolution...`);\n logActivity('sync_main_agent_woken', `Agent resolving ${conflictFiles.length} conflict(s) for ${issueId}`);\n\n // Poll tmux output for MERGE_RESULT markers\n const tmuxSession = getTmuxSessionName('merge-agent', syncProjectKey ?? undefined);\n const startTime = Date.now();\n const POLL_INTERVAL = 5000;\n const SYNC_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes\n let lastOutput = '';\n\n while (Date.now() - startTime < SYNC_TIMEOUT_MS) {\n await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));\n\n const output = await captureTmuxOutput(tmuxSession);\n if (output !== lastOutput) {\n lastOutput = output;\n const hasStructured = output.includes('MERGE_RESULT:');\n const lowerOutput = output.toLowerCase();\n const hasHumanReadable =\n lowerOutput.includes('merge task complete') ||\n lowerOutput.includes('successfully merged') ||\n lowerOutput.includes('merge complete') ||\n lowerOutput.includes('merge failed') ||\n lowerOutput.includes('merge task failed');\n\n if (hasStructured || hasHumanReadable) {\n const agentResult = parseAgentOutput(output);\n\n if (agentResult.success) {\n // Verify no leftover conflict markers\n const remaining = await scanForConflictMarkers(projectPath);\n if (remaining.length > 0) {\n try { await execAsync('git merge --abort', { cwd: projectPath, encoding: 'utf-8' }); } catch {}\n const msg = `Agent reported success but ${remaining.length} conflict marker(s) remain in: ${remaining.join(', ')}`;\n console.error(`[sync-main] ${msg}`);\n logActivity('sync_main_markers_remain', msg);\n return { success: false, conflictFiles, reason: msg };\n }\n\n console.log(`[sync-main] ✓ Conflicts resolved by agent`);\n logActivity('sync_main_success', `Merge agent resolved conflicts for ${issueId}`);\n\n // Collect stats\n let changedFiles: string[] = [];\n let commitCount = 0;\n try {\n const { stdout: diffFiles } = await execAsync(\n 'git diff --name-only ORIG_HEAD HEAD',\n { cwd: projectPath, encoding: 'utf-8' },\n );\n changedFiles = diffFiles.trim().split('\\n').filter(f => f.length > 0);\n const { stdout: logOut } = await execAsync(\n 'git log ORIG_HEAD..HEAD --oneline',\n { cwd: projectPath, encoding: 'utf-8' },\n );\n commitCount = logOut.trim().split('\\n').filter(l => l.length > 0).length;\n } catch { /* non-fatal */ }\n\n return { success: true, commitCount, changedFiles };\n } else {\n // Agent failed — ensure merge is aborted\n try { await execAsync('git merge --abort', { cwd: projectPath, encoding: 'utf-8' }); } catch {}\n console.log(`[sync-main] ✗ Agent could not resolve conflicts`);\n logActivity('sync_main_agent_failed', `Agent failed to resolve conflicts for ${issueId}`);\n return {\n success: false,\n conflictFiles,\n reason: agentResult.reason || 'Merge agent could not resolve conflicts',\n };\n }\n }\n }\n\n const elapsed = Math.round((Date.now() - startTime) / 1000);\n if (elapsed % 30 === 0) {\n console.log(`[sync-main] Still waiting for agent... (${elapsed}s elapsed)`);\n }\n }\n\n // Timeout\n try { await execAsync('git merge --abort', { cwd: projectPath, encoding: 'utf-8' }); } catch {}\n logActivity('sync_main_timeout', `Sync timed out for ${issueId}`);\n return {\n success: false,\n conflictFiles,\n reason: `Timeout: merge agent did not complete within ${SYNC_TIMEOUT_MS / 60000} minutes`,\n };\n}\n\n/**\n * Look up and run quality gates for the project at projectPath.\n * Returns empty array if no quality gates are configured.\n *\n * In polyrepo mode (projectPath is a sub-repo of project.path), only gates\n * whose `path` field matches the relative sub-repo path are run. Gates with\n * no `path` field are skipped in polyrepo context.\n */\nexport async function runProjectQualityGates(\n projectPath: string,\n phase: 'pre_push' | 'post_push'\n): Promise<import('./validation.js').QualityGateResult[]> {\n try {\n const config = loadProjectsConfig();\n // Find the project whose path matches\n const project = Object.values(config.projects).find(p => projectPath.startsWith(p.path));\n if (!project?.quality_gates || Object.keys(project.quality_gates).length === 0) {\n console.log(`[merge-agent] No quality gates configured for ${projectPath}`);\n return [];\n }\n\n // Detect polyrepo context: if projectPath is a subdirectory of project.path,\n // repoRelPath is non-empty (e.g., 'frontend' or 'backend').\n const repoRelPath = relative(project.path, projectPath);\n\n let gatesToRun = project.quality_gates;\n if (repoRelPath && !repoRelPath.startsWith('..')) {\n // Polyrepo: only run gates whose path matches this sub-repo\n const filtered = Object.entries(project.quality_gates).filter(\n ([, gate]) => gate.path === repoRelPath\n );\n if (filtered.length === 0) {\n console.log(`[merge-agent] No quality gates configured for repo path \"${repoRelPath}\"`);\n return [];\n }\n gatesToRun = Object.fromEntries(filtered);\n console.log(\n `[merge-agent] Polyrepo: running ${Object.keys(gatesToRun).length} gate(s) for path \"${repoRelPath}\"`\n );\n }\n\n console.log(`[merge-agent] Running ${phase} quality gates for project \"${project.name}\"`);\n return await runQualityGates(gatesToRun, projectPath, phase);\n } catch (error: any) {\n console.error(`[merge-agent] Failed to load quality gates: ${error.message}`);\n return [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;eAUmE;;;;;;AA2BnE,SAAgB,mBAAuC;CACrD,MAAM,QAA4B,EAAE;CAGpC,MAAM,UAAU,KAAK,SAAS,EAAE,kBAAkB;AAClD,KAAI,WAAW,QAAQ,EAAE;EAEvB,MAAM,aADU,aAAa,SAAS,QAAQ,CACnB,MAAM,oBAAoB;AACrD,MAAI,WACF,OAAM,KAAK,GAAG,WAAW,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,KAAI,MAAK;GACrD,MAAM,CAAC,UAAU,UAAU,EAAE,MAAM,CAAC,MAAM,IAAI;GAC9C,MAAM,CAAC,OAAO,SAAS,YAAY,IAAI,MAAM,IAAI;AACjD,UAAO;IAAE,OAAO,SAAS;IAAI,MAAM,QAAQ;IAAI,SAAS,UAAU,IAAI,aAAa;IAAE;IACrF,CAAC,QAAO,MAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC;;AAKlD,KAAI,MAAM,WAAW,EACnB,KAAI;EACF,MAAM,EAAE,aAAa,oBAAoB;AACzC,OAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,SAAS,CACnD,KAAI,QAAQ,aAAa;GACvB,MAAM,CAAC,OAAO,QAAQ,QAAQ,YAAY,MAAM,IAAI;GAEpD,MAAM,SAAS,eAAe,QAAQ,IAAI,IAAI,aAAa,CAAC,QAAQ,MAAM,GAAG;AAC7E,OAAI,SAAS,QAAQ,OACnB,OAAM,KAAK;IAAE;IAAO;IAAM,QAAQ,OAAO,aAAa;IAAE,CAAC;;SAIzD;AAGV,QAAO;;;;;AAMT,SAAgB,mBAAmB,SAAyB;AAC1D,QAAO,QAAQ,MAAM,IAAI,CAAC,GAAG,aAAa;;;;;;;;AAS5C,SAAgB,mBAAmB,SAAkC;CACnE,MAAM,SAAS,mBAAmB,QAAQ;CAC1C,MAAM,QAAQ,kBAAkB;AAEhC,MAAK,MAAM,cAAc,MACvB,KAAI,WAAW,WAAW,QAAQ;EAChC,MAAM,SAAS,SAAS,QAAQ,MAAM,IAAI,CAAC,IAAI,GAAG;AAClD,MAAI,CAAC,MAAM,OAAO,CAChB,QAAO;GAAE,UAAU;GAAM,GAAG;GAAY;GAAQ;;AAKtD,QAAO,EAAE,UAAU,OAAO;;;;;;;;;;AAoB5B,SAAgB,mBAAmB,SAAwC;AAEzE,KAAI,mBAAmB,QAAQ,CAAC,SAC9B,QAAO;CAIT,MAAM,SAAS,mBAAmB,QAAQ;AAC1C,KAAI;EACF,MAAM,EAAE,aAAa,oBAAoB;AACzC,OAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,SAAS,CAEnD,MADsB,eAAe,QAAQ,IAAI,IAAI,aAAa,CAAC,QAAQ,MAAM,GAAG,GACjE,aAAa,KAAK,QAAQ;AAE3C,OAAI,QAAQ,YAAa,QAAO;AAChC,OAAI,QAAQ,cAAe,QAAO;AAClC,UAAO;;SAGL;AAGR,QAAO;;;;;;;;;;;;uBChIoD;aACnB;AAE1C,MAAMA,cAAY,UAAU,KAAK;;;;AAuDjC,SAAS,sBAAsB,QAAgB,UAAoC;CACjF,MAAM,QAAQ,OAAO,MAAM,KAAK;CAEhC,MAAM,WAAgC,EAAE;CACxC,IAAI,uBAAuB;CAC3B,IAAI,cAA8B;CAClC,IAAI,cAA8B;CAGlC,IAAI,kBAAkB;CACtB,IAAI,eAAe;CACnB,IAAI,cAAc;CAElB,MAAM,gBAA0B,EAAE;AAElC,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,MAAM;AAG3B,MAAI,QAAQ,WAAW,gCAAgC,EAAE;AACvD,qBAAkB;AAClB,kBAAe;AACf,iBAAc;aACL,QAAQ,WAAW,gBAAgB,EAAE;AAC9C,qBAAkB;AAClB,kBAAe;AACf,iBAAc;aACL,QAAQ,WAAW,gBAAgB,EAAE;AAC9C,qBAAkB;AAClB,kBAAe;AACf,iBAAc;;AAIhB,MAAI;OACE,QAAQ,WAAW,kBAAkB,CACvC,wBAAuB;YACd,QAAQ,SAAS,IAAI,IAAI,CAAC,QAAQ,WAAW,QAAQ,CAE9D,eAAc,KAAK,QAAQ;YAClB,QAAQ,WAAW,8BAA8B,CAC1D,wBAAuB;;AAK3B,MAAI;OACE,QAAQ,WAAW,iBAAiB,CACtC,eAAc;YACL,QAAQ,WAAW,sBAAsB,IACzC,QAAQ,SAAS,2CAA2C,CACrE,eAAc;YACL,QAAQ,SAAS,uBAAuB,CACjD,eAAc;;AAKlB,MAAI;OACE,QAAQ,WAAW,iBAAiB,CACtC,eAAc;YACL,QAAQ,WAAW,sBAAsB,IACzC,QAAQ,SAAS,4CAA4C,CACtE,eAAc;YACL,QAAQ,SAAS,sBAAsB,CAChD,eAAc;;;AAMpB,KAAI,qBACF,UAAS,KAAK;EACZ,MAAM;EACN,OAAO,cAAc,SAAS,IAAI,gBAAgB,KAAA;EAClD,SAAS;EACV,CAAC;AAGJ,KAAI,gBAAgB,MAClB,UAAS,KAAK;EACZ,MAAM;EACN,SAAS;EACV,CAAC;AAGJ,KAAI,gBAAgB,MAClB,UAAS,KAAK;EACZ,MAAM;EACN,SAAS;EACV,CAAC;AASJ,QAAO;EACL,SAAS;EACT,OAPY,aAAa,KACb,CAAC,yBACA,gBAAgB,QAAQ,gBAAgB,UACxC,gBAAgB,QAAQ,gBAAgB;EAKrD;EACA;EACA;EACA;EACA;EACD;;;;;;;;AASH,eAAsB,mBACpB,SAC2B;CAC3B,MAAM,EAAE,aAAa,qBAAqB;CAG1C,MAAM,aAAa,oBAAoB,KAAK,aAAa,WAAW,oBAAoB;AAGxF,KAAI,CAAC,WAAW,WAAW,EAAE;AAC3B,UAAQ,IAAI,wCAAwC,WAAW,2CAA2C;AAC1G,SAAO;GACL,SAAS;GACT,OAAO;GACP,SAAS;GACT,sBAAsB;GACtB,aAAa;GACb,aAAa;GACb,UAAU,EAAE;GACZ,QAAQ;GACT;;AAGH,SAAQ,IAAI,2CAA2C,aAAa;AACpE,SAAQ,IAAI,8BAA8B,cAAc;AAExD,KAAI;EAGF,MAAM,MAAM,EAAE,GAAG,QAAQ,KAAK;AAC9B,MAAI,QAAQ,yBAAyB,KAAA,GAAW;AAC9C,OAAI,oBAAoB,OAAO,QAAQ,qBAAqB;AAC5D,WAAQ,IAAI,0CAA0C,QAAQ,qBAAqB,wBAAwB;;EAG7G,MAAM,EAAE,QAAQ,WAAW,MAAMA,YAC/B,SAAS,WAAW,KAAK,YAAY,IACrC;GACE,KAAK;GACL;GACA,WAAW,KAAK,OAAO;GACvB,SAAS,MAAU;GACpB,CACF;EAED,MAAM,SAAS,SAAS;AAExB,UAAQ,IAAI,mCAAmC;AAE/C,SAAO,sBAAsB,QAAQ,EAAE;UAChC,OAAY;EAEnB,MAAM,WAAW,MAAM,QAAQ;EAC/B,MAAM,UAAU,MAAM,UAAU,OAAO,MAAM,UAAU;AAEvD,UAAQ,IAAI,+CAA+C,SAAS,GAAG;AAKvE,SAFe,sBAAsB,QAAQ,SAAS;;;;;;;;;;;;;AAgB1D,eAAsB,gBAAgB,aAAuC;AAC3E,SAAQ,IAAI,wCAAwC,cAAc;AAElE,KAAI;EAEF,MAAM,EAAE,QAAQ,iBAAiB,MAAMA,YAAU,sBAAsB,EACrE,KAAK,aACN,CAAC;AAKF,QAAMA,YAAU,8BAA8B,EAC5C,KAAK,aACN,CAAC;EAGF,MAAM,EAAE,QAAQ,gBAAgB,MAAMA,YAAU,sBAAsB,EACpE,KAAK,aACN,CAAC;AAEF,UAAQ,IACN,0CAA0C,aAAa,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,kBACxF;AAED,SAAO;UACA,OAAY;AACnB,UAAQ,MAAM,sCAAsC,MAAM,QAAQ;AAClE,SAAO;;;;;;;AAsCX,MAAa,gBAAmD;CAC9D,WAAW,EAAE,SAAS,0BAA0B;CAChD,MAAM,EAAE,SAAS,qBAAqB;CACtC,MAAM,EAAE,SAAS,iBAAiB;CACnC;;;;;;;;;;;;;;;;AAiBD,eAAsB,gBACpB,OACA,aACA,QAAkC,YAClC,OAA8B,EAAE,EACF;AAC9B,KAAI,KAAK,YAAY,CAAC,KAAK,OACzB,OAAM,IAAI,MAAM,mCAAmC;AAErD,KAAI,KAAK,YAAY,KAAK,QAAQ;AAIhC,MAAI,CAAC,uBAAuB,KAAK,KAAK,OAAO,CAC3C,OAAM,IAAI,MAAM,2BAA2B,KAAK,SAAS;AAE3D,MAAI,CAAC,sBAAsB,KAAK,YAAY,CAC1C,OAAM,IAAI,MAAM,8CAA8C,cAAc;;CAGhF,MAAM,UAA+B,EAAE;AAEvC,MAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,MAAM,EAAE;AAEhD,OADkB,KAAK,SAAS,gBACd,MAAO;EAEzB,MAAM,WAAW,KAAK,aAAa;EACnC,MAAM,MAAM,KAAK,OAAO,KAAK,aAAa,KAAK,KAAK,GAAG;AAEvD,UAAQ,IAAI,2BAA2B,KAAK,KAAK,WAAW,aAAa,WAAW,OAAO,MAAM;EACjG,MAAM,YAAY,KAAK,KAAK;AAE5B,MAAI,KAAK,SAAS,eAAe;GAE/B,MAAM,SAAS,MAAM,kBAAkB,MAAM,MAAM,SAAS;AAC5D,WAAQ,KAAK,OAAO;AACpB,OAAI,CAAC,OAAO,UAAU,UAAU;AAC9B,YAAQ,IAAI,mCAAmC,KAAK,qBAAqB;AACzE;;AAEF;;EAOF,MAAM,WAAW,KAAK,YAAY,KAAK;EACvC,IAAI;AACJ,MAAI,UAAU;AAGZ,OAAI,CAAC,sBAAsB,KAAK,IAAI,CAClC,OAAM,IAAI,MAAM,SAAS,KAAK,gDAAgD,MAAM;AAKtF,OAAI,KAAK,QAAQ,SAAS,KAAI,CAC5B,OAAM,IAAI,MAAM,SAAS,KAAK,kEAAkE;AAGlG,qBAAkB,sBADC,YAAY,CAAC,QAAQ,KAAK,OAAO,iBACD,UAAU,IAAI,MAAM,KAAK,QAAQ;aAC3E,KAAK,aAAa,KAAK,gBAAgB;GAEhD,IAAI,gBAAgB,KAAK;AACzB,OAAI,KAAK,aACP,iBAAgB,oBAAoB,eAAe,KAAK,aAAa;GAKvE,MAAM,mBAAmB,KAAK,OAAO,uBAAuB,KAAK,SAAS;AAK1E,qBAAkB,eAHD,KAAK,MAClB,OAAO,QAAQ,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,GACpE,GACsC,OAAO,iBAAiB,KAAK,cAAc,IAAI,KAAK;AAC9F,WAAQ,IAAI,wCAAwC,cAAc,aAAa,iBAAiB,GAAG;QAEnG,mBAAkB,KAAK;AAGzB,MAAI;GAEF,MAAM,aAAa,CAAC,YAAY,EAAE,KAAK,aAAa,KAAK;GACzD,MAAM,MAAM;IAAE,GAAG,QAAQ;IAAK,GAAG,KAAK;IAAK;GAC3C,MAAM,EAAE,QAAQ,WAAW,MAAMA,YAAU,iBAAiB;IAC1D,KAAK,aAAa,MAAM,KAAA;IACxB;IACA,WAAW,KAAK,OAAO;IACvB,SAAS,MAAS;IACnB,CAAC;GAEF,MAAM,aAAa,KAAK,KAAK,GAAG;AAChC,WAAQ,IAAI,qBAAqB,KAAK,YAAY,WAAW,KAAK;AAClE,WAAQ,KAAK;IACX;IACA,QAAQ;IACR;IACA,SAAS,SAAS,QAAQ,MAAM,KAAM;IACtC;IACD,CAAC;WACK,OAAY;GACnB,MAAM,aAAa,KAAK,KAAK,GAAG;GAChC,MAAM,WAAW,MAAM,UAAU,OAAO,MAAM,UAAU,KAAK,MAAM,KAAM;AACzE,WAAQ,IAAI,qBAAqB,KAAK,YAAY,WAAW,OAAO,MAAM,SAAS,MAAM,GAAG,IAAI,GAAG;AACnG,WAAQ,KAAK;IACX;IACA,QAAQ;IACR;IACA;IACA;IACA,OAAO,MAAM,SAAS,MAAM,GAAG,IAAI;IACpC,CAAC;AAEF,OAAI,UAAU;AACZ,YAAQ,IAAI,mCAAmC,KAAK,qBAAqB;AACzE;;;;CAKN,MAAM,SAAS,QAAQ,QAAO,MAAK,EAAE,OAAO,CAAC;CAC7C,MAAM,SAAS,QAAQ,QAAO,MAAK,CAAC,EAAE,OAAO,CAAC;AAC9C,SAAQ,IAAI,4BAA4B,OAAO,WAAW,OAAO,iBAAiB,QAAQ,OAAO,QAAQ;AAEzG,QAAO;;;;;AAMT,eAAe,kBACb,MACA,MACA,UAC4B;CAC5B,MAAM,MAAM,KAAK;AACjB,KAAI,CAAC,IACH,QAAO;EACL;EACA,QAAQ;EACR;EACA,QAAQ;EACR,YAAY;EACZ,OAAO;EACR;CAGH,MAAM,cAAc,KAAK,QAAQ;CACjC,MAAM,eAAe,KAAK,iBAAiB;CAC3C,MAAM,YAAY,KAAK,KAAK;AAE5B,SAAQ,IAAI,0BAA0B,YAAY,kCAAkC,MAAM;AAG1F,OAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,cAAc,IAAK,CAAC;AAErE,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YACvB,0DAA0D,IAAI,IAC9D,EAAE,SAAS,KAAK,KAAM,CACvB;EAED,MAAM,aAAa,SAAS,OAAO,MAAM,EAAE,GAAG;EAC9C,MAAM,SAAS,eAAe;EAC9B,MAAM,aAAa,KAAK,KAAK,GAAG;AAEhC,UAAQ,IAAI,+BAA+B,IAAI,IAAI,WAAW,aAAa,aAAa,MAAM,SAAS,SAAS,SAAS;AAEzH,SAAO;GACL;GACA;GACA;GACA,QAAQ,QAAQ,WAAW,QAAQ;GACnC;GACA,OAAO,SAAS,KAAA,IAAY,iBAAiB,aAAa,QAAQ;GACnE;UACM,OAAY;AACnB,SAAO;GACL;GACA,QAAQ;GACR;GACA,QAAQ,MAAM,WAAW;GACzB,YAAY,KAAK,KAAK,GAAG;GACzB,OAAO,wBAAwB,MAAM,SAAS,MAAM,GAAG,IAAI;GAC5D;;;;;;;;WC9gBqD;YAQrC;kBAUK;eAC+B;AAjBzD,MAAM,YAAY,UAAU,KAAK;AAGjC,MAAM,YAAY,QADC,cAAc,OAAO,KAAK,IAAI,CACZ;AAoBrC,MAAM,oBAAoB,KADF,KAAK,iBAAiB,cAAc,EACZ,cAAc;AAC9D,MAAM,qBAAqB,KAAK,mBAAmB,gBAAgB;;;;AA4CnE,MAAM,mBAAmB,MAAU;;;;AAKnC,SAAS,iBAAiB,SAAuC;CAC/D,MAAM,eAAe,KAAK,WAAW,WAAW,iBAAiB;AAEjE,KAAI,CAAC,WAAW,aAAa,CAC3B,OAAM,IAAI,MAAM,4CAA4C,eAAe;AAmB7E,QAAO,oDAhBU,aAAa,cAAc,QAAQ,CAIjD,QAAQ,wBAAwB,QAAQ,YAAY,CACpD,QAAQ,yBAAyB,QAAQ,aAAa,CACtD,QAAQ,yBAAyB,QAAQ,aAAa,CACtD,QAAQ,oBAAoB,QAAQ,QAAQ,CAC5C,QACC,0BACA,QAAQ,cAAc,KAAK,MAAM,OAAO,IAAI,CAAC,KAAK,KAAK,CACxD,CACA,QAAQ,wBAAwB,QAAQ,eAAe,OAAO,CAC9D,QAAQ,mBAAmB,QAAQ,IAAI,iBAAiB,oBAAoB,QAAQ,IAAI,YAAY,QAAQ,IAAI,QAAQ,SAAS,CAGlE;;;;;AAMpE,SAAS,kBAAkB,aAA6B;CAEtD,MAAM,kBAAkB,KAAK,aAAa,eAAe;AACzD,KAAI,WAAW,gBAAgB,CAC7B,KAAI;AAEF,MADoB,KAAK,MAAM,aAAa,iBAAiB,QAAQ,CAAC,CACtD,SAAS,KACvB,QAAO;SAEH;AAMV,KAAI,WAAW,KAAK,aAAa,UAAU,CAAC,CAC1C,QAAO;AAIT,KAAI,WAAW,KAAK,aAAa,aAAa,CAAC,CAC7C,QAAO;AAIT,KAAI,WAAW,KAAK,aAAa,aAAa,CAAC,IAAI,WAAW,KAAK,aAAa,WAAW,CAAC,CAC1F,QAAO;AAIT,QAAO;;;;;AAMT,eAAsB,iBAAiB,aAAqB,cAAqC;AAC/F,KAAI;AACF,UAAQ,IAAI,kEAAkE;EAG9E,MAAM,WAAW,KAAK,aAAa,QAAQ;AAC3C,MAAI,CAAC,WAAW,SAAS,EAAE;AACzB,WAAQ,IAAI,2DAA2D;AACvE;;EAIF,MAAM,EAAE,WAAW,MAAM,UAAU,oCAAoC;GACrE,KAAK;GACL,UAAU;GACX,CAAC;EAEF,MAAM,eAAe,OAClB,MAAM,CACN,MAAM,KAAK,CACX,QAAO,MAAK,EAAE,MAAM,CAAC,SAAS,EAAE,CAChC,QAAO,MAAK;GAEX,MAAM,MAAM,EAAE,MAAM,IAAI,CAAC,KAAK,EAAE,aAAa;AAC7C,UAAO,OAAO;IAAC;IAAM;IAAM;IAAO;IAAO;IAAM;IAAQ;IAAM;IAAM;IAAO;IAAK;IAAI,CAAC,SAAS,IAAI;IACjG;AAEJ,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAQ,IAAI,oEAAoE;AAChF;;AAGF,UAAQ,IAAI,uBAAuB,aAAa,OAAO,kCAAkC;EAGzF,MAAM,EAAE,yBAAyB,MAAM,OAAO;EAC9C,MAAM,cAAc,qBAAqB,aAAa,SAAS;AAI/D,MAAI,EADW,MAAM,YAAY,WAAW,EAChC,SAAS;AACnB,WAAQ,IAAI,+DAA+D;AAC3E;;AAIF,UAAQ,IAAI,8CAA8C;AAC1D,QAAM,YAAY,KAAK,KAAK;AAE5B,UAAQ,IAAI,kDAAkD;AAC9D,cAAY,iBAAiB,mCAAmC,aAAa,OAAO,QAAQ;UACrF,OAAY;AAEnB,UAAQ,KAAK,+CAA+C,MAAM,UAAU;AAC5E,cAAY,qBAAqB,6BAA6B,MAAM,UAAU;;;;;;;;;;;;;;AAkBlF,MAAM,sCAAsB,IAAI,KAAa;AAK7C,MAAM,sCAAsB,IAAI,KAAqB;AACrD,MAAM,oBAAoB;AAE1B,eAAsB,mBAAmB,SAAiB,aAAqB,cAAuB,SAAmD;AAEvJ,KAAI,oBAAoB,IAAI,QAAQ,EAAE;AACpC,UAAQ,IAAI,0DAA0D,QAAQ,YAAY;AAC1F;;AAUF,KAAI,CAAC,SAAS,YAAY;EACxB,MAAM,cAAc,KAAK,iBAAiB,0BAA0B;EACpE,MAAM,WAAW,UAAU,SAAS,QAAQ,GACxC,UAAU,QAAQ,cAAc,GAAG,GACnC,UAAU,QAAQ,eAAe,GAAG,CAAC,QAAQ,cAAc,GAAG;EAClE,MAAM,eAAe,KAAK,UAAU,WAAW,uBAAuB;AAEtE,MAAI;AAOF,SAAM,UAAU,aANI,KAAK,UAAU;IACjC;IACA;IACA,cAAc,gBAAgB;IAC9B,WAAW,KAAK,KAAK;IACtB,CAAC,EACwC,QAAQ;AAClD,WAAQ,IAAI,+CAA+C,cAAc;GAEzE,MAAM,QAAQ,MAAM,cAAc;IAAC;IAAU;IAAS;IAAa,gBAAgB;IAAG,EAAE;IACtF,UAAU;IACV,OAAO;IACR,CAAC;AACF,SAAM,OAAO;AACb,WAAQ,IAAI,qDAAqD,MAAM,IAAI,wCAAwC;AACnH;WACO,KAAU;AACjB,WAAQ,KAAK,gDAAgD,IAAI,QAAQ,uEAAuE;;;AAIpJ,SAAQ,IAAI,gDAAgD,UAAU;AAGtE,KAAI;EACF,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,MAAM,YAAY,MAAM,QAAQ;GAAE;GAAS;GAAa,CAAC;AACzD,MAAI,UAAU,WAAW,CAAC,UAAU,SAAS;AAC3C,WAAQ,IAAI,mBAAmB,UAAU,SAAS,KAAK,KAAK,GAAG;AAC/D,eAAY,aAAa,SAAS,QAAQ,6BAA6B;aAC9D,UAAU,QACnB,SAAQ,IAAI,mCAAmC,UAAU,SAAS,KAAK,KAAK,GAAG;MAE/E,SAAQ,KAAK,kCAAkC,UAAU,QAAQ;UAE5D,KAAK;AACZ,UAAQ,KAAK,qCAAqC,MAAM;;AAI1D,KAAI;EACF,MAAM,EAAE,2BAA2B,MAAM,OAAO;EAChD,MAAM,cAAc,MAAM,uBAAuB;GAAE;GAAS;GAAa,CAAC;AAC1E,MAAI,YAAY,WAAW,CAAC,YAAY,SAAS;AAC/C,WAAQ,IAAI,mBAAmB,YAAY,SAAS,KAAK,KAAK,GAAG;AACjE,eAAY,8BAA8B,YAAY,SAAS,KAAK,KAAK,IAAI,6BAA6B;aACjG,YAAY,QACrB,SAAQ,IAAI,oDAAoD,YAAY,SAAS,KAAK,KAAK,GAAG;MAElG,SAAQ,KAAK,mDAAmD,YAAY,QAAQ;UAE/E,KAAK;AACZ,UAAQ,KAAK,qDAAqD,MAAM;;AAM1E,KAAI;EACF,MAAM,EAAE,wBAAwB,MAAM,OAAO;EAC7C,MAAM,aAAa,mBAAmB,QAAQ;EAI9C,MAAM,cAAc,MAAM,oBAHT,WAAW,WACxB;GAAE;GAAS;GAAa,QAAQ;IAAE,OAAO,WAAW;IAAO,MAAM,WAAW;IAAM,QAAQ,WAAW;IAAQ;GAAE,GAC/G;GAAE;GAAS;GAAa,CAC2B;AACvD,MAAI,YAAY,WAAW,CAAC,YAAY,SAAS;AAC/C,WAAQ,IAAI,mBAAmB,YAAY,SAAS,KAAK,KAAK,GAAG;AACjE,eAAY,kBAAkB,YAAY,SAAS,KAAK,KAAK,IAAI,iBAAiB;aACzE,YAAY,QACrB,SAAQ,IAAI,wCAAwC,YAAY,SAAS,KAAK,KAAK,GAAG;MAEtF,SAAQ,KAAK,mDAAmD,YAAY,QAAQ;UAE/E,KAAK;AACZ,UAAQ,KAAK,yCAAyC,MAAM;;AAO9D,8BAA6B,SAAS,YAAY;AAGlD,KAAI;EACF,MAAM,EAAE,iBAAiB,MAAM,OAAO;EACtC,MAAM,cAAc,MAAM,aAAa;GAAE;GAAS;GAAa,CAAC;AAChE,MAAI,YAAY,WAAW,CAAC,YAAY,SAAS;AAC/C,WAAQ,IAAI,mBAAmB,YAAY,SAAS,KAAK,KAAK,GAAG;AACjE,eAAY,6BAA6B,YAAY,SAAS,KAAK,KAAK,IAAI,kBAAkB;;UAEzF,KAAK;AACZ,UAAQ,KAAK,0CAA0C,MAAM;;AAK/D,KAAI;EACF,MAAM,EAAE,eAAe,mBAAmB,MAAM,OAAO;EACvD,MAAM,EAAE,aAAa,kBAAkB,MAAM,OAAO;EACpD,MAAM,UAAU,SAAS,QAAQ,aAAa;EAC9C,MAAM,aAAa,cAAc,QAAQ;AACzC,MAAI,cAAc,cAAc,QAAQ,EAAE;AACxC,eAAY,QAAQ;AACpB,cAAW,SAAS;AACpB,kBAAe,WAAW;AAC1B,WAAQ,IAAI,6CAA6C,QAAQ,oBAAoB;AACrF,eAAY,wBAAwB,4CAA4C,UAAU;;EAG5F,MAAM,aAAa,YAAY,QAAQ,aAAa;AACpD,MAAI,cAAc,WAAW,EAAE;AAC7B,eAAY,WAAW;AACvB,WAAQ,IAAI,iDAAiD,aAAa;;UAErE,KAAK;AACZ,UAAQ,KAAK,gDAAgD,MAAM;;AAMrE,KAAI;EACF,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAC3C,MAAM,EAAE,wBAAwB,MAAM,OAAO;EAC7C,MAAM,aAAa,QAAQ,aAAa;EACxC,MAAM,gBAAgB,kBAAkB,aAAa,WAAW;AAChE,MAAI,eAAe;GAEjB,MAAM,eAAe,MAAM,oBAAoB,eAD9B,SAAS,YAAY,EACkC,WAAW;AACnF,OAAI,aAAa,iBAAiB;AAChC,YAAQ,IAAI,8CAA8C,aAAa,MAAM,KAAK,KAAK,GAAG;AAC1F,gBAAY,kBAAkB,sBAAsB,QAAQ,IAAI,aAAa,MAAM,KAAK,KAAK,GAAG;;;UAG7F,KAAK;AACZ,UAAQ,KAAK,oDAAoD,MAAM;;AAIzE,qBAAoB,IAAI,QAAQ;AAEhC,SAAQ,IAAI,kDAAkD,QAAQ,6CAA6C;AACnH,aAAY,kBAAkB,UAAU,QAAQ,6CAA6C;;;;;;;AAQ/F,SAAS,6BAA6B,SAAiB,aAA2B;CAChF,MAAM,WAAW,oBAAoB,IAAI,QAAQ,IAAI;AACrD,KAAI,YAAY,mBAAmB;AACjC,UAAQ,IAAI,0CAA0C,QAAQ,gBAAgB,SAAS,uDAAuD;AAC9I;;AAIF,EAAC,YAAY;AACX,MAAI;GACF,MAAM,EAAE,eAAe,MAAM,OAAO;GACpC,MAAM,aAAa,mBAAmB,QAAQ;GAI9C,MAAM,UAAU,MAAM,WAHV,WAAW,WACnB;IAAE;IAAS;IAAa,QAAQ;KAAE,OAAO,WAAW;KAAO,MAAM,WAAW;KAAM,QAAQ,WAAW;KAAQ;IAAE,GAC/G;IAAE;IAAS;IAAa,EACU;IAAE,YAAY;IAAO,SAAS;IAA6C,CAAC;GAElH,IAAI,aAAa;AACjB,QAAK,MAAM,KAAK,QACd,KAAI,EAAE,WAAW,CAAC,EAAE,SAAS;AAC3B,YAAQ,IAAI,mBAAmB,EAAE,SAAS,KAAK,KAAK,GAAG;AACvD,gBAAY,EAAE,MAAM,EAAE,SAAS,KAAK,KAAK,IAAI,EAAE,KAAK;cAC3C,CAAC,EAAE,SAAS;AACrB,YAAQ,KAAK,mBAAmB,EAAE,KAAK,WAAW,EAAE,QAAQ;AAC5D,iBAAa;;AAIjB,OAAI,YAAY;IACd,MAAM,YAAY,oBAAoB,IAAI,QAAQ,IAAI,KAAK;AAC3D,wBAAoB,IAAI,SAAS,SAAS;AAC1C,QAAI,YAAY,kBACd,SAAQ,KAAK,6CAA6C,QAAQ,SAAS,SAAS,wDAAwD;SAI9I,qBAAoB,OAAO,QAAQ;WAE9B,KAAK;GACZ,MAAM,YAAY,oBAAoB,IAAI,QAAQ,IAAI,KAAK;AAC3D,uBAAoB,IAAI,SAAS,SAAS;AAC1C,WAAQ,KAAK,uDAAuD,SAAS,GAAG,kBAAkB,KAAK,MAAM;;KAE7G;;;;;AAMN,SAAgB,oBAAoB,SAAuB;AACzD,qBAAoB,OAAO,QAAQ;AACnC,qBAAoB,OAAO,QAAQ;;;;;AAMrC,SAAS,iBAAiB,QAA6B;CACrD,MAAM,QAAQ,OAAO,MAAM,KAAK;CAEhC,IAAI,cAA4C;CAChD,IAAI,gBAA0B,EAAE;CAChC,IAAI,cAAwB,EAAE;CAC9B,IAAI,cAA+C;CACnD,IAAI,mBAA2C;CAC/C,IAAI,SAAS;CACb,IAAI,QAAQ;AAEZ,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,KAAK,MAAM;AAG3B,MAAI,QAAQ,WAAW,gBAAgB,EAAE;GACvC,MAAM,QAAQ,QAAQ,UAAU,GAAuB,CAAC,MAAM;AAC9D,OAAI,UAAU,aAAa,UAAU,UACnC,eAAc;;AAKlB,MAAI,QAAQ,WAAW,kBAAkB,CAEvC,iBADc,QAAQ,UAAU,GAAyB,CAAC,MAAM,CAE7D,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE;AAIhC,MAAI,QAAQ,WAAW,gBAAgB,CAErC,eADc,QAAQ,UAAU,GAAuB,CAAC,MAAM,CAE3D,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,EAAE;AAIhC,MAAI,QAAQ,WAAW,SAAS,EAAE;GAChC,MAAM,QAAQ,QAAQ,UAAU,EAAgB,CAAC,MAAM;AACvD,OAAI,UAAU,UAAU,UAAU,UAAU,UAAU,OACpD,eAAc;;AAKlB,MAAI,QAAQ,WAAW,cAAc,EAAE;GACrC,MAAM,QAAQ,QAAQ,UAAU,GAAqB,CAAC,MAAM;AAC5D,OAAI,UAAU,UAAU,UAAU,OAChC,oBAAmB;;AAKvB,MAAI,QAAQ,WAAW,UAAU,CAC/B,UAAS,QAAQ,UAAU,EAAiB,CAAC,MAAM;AAIrD,MAAI,QAAQ,WAAW,SAAS,CAC9B,SAAQ,QAAQ,UAAU,EAAgB,CAAC,MAAM;;AAKrD,KAAI,gBAAgB,UAClB,QAAO;EACL,SAAS;EACT;EACA,aAAa,eAAe;EAC5B,kBAAkB,oBAAoB;EACtC;EACA;EACD;UACQ,gBAAgB,UACzB,QAAO;EACL,SAAS;EACT;EACA,kBAAkB,oBAAoB;EACtC;EACA;EACA;EACD;MACI;EAGL,MAAM,cAAc,OAAO,aAAa;EAGxC,MAAM,oBAAoB;GACxB;GACA;GACA;GACA;GACA;GACD;EAED,MAAM,oBAAoB;GACxB;GACA;GACA;GACA;GACD;EAED,MAAM,sBAAsB,kBAAkB,MAAK,MAAK,YAAY,SAAS,EAAE,CAAC;EAChF,MAAM,sBAAsB,kBAAkB,MAAK,MAAK,YAAY,SAAS,EAAE,CAAC;AAEhF,MAAI,uBAAuB,CAAC,qBAAqB;GAE/C,IAAI,qBAA+C;AACnD,OAAI,YAAY,SAAS,cAAc,IAAI,YAAY,SAAS,eAAe,IAC3E,OAAO,MAAM,aAAa,CAC5B,sBAAqB;YACZ,YAAY,SAAS,cAAc,IAAI,YAAY,SAAS,eAAe,CACpF,sBAAqB;AAGvB,WAAQ,IAAI,4DAA4D;AACxE,UAAO;IACL,SAAS;IACT,aAAa;IACb,kBAAkB;IAClB,OAAO;IACP;IACD;;AAGH,MAAI,qBAAqB;AACvB,WAAQ,IAAI,4DAA4D;AACxE,UAAO;IACL,SAAS;IACT,kBAAkB;IAClB,QAAQ;IACR;IACD;;AAIH,SAAO;GACL,SAAS;GACT,kBAAkB;GAClB,QAAQ;GACR;GACD;;;;;;AAOL,eAAe,iBAAiB,aAAwC;AACtE,KAAI;EACF,MAAM,EAAE,QAAQ,WAAW,MAAM,UAAU,wCAAwC;GACjF,KAAK;GACL,UAAU;GACX,CAAC;AAEF,SAAO,OACJ,MAAM,KAAK,CACX,KAAK,SAAS,KAAK,MAAM,CAAC,CAC1B,QAAQ,SAAS,KAAK,SAAS,EAAE;UAC7B,OAAO;AACd,UAAQ,MAAM,iCAAiC,MAAM;AACrD,SAAO,EAAE;;;;;;AAOb,SAAS,gBAAgB,SAA+B,QAAqB,WAA0B;AAErG,KAAI,CAAC,WAAW,kBAAkB,CAChC,WAAU,mBAAmB,EAAE,WAAW,MAAM,CAAC;CAGnD,MAAM,QAA2B;EAC/B,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS,QAAQ;EACjB,cAAc,QAAQ;EACtB,cAAc,QAAQ;EACtB,eAAe,QAAQ;EACvB,QAAQ;GACN,GAAG;GACH,QAAQ,KAAA;GACT;EACD;EACD;AAED,gBAAe,oBAAoB,KAAK,UAAU,MAAM,GAAG,MAAM,QAAQ;;;;;AAM3E,SAAS,YAAY,QAAgB,SAAuB;CAC1D,MAAM,eAAe;AACrB,KAAI;EACF,MAAM,QAAQ;GACZ,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,QAAQ;GACR;GACA;GACD;AACD,iBAAe,cAAc,KAAK,UAAU,MAAM,GAAG,KAAK;SACpD;;;;;AAQV,eAAe,kBAAkB,aAAsC;AACrE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,UAAU,yBAAyB,YAAY,OAAO,EAAE,UAAU,SAAS,CAAC;AACrG,SAAO;SACD;AACN,SAAO;;;;;;AAOX,eAAe,sBAAwC;AACrD,KAAI;AACF,QAAM,UAAU,0DAA0D,EAAE,UAAU,SAAS,CAAC;AAChG,SAAO;SACD;AACN,SAAO;;;;;;AAOX,eAAe,mBAAmB,SAAiB,SAAmC;CAEpF,MAAM,cAAc,SAAS,QAAQ,aAAa;AAElD,KAAI;AAEF,MAAI,CAAC,cAAc,YAAY,EAAE;AAC/B,WAAQ,IAAI,2CAA2C,YAAY,2BAA2B;AAC9F,UAAO;;AAIT,QAAM,cAAc,aAAa,QAAQ;AAEzC,UAAQ,IAAI,iCAAiC,cAAc;AAC3D,cAAY,iBAAiB,WAAW,YAAY,IAAI,QAAQ,MAAM,GAAG,IAAI,CAAC,KAAK;AACnF,SAAO;SACD;AACN,UAAQ,IAAI,2CAA2C,YAAY,0BAA0B;AAC7F,SAAO;;;;;;;;;AAUX,eAAsB,gBAAgB,SAAqD;AACzF,SAAQ,IAAI,kDAAkD,QAAQ,UAAU;AAChF,aAAY,eAAe,sBAAsB,QAAQ,QAAQ,IAAI,QAAQ,cAAc,KAAK,KAAK,GAAG;AAGxG,KAAI,CAAC,QAAQ,YACX,SAAQ,cAAc,kBAAkB,QAAQ,YAAY;CAG9D,MAAM,cAAc,mBAAmB,cAAc;AACrD,SAAQ,IAAI,qCAAqC,cAAc;AAC/D,SAAQ,IAAI,+BAA+B,QAAQ,cAAc;AAGjE,KAAI,CAAE,MAAM,qBAAqB,EAAG;AAClC,UAAQ,IAAI,oDAAoD;AAChE,cAAY,eAAe,WAAW,YAAY,cAAc;AAChE,SAAO;GACL,SAAS;GACT,QAAQ,cAAc,YAAY;GACnC;;CAIH,MAAM,SAAS,iBAAiB,QAAQ;AAExC,KAAI;AAEF,UAAQ,IAAI,iCAAiC,YAAY,KAAK;AAC9D,QAAM,cAAc,aAAa,OAAO;AAGxC,aAAW,cAAc;AACzB,cAAY,mBAAmB,gBAAgB,cAAc;AAE7D,UAAQ,IAAI,qDAAqD;EAGjE,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,gBAAgB;EACtB,IAAI,aAAa;AAEjB,SAAO,KAAK,KAAK,GAAG,YAAY,kBAAkB;AAChD,SAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,cAAc,CAAC;GAEhE,MAAM,SAAS,MAAM,kBAAkB,YAAY;AAGnD,OAAI,WAAW,YAAY;AACzB,iBAAa;IACb,MAAM,cAAc,OAAO,aAAa;IAGxC,MAAM,sBAAsB,OAAO,SAAS,gBAAgB;IAC5D,MAAM,yBACJ,YAAY,SAAS,sBAAsB,IAC3C,YAAY,SAAS,sBAAsB,IAC3C,YAAY,SAAS,iBAAiB,IACtC,YAAY,SAAS,eAAe,IACpC,YAAY,SAAS,oBAAoB;AAE3C,QAAI,uBAAuB,wBAAwB;AACjD,aAAQ,IAAI,6DAA6D,oBAAoB,oBAAoB,uBAAuB,GAAG;KAE3I,MAAM,SAAS,iBAAiB,OAAO;AAGvC,SAAI,OAAO,SAAS;AAClB,cAAQ,IAAI,yEAAyE;AACrF,kBAAY,0BAA0B,0BAA0B,QAAQ,UAAU;MAIlF,MAAM,gBAAgB,OAAO,MAAM,yBAAyB;MAC5D,MAAM,uBAAuB,gBAAgB,SAAS,cAAc,IAAI,GAAG,GAAG,KAAA;AAC9E,UAAI,yBAAyB,KAAA,EAC3B,SAAQ,IAAI,8DAA8D,uBAAuB;MAGnG,MAAM,mBAAmB,MAAM,mBAAmB;OAChD,aAAa,QAAQ;OACrB,SAAS,QAAQ;OACjB;OACD,CAAC;AAEF,UAAI,iBAAiB,OAAO;AAE1B,eAAQ,IAAI,oCAAoC;OAGhD,MAAM,kBADc,MAAM,uBAAuB,QAAQ,aAAa,WAAW,EAC9C,QAAO,MAAK,CAAC,EAAE,UAAU,EAAE,SAAS;AACvE,WAAI,eAAe,SAAS,GAAG;QAC7B,MAAM,cAAc,eAAe,KAAI,MAAK,EAAE,KAAK,CAAC,KAAK,KAAK;AAC9D,gBAAQ,IAAI,yCAAyC,cAAc;AACnE,oBAAY,2BAA2B,4BAA4B,QAAQ,QAAQ,IAAI,cAAc;QAOrG,MAAM,eAA4B;SAChC,SAAS;SACT,kBAAkB;SAClB,QAAQ,2BAA2B,YAAY,IAR3B,MAAM,gBAAgB,QAAQ,YAAY,GAE5D,uCACA;SAMF,OAAO,OAAO;SACd;SACD;AACD,wBAAgB,SAAS,aAAa;AACtC,eAAO;;AAGT,mBAAY,iBAAiB,sCAAsC,QAAQ,UAAU;AAGrF,cAAO,mBAAmB;AAC1B,uBAAgB,SAAS,OAAO;AAGhC,aAAM,mBAAmB,QAAQ,SAAS,QAAQ,aAAa,QAAQ,aAAa;AAGpF,aAAM,iBAAiB,QAAQ,aAAa,QAAQ,aAAa;AAEjE,cAAO;aACF;AAEL,eAAQ,IAAI,sCAAsC,iBAAiB,SAAS;AAC5E,mBAAY,yBAAyB,yBAAyB,QAAQ,QAAQ,IAAI,iBAAiB,SAAS,KAAI,MAAK,EAAE,KAAK,CAAC,KAAK,KAAK,GAAG;OAG1I,MAAM,gBAAgB,MAAM,gBAAgB,QAAQ,YAAY;OAEhE,MAAM,gBAAgB,iBAAiB,SAAS,KAAI,MAAK,GAAG,EAAE,KAAK,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK;OAC9F,MAAM,aAAa,gBACf,uCACA;AAEJ,eAAQ,IAAI,iBAAiB,aAAa;AAC1C,mBAAY,qBAAqB,WAAW;OAG5C,MAAM,eAA4B;QAChC,SAAS;QACT,kBAAkB;QAClB,QAAQ,sBAAsB,cAAc,IAAI;QAChD,OAAO,OAAO;QACd;QACD;AAED,uBAAgB,SAAS,aAAa;AACtC,cAAO;;YAEJ;AAEL,kBAAY,iBAAiB,oBAAoB,QAAQ,QAAQ,IAAI,OAAO,SAAS;AACrF,sBAAgB,SAAS,OAAO;AAChC,aAAO;;;;GAMb,MAAM,UAAU,KAAK,OAAO,KAAK,KAAK,GAAG,aAAa,IAAK;AAC3D,OAAI,UAAU,OAAO,EACnB,SAAQ,IAAI,mCAAmC,QAAQ,YAAY;;AAKvE,UAAQ,IAAI,+BAA+B,mBAAmB,IAAK,UAAU;AAC7E,cAAY,iBAAiB,uBAAuB,QAAQ,UAAU;AAEtE,SAAO;GACL,SAAS;GACT,QAAQ,iBAAiB,mBAAmB,IAAM;GAClD,QAAQ;GACT;UACM,OAAY;AACnB,UAAQ,MAAM,yBAAyB,MAAM;AAC7C,cAAY,eAAe,UAAU,MAAM,UAAU;EAErD,MAAM,SAAsB;GAC1B,SAAS;GACT,QAAQ,MAAM,WAAW;GAC1B;AAED,kBAAgB,SAAS,OAAO;AAChC,SAAO;;;;;;;;;;;;;;;;;;AAmBX,eAAsB,2BACpB,aACA,cACA,cACA,SACA,SACsB;AACtB,SAAQ,IAAI,gDAAgD,aAAa,QAAQ,eAAe;AAChG,aAAY,iBAAiB,gCAAgC,aAAa,MAAM,eAAe;AAG/F,KAAI;AAEF,UAAQ,IAAI,qDAAqD;EACjE,MAAM,cAAc,MAAM,kBAAkB,YAAY;AAExD,MAAI,YAAY,MAAM,SAAS,GAAG;AAChC,WAAQ,IAAI,uBAAuB,YAAY,MAAM,OAAO,eAAe;AAE3E,OAAI,YAAY,QAAQ,SAAS,GAAG;AAClC,YAAQ,IAAI,8BAA8B,YAAY,QAAQ,OAAO,sBAAsB;AAC3F,gBAAY,QAAQ,SAAQ,MAAK,QAAQ,IAAI,OAAO,IAAI,CAAC;AACzD,gBAAY,oBAAoB,WAAW,YAAY,QAAQ,OAAO,qBAAqB;;AAG7F,OAAI,YAAY,OAAO,SAAS,GAAG;AACjC,YAAQ,KAAK,mDAAmD,YAAY,OAAO;AACnF,QAAI,YAAY,OAAO,MAAK,MAAK,EAAE,MAAM,SAAS,4BAA4B,CAAC,EAAE;KAC/E,MAAM,UAAU;AAChB,aAAQ,MAAM,iBAAiB,UAAU;AACzC,iBAAY,iBAAiB,QAAQ;AACrC,YAAO;MAAE,SAAS;MAAO,QAAQ;MAAS;;;;AAMhD,MAAI;GACF,MAAM,EAAE,QAAQ,mBAAmB,MAAM,UAAU,gCAAgC,gBAAgB;IACjG,KAAK;IACL,UAAU;IACX,CAAC;AAEF,OAAI,CAAC,eAAe,MAAM,EAAE;IAC1B,MAAM,UAAU,UAAU,aAAa;AACvC,YAAQ,MAAM,iBAAiB,UAAU;AACzC,gBAAY,iBAAiB,QAAQ;IAErC,MAAM,EAAE,sBAAsB,MAAM,OAAO;IAC3C,MAAM,WAAW,8BAA8B,aAAa,gFAAgF,aAAa;IACzJ,MAAM,aAAa,MAAM,kBAAkB;KACzC;KACA,YAAY;KACZ,SAAS;KACT,SAAS,UAAU,aAAa;KAChC,cAAc;KACf,CAAC;AACF,QAAI,WAAW,QACb,OAAM,mBAAmB,SAAS,yDAAyD,QAAQ,uBAAuB,WAAW,eAAe;QAEpJ,SAAQ,MAAM,mDAAmD,QAAQ,IAAI,WAAW,QAAQ;AAElG,WAAO;KAAE,SAAS;KAAO,QAAQ;KAAS;;UAEtC;GACN,MAAM,UAAU,+BAA+B,aAAa;AAC5D,WAAQ,MAAM,iBAAiB,UAAU;AACzC,eAAY,iBAAiB,QAAQ;AACrC,UAAO;IAAE,SAAS;IAAO,QAAQ;IAAS;;UAQrC,OAAY;AACnB,SAAO;GAAE,SAAS;GAAO,QAAQ,4BAA4B,MAAM;GAAW;;AAIhF,KAAI;AACF,QAAM,UAAU,oBAAoB,aAAa,GAAG,gBAAgB;GAClE,KAAK;GACL,UAAU;GACX,CAAC;EACF,IAAI,kBAAkB;AACtB,MAAI;AACF,SAAM,UACJ,uCAAuC,aAAa,UAAU,gBAC9D;IAAE,KAAK;IAAa,UAAU;IAAS,CACxC;AACD,qBAAkB;WACX,GAAQ;AAGf,OAAI,EAAE,SAAS,EACb,OAAM;;AAGV,MAAI,iBAAiB;GACnB,MAAM,UAAU,UAAU,aAAa,8BAA8B,aAAa;AAClF,WAAQ,IAAI,iBAAiB,UAAU;AACvC,eAAY,iBAAiB,QAAQ;AACrC,UAAO;IAAE,SAAS;IAAM,QAAQ;IAAS;;UAEpC,aAAkB;AACzB,UAAQ,KAAK,wCAAwC,YAAY,QAAQ,eAAe;;CAI1F,MAAM,EAAE,QAAQ,kBAAkB,MAAM,UAAU,sBAAsB;EACtE,KAAK;EACL,UAAU;EACX,CAAC;CACF,MAAM,aAAa,cAAc,MAAM;CAIvC,IAAI,eAAe;AACnB,KAAI;EACF,MAAM,EAAE,QAAQ,cAAc,MAAM,UAAU,0BAA0B;GACtE,KAAK;GACL,UAAU;GACX,CAAC;AACF,MAAI,UAAU,MAAM,EAAE;AACpB,SAAM,UAAU,gDAA+C,UAAU,MAAK;IAC5E,KAAK;IACL,UAAU;IACX,CAAC;AACF,kBAAe;AACf,WAAQ,IAAI,yDAAyD;;UAEhE,UAAe;AACtB,UAAQ,KAAK,kCAAkC,SAAS,QAAQ,sBAAsB;;CAIxF,MAAM,UAAU,QAAQ,IAAI,YAAY,QAAQ,IAAI,QAAQ;CAC5D,MAAM,SAAS,QAAQ,IAAI,iBAAiB,oBAAoB;CAOhE,MAAM,yBANiB,SAAS,kBAAkB,QAO9C;+EAEA;sBACgB,OAAO;;6CAEgB,QAAQ;;;CAInD,MAAM,aAAa,kBAAkB,QAAQ;;WAEpC,YAAY;iBACN,aAAa;iBACb,aAAa;;;;;QAKtB,YAAY;kBACF,aAAa;sBACT,aAAa;gBACnB,aAAa,eAAe,aAAa;4CACb,aAAa,YAAY,aAAa;;;4BAGtD,aAAa;;;;;8BAKX,aAAa;;;;;;eAM5B,aAAa;;;;;;;;;;;;;;;;;;;;;;iCAsBK,aAAa;;;MAGxC,uBAAuB,SAAS,SAAS,GAAG,eAAe;sBAC3C,OAAO;;6CAEgB,QAAQ;gBACrC;4BACY,aAAa;;4BAEb,aAAa;6BACZ,aAAa;;;qDAGW,aAAa;;MAE5D,uBAAuB;;;;;;;;;;;;;;;;;;;;CAsB3B,MAAM,kBAAkB,wBAAwB,QAAQ;CACxD,MAAM,kBAAkB,iBAAiB,cAAc;CACvD,MAAM,eAAe,mBAAmB,eAAe,mBAAmB,KAAA,EAAU;AAEpF,KAAI,CAAC,gBACH,SAAQ,KAAK,+CAA+C,QAAQ,0EAA0E;AAShJ,KAAI,iBAAiB;EACnB,MAAM,EAAE,sBAAsB,0BAA0B,MAAM,OAAO;EACrE,MAAM,qBAAqB;EAC3B,MAAM,gBAAgB;EACtB,MAAM,YAAY,KAAK,KAAK;AAE5B,SAAO,KAAK,KAAK,GAAG,YAAY,eAAe;GAC7C,MAAM,QAAQ,qBAAqB,aAAa;AAChD,OAAI,CAAC,SAAS,MAAM,UAAU,UAAU,MAAM,UAAU,YACtD;AAIF,OAAI;AACF,UAAM,UAAU,wBAAwB,aAAa,eAAe;WAC9D;AAEN,YAAQ,IAAI,oCAAoC,aAAa,sBAAsB,MAAM,MAAM,sBAAsB;AACrH,0BAAsB,cAAc;KAAE,OAAO;KAAQ,+BAAc,IAAI,MAAM,EAAC,aAAa;KAAE,CAAC;AAC9F;;AAEF,WAAQ,IAAI,yCAAyC,MAAM,MAAM,WAAW,MAAM,aAAa,eAAe;AAC9G,SAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,mBAAmB,CAAC;;EAIvE,MAAM,aAAa,qBAAqB,aAAa;AACrD,MAAI,cAAc,WAAW,UAAU,UAAU,WAAW,UAAU,YACpE,SAAQ,KAAK,6CAA6C,gBAAgB,IAAK,sBAAsB;;CAKzG,IAAI;AACJ,KAAI,iBAAiB;AACnB,UAAQ,IAAI,4DAA4D,QAAQ,IAAI,gBAAgB,GAAG;AACvG,eAAa,MAAM,yBAAyB,iBAAiB,eAAe;GAC1E;GACA,QAAQ;GACR,WAAW;GACX,gBAAgB;GACjB,CAAC;QACG;AACL,UAAQ,IAAI,yFAAyF,UAAU;AAC/G,eAAa,MAAM,eAAe,eAAe,YAAY;GAC3D,cAAc;GACd,mBAAmB;GACnB;GACD,CAAC;;AAGJ,KAAI,CAAC,WAAW,SAAS;AACvB,UAAQ,MAAM,4CAA4C,WAAW,UAAU;AAC/E,cAAY,eAAe,8BAA8B,WAAW,UAAU;AAC9E,SAAO;GACL,SAAS;GACT,QAAQ,0CAA0C,WAAW;GAC9D;;AAGH,SAAQ,IAAI,kEAAkE;AAC9E,aAAY,0BAA0B,8BAA8B;CAGpE,MAAM,gBAAgB;CACtB,MAAM,WAAW,MAAU;CAC3B,MAAM,YAAY,KAAK,KAAK;AAE5B,QAAO,KAAK,KAAK,GAAG,YAAY,UAAU;AACxC,QAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,cAAc,CAAC;AAEhE,MAAI;GAEF,MAAM,EAAE,QAAQ,qBAAqB,MAAM,UAAU,6BAA6B;IAChF,KAAK;IACL,UAAU;IACX,CAAC;AAGF,OAFsB,iBAAiB,MAAM,KAEvB,aAEpB;GAIF,MAAM,EAAE,QAAQ,mBAAmB,MAAM,UAAU,sBAAsB;IACvE,KAAK;IACL,UAAU;IACX,CAAC;GACF,MAAM,cAAc,eAAe,MAAM;AAEzC,OAAI,gBAAgB,YAAY;AAS5B,QAAI;AACF,WAAM,UAAU,oBAAoB,gBAAgB;MAClD,KAAK;MACL,UAAU;MACV,SAAS;MACV,CAAC,CAAC,YAAY,GAAG;KAClB,MAAM,EAAE,QAAQ,kBAAkB,MAAM,UAAU,wBAAwB,gBAAgB;MACxF,KAAK;MACL,UAAU;MACX,CAAC;AAGF,SAFmB,cAAc,MAAM,KAEpB,aAAa;AAC9B,cAAQ,IAAI,kEAAkE;AAC9E,kBAAY,0BAA0B,qCAAqC,UAAU;MAGrF,IAAI;AACJ,UAAI;OAEF,MAAM,iBADmB,MAAM,kBAAkB,aAAa,EACvB,MAAM,yBAAyB;AACtE,4BAAqB,gBAAgB,SAAS,cAAc,IAAI,GAAG,GAAG,KAAA;AACtE,WAAI,uBAAuB,KAAA,EACzB,SAAQ,IAAI,qDAAqD,qBAAqB;cAElF;MAGR,MAAM,mBAAmB,MAAM,mBAAmB;OAChD;OACA;OACA,sBAAsB;OACvB,CAAC;AAEF,UAAI,iBAAiB,OAAO;OAE1B,MAAM,WAAW,iBAAiB,UAAU,0DAA0D;AACtG,eAAQ,IAAI,0CAA0C,WAAW;OAGjE,MAAM,kBADc,MAAM,uBAAuB,aAAa,WAAW,EACtC,QAAO,MAAK,CAAC,EAAE,UAAU,EAAE,SAAS;AACvE,WAAI,eAAe,SAAS,GAAG;QAC7B,MAAM,cAAc,eAAe,KAAI,MAAK,EAAE,KAAK,CAAC,KAAK,KAAK;AAC9D,gBAAQ,IAAI,yCAAyC,cAAc;AACnE,oBAAY,2BAA2B,4BAA4B,QAAQ,IAAI,cAAc;AAO7F,eAAO;SACL,SAAS;SACT,kBAAkB;SAClB,QAAQ,2BAA2B,YAAY,IAR3B,MAAM,gBAAgB,YAAY,GAEpD,uCACA;SAMH;;AAGH,mBAAY,kBAAkB,gCAAgC,WAAW;AAGzE,aAAM,mBAAmB,SAAS,aAAa,aAAa;AAG5D,WAAI,aACF,KAAI;AACF,cAAM,UAAU,iBAAiB;SAAE,KAAK;SAAa,UAAU;SAAS,CAAC;AACzE,gBAAQ,IAAI,kEAAkE;gBACvE,QAAa;AACpB,gBAAQ,KAAK,wDAAwD,OAAO,UAAU;;AAI1F,cAAO;QACL,SAAS;QACT,kBAAkB;QAClB,aAAa;QACb,OAAO;QACR;aACI;AAEL,eAAQ,IAAI,sCAAsC,iBAAiB,SAAS;AAC5E,mBAAY,yBAAyB,sBAAsB,iBAAiB,SAAS,KAAI,MAAK,EAAE,KAAK,CAAC,KAAK,KAAK,GAAG;OAGnH,MAAM,gBAAgB,MAAM,gBAAgB,YAAY;AAGxD,WAAI,cACF,KAAI;AACF,cAAM,UAAU,sCAAsC,gBAAgB;SACpE,KAAK;SACL,UAAU;SACX,CAAC;AACF,gBAAQ,IAAI,+CAA+C;AAC3D,oBAAY,qBAAqB,2CAA2C;gBACrE,WAAgB;AACvB,gBAAQ,MAAM,0CAA0C,UAAU,UAAU;AAC5E,oBAAY,0BAA0B,yCAAyC;;AAKnF,WAAI,aACF,KAAI;AACF,cAAM,UAAU,iBAAiB;SAAE,KAAK;SAAa,UAAU;SAAS,CAAC;AACzE,gBAAQ,IAAI,wDAAwD;gBAC7D,QAAa;AACpB,gBAAQ,KAAK,yDAAyD,OAAO,UAAU;;AAS3F,cAAO;QACL,SAAS;QACT,kBAAkB;QAClB,QAAQ,sBARY,iBAAiB,SAAS,KAAI,MAAK,GAAG,EAAE,KAAK,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK,CAQhD,IAP3B,gBACf,mDACA;QAMF,OAAO;QACR;;;YAGC;AAEN,aAAQ,IAAI,6DAA6D;;AAI3E,YAAQ,IAAI,2DAA2D;;AAK3E,OAAI,CAAE,MAAM,UAAU,eAAe,mBAAmB,KAAA,EAAU,EAAG;AACnE,YAAQ,MAAM,qFAAqF;AACnG,gBAAY,eAAe,kCAAkC;IAG7D,MAAM,gBAAgB,MAAM,qBAAqB,aAAa,cAAc,YAAY,SAAS,YAAY;AAC7G,QAAI,cAAe,QAAO;AAE1B,WAAO;KACL,SAAS;KACT,QAAQ;KACT;;WAGI,WAAgB;AACvB,WAAQ,KAAK,6BAA6B,UAAU,UAAU;;;AAMlE,SAAQ,MAAM,0FAA0F;AACxG,aAAY,iBAAiB,mDAAmD;CAEhF,MAAM,gBAAgB,MAAM,qBAAqB,aAAa,cAAc,YAAY,SAAS,YAAY;AAC7G,KAAI,cAAe,QAAO;AAE1B,QAAO;EACL,SAAS;EACT,QAAQ;EACT;;;;;;;;;AAUH,eAAsB,0BACpB,eACA,eACA,YACA,SACsB;AACtB,SAAQ,IAAI,oCAAoC,cAAc,QAAQ,WAAW,OAAO,UAAU;AAClG,aAAY,gBAAgB,YAAY,cAAc,QAAQ,WAAW,OAAO,UAAU;AAG1F,KAAI;EACF,MAAM,EAAE,QAAQ,mBAAmB,MAAM,UACvC,gCAAgC,iBAChC;GAAE,KAAK;GAAe,UAAU;GAAS,CAC1C;AACD,MAAI,CAAC,eAAe,MAAM,EAAE;GAC1B,MAAM,UAAU,UAAU,cAAc;AACxC,WAAQ,MAAM,iBAAiB,UAAU;AACzC,UAAO;IAAE,SAAS;IAAO,QAAQ;IAAS;;SAEtC;EACN,MAAM,UAAU,+BAA+B;AAC/C,UAAQ,MAAM,iBAAiB,UAAU;AACzC,SAAO;GAAE,SAAS;GAAO,QAAQ;GAAS;;CAI5C,IAAI;AACJ,KAAI;AACF,QAAM,UAAU,oBAAoB,iBAAiB;GAAE,KAAK;GAAe,UAAU;GAAS,CAAC;EAC/F,MAAM,EAAE,WAAW,MAAM,UAAU,wBAAwB,iBAAiB;GAC1E,KAAK;GACL,UAAU;GACX,CAAC;AACF,eAAa,OAAO,MAAM;UACnB,KAAU;AACjB,SAAO;GAAE,SAAS;GAAO,QAAQ,8BAA8B,IAAI;GAAW;;CAIhF,MAAM,UAAU,QAAQ,IAAI,YAAY,QAAQ,IAAI,QAAQ;CAC5D,MAAM,SAAS,QAAQ,IAAI,iBAAiB,oBAAoB;CAEhE,MAAM,aAAa,mBAAmB,QAAQ;;aAEnC,cAAc;kBACT,cAAc;eACjB,WAAW;;;;QAIlB,cAAc;sBACA,WAAW;;;gDAGe,WAAW;iCAC1B,WAAW;;;;;;uBAMrB,WAAW;;;;;wCAKM,cAAc;;;iBAGrC,cAAc;;;;;;qBAMV,OAAO;;4CAEgB,QAAQ;;;iBAGnC,cAAc;;;;;;;;;;kBAUb,OAAO;;yCAEgB,QAAQ;;;;;;kBAM/B,OAAO;;yCAEgB,QAAQ;;;;;CAO/C,MAAM,kBAAkB,wBAAwB,QAAQ;CACxD,MAAM,kBAAkB,iBAAiB,cAAc;CACvD,MAAM,eAAe,mBAAmB,eAAe,mBAAmB,KAAA,EAAU;AAEpF,KAAI,CAAC,gBACH,SAAQ,KAAK,+CAA+C,QAAQ,4BAA4B;AAIlG,KAAI,iBAAiB;EACnB,MAAM,EAAE,sBAAsB,0BAA0B,MAAM,OAAO;EACrE,MAAM,qBAAqB;EAC3B,MAAM,gBAAgB;EACtB,MAAM,YAAY,KAAK,KAAK;AAE5B,SAAO,KAAK,KAAK,GAAG,YAAY,eAAe;GAC7C,MAAM,QAAQ,qBAAqB,aAAa;AAChD,OAAI,CAAC,SAAS,MAAM,UAAU,UAAU,MAAM,UAAU,YAAa;AACrE,OAAI;AACF,UAAM,UAAU,wBAAwB,aAAa,eAAe;WAC9D;AACN,0BAAsB,cAAc;KAAE,OAAO;KAAQ,+BAAc,IAAI,MAAM,EAAC,aAAa;KAAE,CAAC;AAC9F;;AAEF,SAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,mBAAmB,CAAC;;;CAKzE,IAAI;AACJ,KAAI,gBACF,cAAa,MAAM,yBAAyB,iBAAiB,eAAe;EAC1E;EACA,QAAQ;EACR,WAAW;EACX,gBAAgB;EACjB,CAAC;KAEF,cAAa,MAAM,eAAe,eAAe,YAAY;EAC3D,cAAc;EACd,mBAAmB;EACnB;EACD,CAAC;AAGJ,KAAI,CAAC,WAAW,QACd,QAAO;EACL,SAAS;EACT,QAAQ,0CAA0C,WAAW;EAC9D;AAGH,SAAQ,IAAI,6CAA6C,QAAQ,6BAA6B;CAG9F,MAAM,gBAAgB;CACtB,MAAM,WAAW,MAAU;CAE3B,MAAM,YAAY,KAAK,KAAK;AAC5B,QAAO,KAAK,KAAK,GAAG,YAAY,UAAU;AACxC,QAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,cAAc,CAAC;AAEhE,MAAI;AACF,SAAM,UAAU,oBAAoB,iBAAiB;IACnD,KAAK;IACL,UAAU;IACV,SAAS;IACV,CAAC,CAAC,YAAY,GAAG;GAElB,MAAM,EAAE,QAAQ,kBAAkB,MAAM,UACtC,wBAAwB,iBACxB;IAAE,KAAK;IAAe,UAAU;IAAS,CAC1C;GACD,MAAM,aAAa,cAAc,MAAM;AAEvC,OAAI,eAAe,YAAY;AAC7B,YAAQ,IAAI,qCAAqC,QAAQ,qBAAqB,aAAa;AAC3F,gBAAY,mBAAmB,wBAAwB,UAAU;AACjE,WAAO;KAAE,SAAS;KAAM,QAAQ;KAAiC;;UAE7D;AAKR,MAAI,CAAE,MAAM,UAAU,eAAe,mBAAmB,KAAA,EAAU,EAAG;AAEnE,OAAI;AACF,UAAM,UAAU,oBAAoB,iBAAiB;KACnD,KAAK;KACL,UAAU;KACX,CAAC,CAAC,YAAY,GAAG;IAClB,MAAM,EAAE,WAAW,MAAM,UAAU,wBAAwB,iBAAiB;KAC1E,KAAK;KACL,UAAU;KACX,CAAC;AACF,QAAI,OAAO,MAAM,KAAK,YAAY;AAChC,aAAQ,IAAI,8DAA8D,UAAU;AACpF,YAAO;MAAE,SAAS;MAAM,QAAQ;MAAwD;;WAEpF;AAER,UAAO;IACL,SAAS;IACT,QAAQ;IACT;;;AAIL,aAAY,kBAAkB,wBAAwB,UAAU;AAChE,QAAO;EACL,SAAS;EACT,QAAQ;EACT;;;;;;;;AASH,eAAe,qBACb,aACA,cACA,YACA,SACA,aACuD;AACvD,KAAI;EACF,MAAM,EAAE,QAAQ,mBAAmB,MAAM,UAAU,sBAAsB;GACvE,KAAK;GACL,UAAU;GACX,CAAC;EACF,MAAM,cAAc,eAAe,MAAM;AAEzC,MAAI,gBAAgB,WAElB,QAAO;AAIT,QAAM,UAAU,oBAAoB,gBAAgB;GAClD,KAAK;GACL,UAAU;GACV,SAAS;GACV,CAAC,CAAC,YAAY,GAAG;EAElB,MAAM,EAAE,QAAQ,kBAAkB,MAAM,UAAU,wBAAwB,gBAAgB;GACxF,KAAK;GACL,UAAU;GACX,CAAC;AAEF,MAAI,cAAc,MAAM,KAAK,aAAa;AAExC,WAAQ,IAAI,oDAAoD;AAChE,UAAO,EAAE,SAAS,MAAM;;AAI1B,UAAQ,IAAI,8CAA8C,QAAQ,eAAe,YAAY,MAAM,GAAG,EAAE,CAAC,aAAa,cAAc,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG;AACzJ,cAAY,iBAAiB,iCAAiC,YAAY,MAAM,GAAG,EAAE,CAAC,OAAO,UAAU;AAEvG,QAAM,UAAU,mBAAmB,gBAAgB;GACjD,KAAK;GACL,UAAU;GACV,SAAS;GACV,CAAC;AAEF,UAAQ,IAAI,6CAA6C,UAAU;AACnE,cAAY,yBAAyB,qCAAqC;AAC1E,SAAO,EAAE,SAAS,MAAM;UACjB,OAAY;AACnB,UAAQ,MAAM,iCAAiC,MAAM,UAAU;AAC/D,cAAY,wBAAwB,wBAAwB,MAAM,UAAU;AAC5E,SAAO;;;;;;AAmBX,eAAsB,uBAAuB,aAAwC;AACnF,KAAI;EAEF,MAAM,EAAE,WAAW,MAAM,UAAU,iCAAiC;GAClE,KAAK;GACL,UAAU;GACX,CAAC;EACF,MAAM,QAAQ,OACX,MAAM,KAAK,CACX,QAAO,SAAQ,KAAK,SAAS,2BAA2B,CAAC,CACzD,KAAI,SAAQ,KAAK,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,CACtC,QAAO,MAAK,EAAE,SAAS,EAAE;AAC5B,SAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;SACpB;AACN,SAAO,EAAE;;;;;;;;;;;;AAab,eAAsB,sBACpB,aACA,SACyB;AACzB,SAAQ,IAAI,wDAAwD,UAAU;AAC9E,aAAY,mBAAmB,qBAAqB,UAAU;AAG9D,KAAI;EACF,MAAM,EAAE,QAAQ,cAAc,MAAM,UAAU,0BAA0B;GACtE,KAAK;GACL,UAAU;GACX,CAAC;AACF,MAAI,UAAU,MAAM,EAAE;AACpB,WAAQ,IAAI,+DAA+D;AAC3E,eAAY,yBAAyB,kDAAkD;AACvF,OAAI;AACF,UAAM,UAAU,0EAAwE;KACtF,KAAK;KACL,UAAU;KACX,CAAC;AACF,YAAQ,IAAI,qCAAqC;YAC1C,WAAgB;IACvB,MAAM,UAAU,8CAA8C,UAAU;AACxE,YAAQ,MAAM,eAAe,UAAU;AACvC,gBAAY,qBAAqB,QAAQ;AACzC,WAAO;KAAE,SAAS;KAAO,QAAQ;KAAS;;GAI5C,MAAM,EAAE,QAAQ,qBAAqB,MAAM,UAAU,0BAA0B;IAC7E,KAAK;IACL,UAAU;IACX,CAAC;AACF,OAAI,iBAAiB,MAAM,EAAE;IAC3B,MAAM,UAAU;AAChB,YAAQ,MAAM,eAAe,UAAU;AACvC,gBAAY,qBAAqB,QAAQ;AACzC,WAAO;KAAE,SAAS;KAAO,QAAQ;KAAS;;;UAGvC,OAAY;AACnB,SAAO;GAAE,SAAS;GAAO,QAAQ,+BAA+B,MAAM;GAAW;;AAInF,KAAI;EACF,MAAM,cAAc,MAAM,kBAAkB,YAAY;AACxD,MAAI,YAAY,MAAM,SAAS,GAAG;AAChC,WAAQ,IAAI,qBAAqB,YAAY,MAAM,OAAO,eAAe;AACzE,OAAI,YAAY,QAAQ,SAAS,GAAG;AAClC,YAAQ,IAAI,0BAA0B,YAAY,QAAQ,OAAO,qBAAqB;AACtF,gBAAY,oBAAoB,WAAW,YAAY,QAAQ,OAAO,qBAAqB;;AAE7F,OAAI,YAAY,OAAO,MAAM,MAAuC,EAAE,MAAM,SAAS,4BAA4B,CAAC,EAAE;IAClH,MAAM,UAAU;AAChB,YAAQ,MAAM,eAAe,UAAU;AACvC,gBAAY,qBAAqB,QAAQ;AACzC,WAAO;KAAE,SAAS;KAAO,QAAQ;KAAS;;;UAGvC,SAAc;AACrB,UAAQ,KAAK,qCAAqC,QAAQ,QAAQ,eAAe;;AAInF,KAAI;AACF,UAAQ,IAAI,sCAAsC;AAClD,QAAM,UAAU,yBAAyB;GAAE,KAAK;GAAa,UAAU;GAAS,CAAC;UAC1E,OAAY;AACnB,SAAO;GAAE,SAAS;GAAO,QAAQ,gCAAgC,MAAM;GAAW;;CAIpF,IAAI,cAAc;CAClB,IAAI,eAAe;AACnB,KAAI;EACF,MAAM,SAAS,MAAM,UAAU,yBAAyB;GAAE,KAAK;GAAa,UAAU;GAAS,CAAC;AAChG,iBAAe,OAAO,UAAU,OAAO,OAAO,UAAU;UACjD,OAAY;AACnB,iBAAe,MAAM,UAAU,OAAO,MAAM,UAAU;AACtD,iBAAe;;AAIjB,KAAI,YAAY,SAAS,qBAAqB,IAAI,YAAY,SAAS,qBAAqB,EAAE;AAC5F,UAAQ,IAAI,iCAAiC;AAC7C,cAAY,kBAAkB,GAAG,QAAQ,+BAA+B;AACxE,SAAO;GAAE,SAAS;GAAM,iBAAiB;GAAM;;AAGjD,KAAI,CAAC,cAAc;AAEjB,UAAQ,IAAI,oCAAoC;AAChD,cAAY,qBAAqB,4BAA4B,UAAU;EAEvE,IAAI,eAAyB,EAAE;EAC/B,IAAI,cAAc;AAClB,MAAI;GACF,MAAM,EAAE,QAAQ,cAAc,MAAM,UAClC,uFACA;IAAE,KAAK;IAAa,UAAU;IAAS,CACxC;AACD,kBAAe,UAAU,MAAM,CAAC,MAAM,KAAK,CAAC,QAAO,MAAK,EAAE,SAAS,EAAE;UAC/D;AACR,MAAI;GACF,MAAM,EAAE,QAAQ,WAAW,MAAM,UAC/B,8DACA;IAAE,KAAK;IAAa,UAAU;IAAS,CACxC;AACD,iBAAc,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,QAAO,MAAK,EAAE,SAAS,EAAE,CAAC;UAC5D;AAER,SAAO;GAAE,SAAS;GAAM;GAAa;GAAc;;CAIrD,MAAM,gBAAgB,MAAM,iBAAiB,YAAY;AACzD,SAAQ,IAAI,eAAe,cAAc,OAAO,qCAAqC;AACrF,aAAY,uBAAuB,GAAG,cAAc,OAAO,kBAAkB,QAAQ,IAAI,cAAc,KAAK,KAAK,GAAG;CAEpH,MAAM,kBAAkB,MAAM,UAAU,6BAA6B;EAAE,KAAK;EAAa,UAAU;EAAS,CAAC,CAC1G,MAAK,MAAK,EAAE,OAAO,MAAM,CAAC,CAC1B,YAAY,WAAW,QAAQ,aAAa,GAAG;CAGlD,MAAM,aAAa,KAAK,WAAW,WAAW,eAAe;CAC7D,IAAI;AACJ,KAAI;AAEF,eADiB,aAAa,YAAY,QAAQ,CAE/C,QAAQ,oBAAoB,YAAY,CACxC,QAAQ,wBAAwB,gBAAgB,CAChD,QAAQ,gBAAgB,QAAQ,CAChC,QAAQ,sBAAsB,cAAc,KAAI,MAAK,KAAK,IAAI,CAAC,KAAK,KAAK,CAAC;UACtE,aAAkB;AACzB,UAAQ,MAAM,qDAAqD,YAAY,UAAU;AACzF,cAAY,mBAAmB,yBAAyB,YAAY,UAAU;AAC9E,MAAI;AAAE,SAAM,UAAU,qBAAqB;IAAE,KAAK;IAAa,UAAU;IAAS,CAAC;UAAU;AAC7F,SAAO;GAAE,SAAS;GAAO;GAAe,QAAQ;GAAuD;;CAKzG,MAAM,iBADsB,wBAAwB,QAAQ,EAChB,cAAc;CAC1D,IAAI;AACJ,KAAI,eACF,kBAAiB,MAAM,yBAAyB,gBAAgB,eAAe;EAC7E;EACA,QAAQ;EACR,WAAW;EACX,gBAAgB;EACjB,CAAC;KAEF,kBAAiB,MAAM,eAAe,eAAe,YAAY;EAC/D,cAAc;EACd,mBAAmB;EACnB;EACD,CAAC;AAGJ,KAAI,CAAC,eAAe,SAAS;AAC3B,MAAI;AAAE,SAAM,UAAU,qBAAqB;IAAE,KAAK;IAAa,UAAU;IAAS,CAAC;UAAU;EAC7F,MAAM,UAAU,0CAA0C,eAAe;AACzE,UAAQ,MAAM,eAAe,UAAU;AACvC,cAAY,mBAAmB,QAAQ;AACvC,SAAO;GAAE,SAAS;GAAO;GAAe,QAAQ;GAAS;;AAG3D,SAAQ,IAAI,mEAAmE;AAC/E,aAAY,yBAAyB,mBAAmB,cAAc,OAAO,mBAAmB,UAAU;CAG1G,MAAM,cAAc,mBAAmB,eAAe,kBAAkB,KAAA,EAAU;CAClF,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,gBAAgB;CACtB,MAAM,kBAAkB,MAAU;CAClC,IAAI,aAAa;AAEjB,QAAO,KAAK,KAAK,GAAG,YAAY,iBAAiB;AAC/C,QAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,cAAc,CAAC;EAEhE,MAAM,SAAS,MAAM,kBAAkB,YAAY;AACnD,MAAI,WAAW,YAAY;AACzB,gBAAa;GACb,MAAM,gBAAgB,OAAO,SAAS,gBAAgB;GACtD,MAAM,cAAc,OAAO,aAAa;GACxC,MAAM,mBACJ,YAAY,SAAS,sBAAsB,IAC3C,YAAY,SAAS,sBAAsB,IAC3C,YAAY,SAAS,iBAAiB,IACtC,YAAY,SAAS,eAAe,IACpC,YAAY,SAAS,oBAAoB;AAE3C,OAAI,iBAAiB,kBAAkB;IACrC,MAAM,cAAc,iBAAiB,OAAO;AAE5C,QAAI,YAAY,SAAS;KAEvB,MAAM,YAAY,MAAM,uBAAuB,YAAY;AAC3D,SAAI,UAAU,SAAS,GAAG;AACxB,UAAI;AAAE,aAAM,UAAU,qBAAqB;QAAE,KAAK;QAAa,UAAU;QAAS,CAAC;cAAU;MAC7F,MAAM,MAAM,8BAA8B,UAAU,OAAO,iCAAiC,UAAU,KAAK,KAAK;AAChH,cAAQ,MAAM,eAAe,MAAM;AACnC,kBAAY,4BAA4B,IAAI;AAC5C,aAAO;OAAE,SAAS;OAAO;OAAe,QAAQ;OAAK;;AAGvD,aAAQ,IAAI,4CAA4C;AACxD,iBAAY,qBAAqB,sCAAsC,UAAU;KAGjF,IAAI,eAAyB,EAAE;KAC/B,IAAI,cAAc;AAClB,SAAI;MACF,MAAM,EAAE,QAAQ,cAAc,MAAM,UAClC,uCACA;OAAE,KAAK;OAAa,UAAU;OAAS,CACxC;AACD,qBAAe,UAAU,MAAM,CAAC,MAAM,KAAK,CAAC,QAAO,MAAK,EAAE,SAAS,EAAE;MACrE,MAAM,EAAE,QAAQ,WAAW,MAAM,UAC/B,qCACA;OAAE,KAAK;OAAa,UAAU;OAAS,CACxC;AACD,oBAAc,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,QAAO,MAAK,EAAE,SAAS,EAAE,CAAC;aAC5D;AAER,YAAO;MAAE,SAAS;MAAM;MAAa;MAAc;WAC9C;AAEL,SAAI;AAAE,YAAM,UAAU,qBAAqB;OAAE,KAAK;OAAa,UAAU;OAAS,CAAC;aAAU;AAC7F,aAAQ,IAAI,kDAAkD;AAC9D,iBAAY,0BAA0B,yCAAyC,UAAU;AACzF,YAAO;MACL,SAAS;MACT;MACA,QAAQ,YAAY,UAAU;MAC/B;;;;EAKP,MAAM,UAAU,KAAK,OAAO,KAAK,KAAK,GAAG,aAAa,IAAK;AAC3D,MAAI,UAAU,OAAO,EACnB,SAAQ,IAAI,2CAA2C,QAAQ,YAAY;;AAK/E,KAAI;AAAE,QAAM,UAAU,qBAAqB;GAAE,KAAK;GAAa,UAAU;GAAS,CAAC;SAAU;AAC7F,aAAY,qBAAqB,sBAAsB,UAAU;AACjE,QAAO;EACL,SAAS;EACT;EACA,QAAQ,gDAAgD,kBAAkB,IAAM;EACjF;;;;;;;;;;AAWH,eAAsB,uBACpB,aACA,OACwD;AACxD,KAAI;EACF,MAAM,SAAS,oBAAoB;EAEnC,MAAM,UAAU,OAAO,OAAO,OAAO,SAAS,CAAC,MAAK,MAAK,YAAY,WAAW,EAAE,KAAK,CAAC;AACxF,MAAI,CAAC,SAAS,iBAAiB,OAAO,KAAK,QAAQ,cAAc,CAAC,WAAW,GAAG;AAC9E,WAAQ,IAAI,iDAAiD,cAAc;AAC3E,UAAO,EAAE;;EAKX,MAAM,cAAc,SAAS,QAAQ,MAAM,YAAY;EAEvD,IAAI,aAAa,QAAQ;AACzB,MAAI,eAAe,CAAC,YAAY,WAAW,KAAK,EAAE;GAEhD,MAAM,WAAW,OAAO,QAAQ,QAAQ,cAAc,CAAC,QACpD,GAAG,UAAU,KAAK,SAAS,YAC7B;AACD,OAAI,SAAS,WAAW,GAAG;AACzB,YAAQ,IAAI,4DAA4D,YAAY,GAAG;AACvF,WAAO,EAAE;;AAEX,gBAAa,OAAO,YAAY,SAAS;AACzC,WAAQ,IACN,mCAAmC,OAAO,KAAK,WAAW,CAAC,OAAO,qBAAqB,YAAY,GACpG;;AAGH,UAAQ,IAAI,yBAAyB,MAAM,8BAA8B,QAAQ,KAAK,GAAG;AACzF,SAAO,MAAM,gBAAgB,YAAY,aAAa,MAAM;UACrD,OAAY;AACnB,UAAQ,MAAM,+CAA+C,MAAM,UAAU;AAC7E,SAAO,EAAE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"projects-Cq3TWdPS.js","names":[],"sources":["../../src/lib/projects.ts"],"sourcesContent":["/**\n * Project Registry - Multi-project support for Panopticon\n *\n * Maps Linear team prefixes and labels to project paths for workspace creation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join, resolve } from 'path';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { PANOPTICON_HOME } from './paths.js';\nimport type { QualityGateConfig } from './workspace-config.js';\n\nexport const PROJECTS_CONFIG_FILE = join(PANOPTICON_HOME, 'projects.yaml');\n\n/**\n * Issue routing rule - routes issues with certain labels to specific paths\n */\nexport interface IssueRoutingRule {\n labels?: string[];\n default?: boolean;\n path: string;\n}\n\n/**\n * Workspace configuration (imported from workspace-config.ts for full details)\n */\nexport interface WorkspaceConfig {\n type?: 'polyrepo' | 'monorepo';\n workspaces_dir?: string;\n repos?: Array<{ name: string; path: string; branch_prefix?: string }>;\n dns?: { domain: string; entries: string[]; sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' };\n ports?: Record<string, { range: [number, number] }>;\n docker?: { traefik?: string; compose_template?: string };\n database?: { seed_file?: string; container_name?: string; [key: string]: any };\n agent?: { template_dir: string; templates?: Array<{ source: string; target: string }>; copy_dirs?: string[]; symlinks?: string[] };\n env?: { template?: string; secrets_file?: string };\n services?: Array<{ name: string; path: string; start_command: string; docker_command?: string; health_url?: string; port?: number }>;\n}\n\n/**\n * Test configuration\n */\nexport interface TestConfig {\n type: string;\n path: string;\n command: string;\n container?: boolean;\n container_name?: string;\n env?: Record<string, string>;\n}\n\n/**\n * Specialist configuration for per-project specialists\n */\nexport interface SpecialistConfig {\n /** Number of recent runs to include in context digest (default: 5) */\n context_runs?: number;\n /** Model to use for generating context digests (null = same as specialist) */\n digest_model?: string | null;\n /** Log retention policy */\n retention?: {\n /** Maximum days to keep logs */\n max_days: number;\n /** Maximum number of runs to keep (whichever is more permissive) */\n max_runs: number;\n };\n /** Per-specialist prompt overrides */\n prompts?: {\n 'review-agent'?: string;\n 'test-agent'?: string;\n 'merge-agent'?: string;\n };\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n name: string;\n path: string;\n /** Issue prefix for identifier construction (e.g., \"PAN\" → PAN-123) */\n issue_prefix?: string;\n github_repo?: string; // e.g. \"owner/repo\"\n gitlab_repo?: string; // e.g. \"group/repo\"\n issue_routing?: IssueRoutingRule[];\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n /** Test configuration by name */\n tests?: Record<string, TestConfig>;\n /** Custom command to create workspaces (e.g., infra/new-feature for MYN) */\n workspace_command?: string;\n /** Custom command to remove workspaces */\n workspace_remove_command?: string;\n /** Rally project OID (e.g., \"/project/822404704163\") for per-project Rally scoping */\n rally_project?: string;\n /** Specialist agent configuration */\n specialists?: SpecialistConfig;\n /** Quality gates run by merge-agent before pushing (lint, typecheck, prod build, etc.) */\n quality_gates?: Record<string, QualityGateConfig>;\n /** Package manager for dependency installation in workspaces (bun, npm, pnpm) */\n package_manager?: 'bun' | 'npm' | 'pnpm';\n /** Local workspace packages that need building before quality gates (e.g., @panopticon/contracts) */\n workspace_packages?: Array<{ path: string; build_command: string }>;\n /**\n * Path to the repo where per-project cost WAL files live.\n * Defaults to `path` (the project repo itself).\n * For polyrepo setups, point this at the docs/shared repo.\n */\n events_repo?: string;\n /**\n * Subdirectory within events_repo where cost JSONL files are stored.\n * Defaults to \".pan/events\".\n */\n events_path?: string;\n}\n\n/** Resolve the issue prefix for a project. */\nexport function getIssuePrefix(config: ProjectConfig): string | undefined {\n return config.issue_prefix;\n}\n\n/**\n * Full projects configuration file\n */\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Resolved project info for workspace creation\n */\nexport interface ResolvedProject {\n projectKey: string;\n projectName: string;\n projectPath: string;\n linearTeam?: string;\n}\n\n/**\n * Load projects configuration from ~/.panopticon/projects.yaml\n */\nexport function loadProjectsConfig(): ProjectsConfig {\n if (!existsSync(PROJECTS_CONFIG_FILE)) {\n return { projects: {} };\n }\n\n try {\n const content = readFileSync(PROJECTS_CONFIG_FILE, 'utf-8');\n const config = parseYaml(content) as ProjectsConfig;\n return config || { projects: {} };\n } catch (error: any) {\n console.error(`Failed to parse projects.yaml: ${error.message}`);\n return { projects: {} };\n }\n}\n\n/**\n * Save projects configuration\n */\nexport function saveProjectsConfig(config: ProjectsConfig): void {\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const yaml = stringifyYaml(config, { indent: 2 });\n writeFileSync(PROJECTS_CONFIG_FILE, yaml, 'utf-8');\n}\n\n/**\n * Get a list of all registered projects\n */\nexport function listProjects(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects).map(([key, projectConfig]) => ({\n key,\n config: projectConfig,\n }));\n}\n\n/**\n * Add or update a project in the registry\n */\nexport function registerProject(key: string, projectConfig: ProjectConfig): void {\n const config = loadProjectsConfig();\n config.projects[key] = projectConfig;\n saveProjectsConfig(config);\n}\n\n/**\n * Remove a project from the registry\n */\nexport function unregisterProject(key: string): boolean {\n const config = loadProjectsConfig();\n if (config.projects[key]) {\n delete config.projects[key];\n saveProjectsConfig(config);\n return true;\n }\n return false;\n}\n\n/**\n * Extract Linear team prefix from an issue ID\n * E.g., \"MIN-123\" -> \"MIN\", \"PAN-456\" -> \"PAN\"\n */\nexport function extractTeamPrefix(issueId: string): string | null {\n const match = issueId.match(/^([A-Z]+)-\\d+$/i);\n return match ? match[1].toUpperCase() : null;\n}\n\n/**\n * Find project by Linear team prefix\n */\nexport function findProjectByTeam(teamPrefix: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n if (getIssuePrefix(projectConfig)?.toUpperCase() === teamPrefix.toUpperCase()) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n/**\n * Find project by workspace path.\n * Matches any project whose root path is an ancestor of the given path.\n * Used to resolve the tracker (GitHub/GitLab) from a workspace directory.\n */\nexport function findProjectByPath(workspacePath: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n const normalizedTarget = resolve(workspacePath);\n\n for (const [, projectConfig] of Object.entries(config.projects)) {\n const normalizedProject = resolve(projectConfig.path);\n if (normalizedTarget === normalizedProject || normalizedTarget.startsWith(normalizedProject + '/')) {\n return projectConfig;\n }\n }\n\n return null;\n}\n\n\n/**\n * Resolve the correct project path for an issue based on labels\n *\n * @param project - The project config\n * @param labels - Array of label names from the Linear issue\n * @returns The resolved path (may differ from project.path based on routing rules)\n */\nexport function resolveProjectPath(project: ProjectConfig, labels: string[] = []): string {\n if (!project.issue_routing || project.issue_routing.length === 0) {\n return project.path;\n }\n\n // Normalize labels to lowercase for comparison\n const normalizedLabels = labels.map(l => l.toLowerCase());\n\n // First, check label-based routing rules\n for (const rule of project.issue_routing) {\n if (rule.labels && rule.labels.length > 0) {\n const ruleLabels = rule.labels.map(l => l.toLowerCase());\n const hasMatch = ruleLabels.some(label => normalizedLabels.includes(label));\n if (hasMatch) {\n return rule.path;\n }\n }\n }\n\n // Then, find default rule\n for (const rule of project.issue_routing) {\n if (rule.default) {\n return rule.path;\n }\n }\n\n // Fall back to project path\n return project.path;\n}\n\n/**\n * Resolve project from an issue ID (and optional labels)\n *\n * @param issueId - Linear issue ID (e.g., \"MIN-123\")\n * @param labels - Optional array of label names\n * @returns Resolved project info or null if not found\n */\nexport function resolveProjectFromIssue(\n issueId: string,\n labels: string[] = []\n): ResolvedProject | null {\n const teamPrefix = extractTeamPrefix(issueId);\n if (!teamPrefix) {\n return null;\n }\n\n const config = loadProjectsConfig();\n\n // Find project by team prefix (check linear_team first, then derive from project key)\n for (const [key, projectConfig] of Object.entries(config.projects)) {\n if (getIssuePrefix(projectConfig)?.toUpperCase() === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: getIssuePrefix(projectConfig),\n };\n }\n // For projects without linear_team (GitHub-only or Rally-only), derive prefix from project key\n if (!getIssuePrefix(projectConfig) && (projectConfig.github_repo || projectConfig.rally_project)) {\n const derivedPrefix = key.toUpperCase().replace(/-/g, '');\n if (derivedPrefix === teamPrefix) {\n const resolvedPath = resolveProjectPath(projectConfig, labels);\n return {\n projectKey: key,\n projectName: projectConfig.name,\n projectPath: resolvedPath,\n linearTeam: undefined,\n };\n }\n }\n }\n\n return null;\n}\n\n/**\n * Get a project by key\n */\nexport function getProject(key: string): ProjectConfig | null {\n const config = loadProjectsConfig();\n return config.projects[key] || null;\n}\n\n/**\n * Check if projects.yaml exists and has any projects\n */\nexport function hasProjects(): boolean {\n const config = loadProjectsConfig();\n return Object.keys(config.projects).length > 0;\n}\n\n/**\n * Create a default projects.yaml with example structure\n */\nexport function createDefaultProjectsConfig(): ProjectsConfig {\n const defaultConfig: ProjectsConfig = {\n projects: {\n // Example project - commented out in actual file\n },\n };\n\n return defaultConfig;\n}\n\n/**\n * Initialize projects.yaml with example configuration\n */\nexport function initializeProjectsConfig(): void {\n if (existsSync(PROJECTS_CONFIG_FILE)) {\n console.log(`Projects config already exists at ${PROJECTS_CONFIG_FILE}`);\n return;\n }\n\n const exampleYaml = `# Panopticon Project Registry\n# Maps Linear teams to project paths for workspace creation\n\nprojects:\n # Example: Mind Your Now project\n # myn:\n # name: \"Mind Your Now\"\n # path: /home/user/projects/myn\n # linear_team: MIN\n # issue_routing:\n # # Route docs/marketing issues to docs repo\n # - labels: [docs, marketing, seo, landing-pages]\n # path: /home/user/projects/myn/docs\n # # Default: main repo\n # - default: true\n # path: /home/user/projects/myn\n # specialists:\n # context_runs: 5\n # digest_model: null # Use same model as specialist\n # retention:\n # max_days: 30\n # max_runs: 50\n # prompts:\n # review-agent: |\n # Pay special attention to:\n # - Database migration safety\n # - API backward compatibility\n\n # Example: Panopticon itself\n # panopticon:\n # name: \"Panopticon\"\n # path: /home/user/projects/panopticon\n # linear_team: PAN\n`;\n\n const dir = PANOPTICON_HOME;\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(PROJECTS_CONFIG_FILE, exampleYaml, 'utf-8');\n console.log(`Created example projects config at ${PROJECTS_CONFIG_FILE}`);\n}\n\n/**\n * Default specialist configuration values\n */\nconst DEFAULT_SPECIALIST_CONFIG: Required<SpecialistConfig> = {\n context_runs: 5,\n digest_model: null,\n retention: {\n max_days: 30,\n max_runs: 50,\n },\n prompts: {},\n};\n\n/**\n * Get specialist configuration for a project with defaults\n *\n * @param projectKey - Project key\n * @returns Specialist config with defaults applied\n */\nexport function getSpecialistConfig(projectKey: string): Required<SpecialistConfig> {\n const project = getProject(projectKey);\n\n if (!project || !project.specialists) {\n return DEFAULT_SPECIALIST_CONFIG;\n }\n\n return {\n context_runs: project.specialists.context_runs ?? DEFAULT_SPECIALIST_CONFIG.context_runs,\n digest_model: project.specialists.digest_model ?? DEFAULT_SPECIALIST_CONFIG.digest_model,\n retention: {\n max_days: project.specialists.retention?.max_days ?? DEFAULT_SPECIALIST_CONFIG.retention.max_days,\n max_runs: project.specialists.retention?.max_runs ?? DEFAULT_SPECIALIST_CONFIG.retention.max_runs,\n },\n prompts: project.specialists.prompts ?? DEFAULT_SPECIALIST_CONFIG.prompts,\n };\n}\n\n/**\n * Get retention policy for a project's specialists\n *\n * @param projectKey - Project key\n * @returns Retention policy\n */\nexport function getSpecialistRetention(projectKey: string): { max_days: number; max_runs: number } {\n const config = getSpecialistConfig(projectKey);\n return config.retention;\n}\n\n/**\n * Find all projects that have a rally_project configured.\n * Returns array of { key, config } for projects with Rally project OIDs.\n */\nexport function findProjectsByRallyProject(): Array<{ key: string; config: ProjectConfig }> {\n const config = loadProjectsConfig();\n return Object.entries(config.projects)\n .filter(([, projectConfig]) => !!projectConfig.rally_project)\n .map(([key, projectConfig]) => ({ key, config: projectConfig }));\n}\n\n/**\n * Get custom prompt override for a specialist (if configured)\n *\n * @param projectKey - Project key\n * @param specialistType - Specialist type\n * @returns Custom prompt or null if not configured\n */\nexport function getSpecialistPromptOverride(\n projectKey: string,\n specialistType: string\n): string | null {\n const config = getSpecialistConfig(projectKey);\n return (config.prompts as Record<string, string | undefined>)[specialistType] || null;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"providers-Ck2sQd_F.js","names":[],"sources":["../../src/lib/providers.ts"],"sourcesContent":["/**\r\n * Provider Configuration and Compatibility\r\n *\r\n * Defines which LLM providers are compatible with Claude Code's API format.\r\n * - Direct providers: Implement Anthropic-compatible API (no router needed)\r\n * - Router providers: Require claude-code-router for API translation\r\n */\r\n\r\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\r\nimport { join } from 'path';\r\nimport type { ModelId, AnthropicModel, OpenAIModel, GoogleModel, ZAIModel, MiniMaxModel } from './settings.js';\r\n\r\nexport type ProviderName = 'anthropic' | 'kimi' | 'openai' | 'google' | 'zai' | 'minimax' | 'openrouter';\r\n\r\n/**\r\n * Provider compatibility types\r\n * - direct: Anthropic-compatible API, use ANTHROPIC_BASE_URL directly\r\n * - router: Incompatible API, requires claude-code-router for translation\r\n */\r\nexport type ProviderCompatibility = 'direct' | 'router';\r\n\r\n/**\r\n * Provider configuration\r\n */\r\n/**\r\n * Auth type for direct providers:\r\n * - static: Use a long-lived API key passed via ANTHROPIC_AUTH_TOKEN (default)\r\n * - credential-file: Use apiKeyHelper to read a fresh token from a credential file.\r\n * Used for providers like Kimi Code Plan whose JWT tokens expire every ~15 minutes.\r\n */\r\nexport type ProviderAuthType = 'static' | 'credential-file';\r\n\r\nexport interface ProviderConfig {\r\n name: ProviderName;\r\n displayName: string;\r\n compatibility: ProviderCompatibility;\r\n baseUrl?: string; // For direct providers\r\n authType?: ProviderAuthType; // Defaults to 'static'\r\n credentialFile?: string; // Path to credential file (for 'credential-file' auth)\r\n credentialHelper?: string; // Script that reads credential file and prints token\r\n models: ModelId[];\r\n tested: boolean; // Whether compatibility has been verified\r\n description: string;\r\n}\r\n\r\n/**\r\n * All provider configurations\r\n */\r\nexport const PROVIDERS: Record<ProviderName, ProviderConfig> = {\r\n anthropic: {\r\n name: 'anthropic',\r\n displayName: 'Anthropic',\r\n compatibility: 'direct',\r\n models: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-haiku-4-5'],\r\n tested: true,\r\n description: 'Native Claude API',\r\n },\r\n\r\n kimi: {\r\n name: 'kimi',\r\n displayName: 'Kimi (Moonshot AI)',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.kimi.com/coding/',\r\n authType: 'credential-file',\r\n credentialFile: '~/.kimi/credentials/kimi-code.json',\r\n credentialHelper: '~/.panopticon/bin/kimi-token-helper.sh',\r\n models: [], // Kimi uses same model names as Anthropic\r\n tested: true,\r\n description: 'Anthropic-compatible API via Kimi Code Plan (OAuth token refresh)',\r\n },\r\n\r\n zai: {\r\n name: 'zai',\r\n displayName: 'Z.AI (GLM)',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.z.ai/api/anthropic',\r\n models: ['glm-4.7', 'glm-4.7-flash'],\r\n tested: true,\r\n description: 'Anthropic-compatible API, tested 2026-01-28',\r\n },\r\n\r\n openai: {\r\n name: 'openai',\r\n displayName: 'OpenAI',\r\n compatibility: 'router',\r\n models: ['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini'],\r\n tested: false,\r\n description: 'Requires claude-code-router for API translation',\r\n },\r\n\r\n google: {\r\n name: 'google',\r\n displayName: 'Google (Gemini)',\r\n compatibility: 'router',\r\n models: ['gemini-3-pro-preview', 'gemini-3-flash-preview'],\r\n tested: false,\r\n description: 'Requires claude-code-router for API translation',\r\n },\r\n\r\n minimax: {\r\n name: 'minimax',\r\n displayName: 'MiniMax',\r\n compatibility: 'direct',\r\n baseUrl: 'https://api.minimax.io/anthropic',\r\n models: ['minimax-m2.7', 'minimax-m2.7-highspeed'],\r\n tested: true,\r\n description: 'Anthropic-compatible API, 10B active params, 100 tps highspeed variant',\r\n },\r\n\r\n openrouter: {\r\n name: 'openrouter',\r\n displayName: 'OpenRouter',\r\n compatibility: 'direct',\r\n baseUrl: 'https://openrouter.ai/api',\r\n models: [], // Dynamic models fetched from OpenRouter API; IDs contain '/'\r\n tested: true,\r\n description: 'Anthropic-compatible API aggregator. Model IDs contain \\'/\\' (e.g. qwen/qwen3.6-plus:free)',\r\n },\r\n};\r\n\r\n/**\r\n * Get provider for a given model ID\r\n */\r\nexport function getProviderForModel(modelId: ModelId | string): ProviderConfig {\r\n // OpenRouter model IDs always contain '/' (e.g. 'qwen/qwen3.6-plus:free')\r\n if (modelId.includes('/')) {\r\n return PROVIDERS.openrouter;\r\n }\r\n\r\n // Check Anthropic models\r\n if (['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-sonnet-4-5', 'claude-haiku-4-5'].includes(modelId)) {\r\n return PROVIDERS.anthropic;\r\n }\r\n\r\n // Check OpenAI models\r\n if (['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini'].includes(modelId)) {\r\n return PROVIDERS.openai;\r\n }\r\n\r\n // Check Google models\r\n if (['gemini-3-pro-preview', 'gemini-3-flash-preview'].includes(modelId)) {\r\n return PROVIDERS.google;\r\n }\r\n\r\n // Check Z.AI models\r\n if (['glm-4.7', 'glm-4.7-flash'].includes(modelId)) {\r\n return PROVIDERS.zai;\r\n }\r\n\r\n // Check Kimi models\r\n if (['kimi-k2', 'kimi-k2.5'].includes(modelId)) {\r\n return PROVIDERS.kimi;\r\n }\r\n\r\n // Check MiniMax models\r\n if (['minimax-m2.7', 'minimax-m2.7-highspeed'].includes(modelId)) {\r\n return PROVIDERS.minimax;\r\n }\r\n\r\n // Default to Anthropic if unknown\r\n return PROVIDERS.anthropic;\r\n}\r\n\r\n/**\r\n * Check if a provider requires claude-code-router\r\n */\r\nexport function requiresRouter(provider: ProviderName): boolean {\r\n return PROVIDERS[provider].compatibility === 'router';\r\n}\r\n\r\n/**\r\n * Get all providers that require router (have router compatibility)\r\n */\r\nexport function getRouterProviders(): ProviderConfig[] {\r\n return Object.values(PROVIDERS).filter(p => p.compatibility === 'router');\r\n}\r\n\r\n/**\r\n * Get all direct-compatible providers\r\n */\r\nexport function getDirectProviders(): ProviderConfig[] {\r\n return Object.values(PROVIDERS).filter(p => p.compatibility === 'direct');\r\n}\r\n\r\n/**\r\n * Check if any configured providers require router\r\n * Used to determine if router installation is needed\r\n */\r\nexport function needsRouter(apiKeys: { openai?: string; google?: string; zai?: string }): boolean {\r\n return !!(apiKeys.openai || apiKeys.google);\r\n}\r\n\r\n/**\r\n * Get environment variables for spawning agent with specific provider\r\n */\r\nexport function getProviderEnv(\r\n provider: ProviderConfig,\r\n apiKey: string\r\n): Record<string, string> {\r\n if (provider.compatibility === 'direct') {\r\n // Direct providers use ANTHROPIC_BASE_URL\r\n const env: Record<string, string> = {};\r\n\r\n if (provider.baseUrl) {\r\n env.ANTHROPIC_BASE_URL = provider.baseUrl;\r\n }\r\n\r\n if (provider.name !== 'anthropic') {\r\n if (provider.authType === 'credential-file') {\r\n // Credential-file providers use apiKeyHelper for dynamic token refresh.\r\n // We still need an initial ANTHROPIC_AUTH_TOKEN for the first request,\r\n // but apiKeyHelper (configured via setupCredentialFileAuth) will keep it fresh.\r\n env.ANTHROPIC_AUTH_TOKEN = apiKey;\r\n // Refresh token every 60 seconds (kimi-cli refreshes credential file automatically)\r\n env.CLAUDE_CODE_API_KEY_HELPER_TTL_MS = '60000';\r\n } else {\r\n // Static providers use a long-lived API key\r\n env.ANTHROPIC_AUTH_TOKEN = apiKey;\r\n }\r\n }\r\n\r\n // Z.AI recommends longer timeout\r\n if (provider.name === 'zai') {\r\n env.API_TIMEOUT_MS = '300000';\r\n }\r\n\r\n return env;\r\n } else {\r\n // Router providers use local router proxy\r\n return {\r\n ANTHROPIC_BASE_URL: 'http://localhost:3456',\r\n ANTHROPIC_AUTH_TOKEN: 'router-managed',\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * For credential-file providers (e.g. Kimi Code Plan), configure Claude Code's\r\n * apiKeyHelper in the workspace settings so tokens are refreshed dynamically.\r\n *\r\n * This writes to .claude/settings.local.json in the workspace directory.\r\n * Must be called before spawning the agent.\r\n */\r\nexport function setupCredentialFileAuth(provider: ProviderConfig, workspacePath: string): void {\r\n if (provider.authType !== 'credential-file' || !provider.credentialHelper) return;\r\n\r\n const helperPath = provider.credentialHelper.replace('~', process.env.HOME || '');\r\n const claudeDir = join(workspacePath, '.claude');\r\n const settingsPath = join(claudeDir, 'settings.local.json');\r\n\r\n if (!existsSync(claudeDir)) {\r\n mkdirSync(claudeDir, { recursive: true });\r\n }\r\n\r\n // Read existing settings or start fresh\r\n let settings: Record<string, unknown> = {};\r\n if (existsSync(settingsPath)) {\r\n try {\r\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\r\n } catch { /* start fresh */ }\r\n }\r\n\r\n // Set the apiKeyHelper to our token reader script\r\n settings.apiKeyHelper = helperPath;\r\n\r\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\r\n}\r\n\r\n/**\r\n * Clear credential-file auth from workspace settings.\r\n *\r\n * When switching from a credential-file provider (e.g. Kimi) to a static/plan-based\r\n * provider (e.g. Anthropic), the apiKeyHelper must be removed from\r\n * .claude/settings.local.json. Otherwise Claude Code will keep using the stale\r\n * token helper and fail with \"Invalid API key\".\r\n */\r\nexport function clearCredentialFileAuth(workspacePath: string): void {\r\n const settingsPath = join(workspacePath, '.claude', 'settings.local.json');\r\n if (!existsSync(settingsPath)) return;\r\n\r\n try {\r\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\r\n if (!settings.apiKeyHelper) return; // Nothing to clear\r\n\r\n delete settings.apiKeyHelper;\r\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\r\n } catch { /* non-fatal */ }\r\n}\r\n"],"mappings":"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"}
|