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":"workflows-N1UTipYl.js","names":["execAsync"],"sources":["../../src/lib/lifecycle/teardown-workspace.ts","../../src/lib/lifecycle/workflows.ts"],"sourcesContent":["/**\n * teardown-workspace — Full workspace cleanup.\n *\n * Consolidates workspace teardown from close-out.ts and workspace-manager.ts.\n * Handles: tmux sessions, TLDR daemon, Docker containers, git worktrees,\n * agent state directories, and (optionally) git branches.\n *\n * The workspace-manager's removeWorkspace() handles additional project-specific\n * cleanup (DNS, tunnels, Hume, ports) that this module does not cover.\n * In Phase 2, removeWorkspace() will delegate to this module for the common steps.\n */\n\nimport { existsSync, rmSync, unlinkSync } from 'fs';\nimport { join, basename, dirname } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { AGENTS_DIR } from '../paths.js';\nimport { sessionExists } from '../tmux.js';\nimport type { LifecycleContext, StepResult, TeardownOptions } from './types.js';\nimport { stepOk, stepSkipped, stepFailed } from './types.js';\nimport { findWorkspacePath } from './archive-planning.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Kill tmux sessions associated with an issue.\n */\nasync function killTmuxSessions(issueLower: string): Promise<StepResult> {\n const step = 'teardown:tmux-sessions';\n // Legacy naming: agent-{issue}, review-{issue}, etc.\n const patterns = [\n `agent-${issueLower}`,\n `review-${issueLower}`,\n `test-${issueLower}`,\n `merge-${issueLower}`,\n `planning-${issueLower}`,\n ];\n\n let killed = 0;\n for (const session of patterns) {\n if (sessionExists(session)) {\n try {\n await execAsync(`tmux kill-session -t ${session}`);\n killed++;\n } catch {\n // session may have died between check and kill\n }\n }\n }\n\n // NOTE: Per-project ephemeral specialists (specialist-{project}-{type}) are NOT killed here.\n // They belong to the project, not the issue, and accumulate context across issues via --resume.\n // Their grace period / idle timeout handles cleanup when no new work arrives.\n\n if (killed > 0) {\n return stepOk(step, [`Killed ${killed} tmux session(s)`]);\n }\n return stepSkipped(step, ['No tmux sessions found']);\n}\n\n/**\n * Stop TLDR daemon if workspace has a .venv.\n */\nasync function stopTldrDaemon(workspacePath: string): Promise<StepResult> {\n const step = 'teardown:tldr-daemon';\n const venvPath = join(workspacePath, '.venv');\n if (!existsSync(venvPath)) {\n return stepSkipped(step, ['No .venv found']);\n }\n try {\n const { getTldrDaemonService } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonService(workspacePath, venvPath);\n await tldrService.stop();\n return stepOk(step, ['Stopped TLDR daemon']);\n } catch {\n return stepSkipped(step, ['TLDR daemon not running or failed to stop (non-fatal)']);\n }\n}\n\n/**\n * Stop Docker containers for the workspace.\n */\nasync function stopDocker(\n workspacePath: string,\n projectName: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:docker';\n try {\n const { stopWorkspaceDocker } = await import('../workspace-manager.js');\n await stopWorkspaceDocker(workspacePath, projectName, issueLower);\n return stepOk(step, ['Stopped Docker containers']);\n } catch {\n return stepSkipped(step, ['Docker cleanup skipped (not running or failed)']);\n }\n}\n\n/**\n * Kill orphaned host processes for a workspace.\n *\n * When workspaces use `./dev all`, Vite/node processes run on the host (not in\n * containers). Docker compose down doesn't touch them, so they leak and exhaust\n * inotify watchers. This step finds and kills processes whose cwd or args\n * reference the workspace path.\n */\nasync function killOrphanedProcesses(workspacePath: string): Promise<StepResult> {\n const step = 'teardown:orphaned-processes';\n try {\n // Find PIDs with cwd matching the workspace path\n const { stdout } = await execAsync(\n `lsof +D \"${workspacePath}\" -t 2>/dev/null || true`,\n { encoding: 'utf-8', timeout: 10000 },\n );\n const pids = stdout.trim().split('\\n').filter(Boolean).map(p => p.trim()).filter(p => /^\\d+$/.test(p));\n\n if (pids.length === 0) {\n return stepSkipped(step, ['No orphaned processes found']);\n }\n\n // Don't kill our own process or the dashboard\n const myPid = String(process.pid);\n const safePids = pids.filter(p => p !== myPid);\n\n if (safePids.length === 0) {\n return stepSkipped(step, ['No orphaned processes to kill']);\n }\n\n await execAsync(`kill ${safePids.join(' ')} 2>/dev/null || true`, { encoding: 'utf-8', timeout: 5000 });\n return stepOk(step, [`Killed ${safePids.length} orphaned process(es)`]);\n } catch {\n return stepSkipped(step, ['Orphaned process cleanup failed (non-fatal)']);\n }\n}\n\n/**\n * Sync workspace beads to the project-root beads database before workspace deletion.\n * Without this, beads created in the workspace's .beads/dolt/ are lost when the worktree is removed.\n */\nasync function syncWorkspaceBeads(\n projectPath: string,\n workspacePath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:sync-beads';\n const workspaceBeadsDir = join(workspacePath, '.beads');\n\n if (!existsSync(workspaceBeadsDir)) {\n return stepSkipped(step, ['No .beads directory in workspace']);\n }\n\n try {\n // Export workspace beads to JSONL\n const { stdout: exportOutput } = await execAsync(\n 'bd export --output .beads/issues-export.jsonl 2>&1 || true',\n { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 }\n );\n\n const exportPath = join(workspacePath, '.beads', 'issues-export.jsonl');\n if (!existsSync(exportPath)) {\n // Try syncing directly — bd sync exports to the standard JSONL\n await execAsync('bd sync 2>&1 || true', { cwd: workspacePath, encoding: 'utf-8', timeout: 15000 });\n }\n\n // Import workspace beads into project-root database\n // Use bd import if available, otherwise copy JSONL entries\n try {\n await execAsync(\n `bd import \"${join(workspacePath, '.beads', 'issues.jsonl')}\" 2>&1 || true`,\n { cwd: projectPath, encoding: 'utf-8', timeout: 15000 }\n );\n return stepOk(step, [`Synced workspace beads to project root for ${issueLower}`]);\n } catch {\n // bd import may not exist — try manual JSONL merge\n const { readFileSync, appendFileSync } = await import('fs');\n const wsJsonl = join(workspacePath, '.beads', 'issues.jsonl');\n const projJsonl = join(projectPath, '.beads', 'issues.jsonl');\n\n if (existsSync(wsJsonl) && existsSync(projJsonl)) {\n const wsContent = readFileSync(wsJsonl, 'utf-8');\n const issuePattern = issueLower.replace('-', '[-_]');\n const relevantLines = wsContent.split('\\n').filter(\n line => line.trim() && new RegExp(issuePattern, 'i').test(line)\n );\n if (relevantLines.length > 0) {\n appendFileSync(projJsonl, '\\n' + relevantLines.join('\\n'));\n return stepOk(step, [`Appended ${relevantLines.length} beads entries for ${issueLower} to project JSONL`]);\n }\n }\n return stepSkipped(step, ['No beads to sync or import not available']);\n }\n } catch (err) {\n return stepFailed(step, `Failed to sync workspace beads: ${(err as Error).message}`);\n }\n}\n\n/**\n * Clear beads for this issue from the project-root .beads/issues.jsonl.\n * On wipe, beads should be removed so the user starts fresh.\n */\nasync function clearProjectBeads(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:clear-beads';\n const projJsonl = join(projectPath, '.beads', 'issues.jsonl');\n\n if (!existsSync(projJsonl)) {\n return stepSkipped(step, ['No .beads/issues.jsonl in project root']);\n }\n\n try {\n const { readFileSync, writeFileSync } = await import('fs');\n const content = readFileSync(projJsonl, 'utf-8');\n const lines = content.split('\\n');\n const issueUpper = issueLower.toUpperCase();\n const before = lines.length;\n // Remove lines that reference this issue (by ID in the title or issue field)\n const filtered = lines.filter(line => {\n if (!line.trim()) return true; // keep blank lines\n try {\n const entry = JSON.parse(line);\n const title = (entry.title || '').toUpperCase();\n const issue = (entry.issue || '').toUpperCase();\n return !title.includes(issueUpper) && issue !== issueUpper;\n } catch {\n return true; // keep unparseable lines\n }\n });\n const removed = before - filtered.length;\n if (removed > 0) {\n writeFileSync(projJsonl, filtered.join('\\n'));\n return stepOk(step, [`Removed ${removed} beads entries for ${issueLower} from project JSONL`]);\n }\n return stepSkipped(step, [`No beads entries found for ${issueLower}`]);\n } catch (err) {\n return stepFailed(step, `Failed to clear beads: ${(err as Error).message}`);\n }\n}\n\n/**\n * Remove git worktree for the workspace.\n */\nasync function removeWorktree(\n projectPath: string,\n workspacePath: string,\n): Promise<StepResult> {\n const step = 'teardown:worktree';\n if (!existsSync(workspacePath)) {\n return stepSkipped(step, ['Workspace directory does not exist']);\n }\n\n try {\n await execAsync(`git worktree remove \"${workspacePath}\" --force`, { cwd: projectPath });\n return stepOk(step, ['Removed git worktree']);\n } catch {\n // worktree remove failed — try direct removal\n try {\n rmSync(workspacePath, { recursive: true, force: true });\n return stepOk(step, ['Removed workspace directory (worktree remove failed, used rmSync)']);\n } catch (err) {\n return stepFailed(step, `Failed to remove workspace: ${(err as Error).message}`);\n }\n }\n}\n\n/**\n * Remove agent state directories (~/.panopticon/agents/agent-<issue>/ and planning-<issue>/).\n */\nasync function removeAgentState(issueLower: string): Promise<StepResult> {\n const step = 'teardown:agent-state';\n const dirs = [\n join(AGENTS_DIR, `agent-${issueLower}`),\n join(AGENTS_DIR, `planning-${issueLower}`),\n ];\n\n let removed = 0;\n for (const dir of dirs) {\n if (existsSync(dir)) {\n rmSync(dir, { recursive: true, force: true });\n removed++;\n }\n }\n\n if (removed > 0) {\n return stepOk(step, [`Removed ${removed} agent state director${removed === 1 ? 'y' : 'ies'}`]);\n }\n return stepSkipped(step, ['No agent state directories found']);\n}\n\n/**\n * Delete feature branches (local + remote).\n */\nasync function deleteBranches(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:branches';\n const branchName = `feature/${issueLower}`;\n const details: string[] = [];\n\n // Delete local branch\n try {\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted local branch ${branchName}`);\n } catch {\n details.push(`Local branch ${branchName} not found (already deleted)`);\n }\n\n // Delete remote branch\n try {\n await execAsync(`git push origin --delete \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted remote branch ${branchName}`);\n } catch {\n details.push(`Remote branch ${branchName} not found (already deleted)`);\n }\n\n return stepOk(step, details);\n}\n\n/**\n * Clear shadow state for an issue.\n */\nasync function clearShadowState(issueId: string): Promise<StepResult> {\n const step = 'teardown:shadow-state';\n try {\n const { removeShadowState } = await import('../shadow-state.js');\n const result = removeShadowState(issueId);\n if (result.success) {\n return stepOk(step, [`Cleared shadow state for ${issueId}`]);\n }\n return stepSkipped(step, ['No shadow state found']);\n } catch {\n return stepSkipped(step, ['Shadow state cleanup skipped (non-fatal)']);\n }\n}\n\n/**\n * Remove legacy .planning/<issue>/ directory from project root.\n */\nasync function clearLegacyPlanningDir(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:legacy-planning-dir';\n const legacyDir = join(projectPath, '.planning', issueLower);\n if (existsSync(legacyDir)) {\n rmSync(legacyDir, { recursive: true, force: true });\n return stepOk(step, [`Deleted legacy planning dir: ${legacyDir}`]);\n }\n return stepSkipped(step, ['No legacy planning directory found']);\n}\n\n/**\n * Clear .planning/.planning-complete marker from workspace.\n * Only runs if workspace still exists (before worktree removal).\n */\nasync function clearPlanningMarker(workspacePath: string): Promise<StepResult> {\n const step = 'teardown:planning-marker';\n const markerPath = join(workspacePath, '.planning', '.planning-complete');\n if (existsSync(markerPath)) {\n unlinkSync(markerPath);\n return stepOk(step, ['Cleared .planning-complete marker']);\n }\n return stepSkipped(step, ['No .planning-complete marker found']);\n}\n\n/**\n * Build template placeholders for project-specific cleanup (tunnel, Hume).\n */\nfunction buildPlaceholders(\n ctx: LifecycleContext,\n opts: TeardownOptions,\n workspacePath: string,\n) {\n const issueLower = ctx.issueId.toLowerCase();\n const featureFolder = `feature-${issueLower}`;\n const projName = opts.projectName || ctx.projectName || basename(ctx.projectPath);\n const domain = opts.workspaceConfig?.dns?.domain || 'localhost';\n return {\n FEATURE_NAME: issueLower,\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${issueLower}`,\n COMPOSE_PROJECT: `${projName}-${featureFolder}`,\n DOMAIN: domain,\n PROJECT_NAME: projName,\n PROJECT_PATH: ctx.projectPath,\n WORKSPACE_PATH: workspacePath,\n };\n}\n\n/**\n * Remove Cloudflare tunnel ingress for workspace.\n */\nasync function removeTunnelConfig(\n tunnelConfig: any,\n placeholders: Record<string, string>,\n): Promise<StepResult> {\n const step = 'teardown:tunnel';\n try {\n const { removeTunnelIngress } = await import('../tunnel.js');\n const result = await removeTunnelIngress(tunnelConfig, placeholders as any);\n return stepOk(step, result.steps || ['Removed tunnel ingress']);\n } catch (err) {\n return stepSkipped(step, [`Tunnel cleanup warning: ${(err as Error).message}`]);\n }\n}\n\n/**\n * Remove Hume EVI config for workspace.\n */\nasync function removeHumeEviConfig(\n humeConfig: any,\n placeholders: Record<string, string>,\n): Promise<StepResult> {\n const step = 'teardown:hume';\n try {\n const { deleteHumeConfig } = await import('../hume.js');\n const result = await deleteHumeConfig(humeConfig, placeholders as any);\n return stepOk(step, result.steps || ['Removed Hume EVI config']);\n } catch (err) {\n return stepSkipped(step, [`Hume cleanup warning: ${(err as Error).message}`]);\n }\n}\n\n/**\n * Full workspace teardown.\n *\n * Steps (in order):\n * 1. Kill tmux sessions\n * 2. Clear shadow state\n * 3. Clear legacy planning directory\n * 4. Stop TLDR daemon (if workspace exists)\n * 5. Stop Docker containers (if workspace exists)\n * 6. Clear planning marker (if workspace exists, before deletion)\n * 7. Remove tunnel config (if workspace config provided)\n * 8. Remove Hume config (if workspace config provided)\n * 9. Remove git worktree + workspace directory\n * 10. Remove agent state directories\n * 11. (Optional) Delete feature branches\n */\nexport async function teardownWorkspace(\n ctx: LifecycleContext,\n opts: TeardownOptions = {},\n): Promise<StepResult[]> {\n const issueLower = ctx.issueId.toLowerCase();\n const projName = opts.projectName || ctx.projectName || ctx.issueId.split('-')[0].toLowerCase();\n const workspacePath = findWorkspacePath(ctx.projectPath, issueLower);\n const shouldDeleteWorkspace = opts.deleteWorkspace !== false; // default true\n const results: StepResult[] = [];\n\n // 1. Kill tmux sessions\n results.push(await killTmuxSessions(issueLower));\n\n // 2. Clear shadow state (always runs)\n results.push(await clearShadowState(ctx.issueId));\n\n // 3. Clear legacy planning directory (always runs)\n results.push(await clearLegacyPlanningDir(ctx.projectPath, issueLower));\n\n // 4-9: Workspace-specific cleanup\n if (workspacePath && existsSync(workspacePath)) {\n // 4. Stop TLDR daemon (only if deleting workspace)\n if (shouldDeleteWorkspace) {\n results.push(await stopTldrDaemon(workspacePath));\n }\n\n // 5. Stop Docker containers (only if deleting workspace)\n if (shouldDeleteWorkspace && !opts.skipDocker) {\n results.push(await stopDocker(workspacePath, projName, issueLower));\n }\n\n // 5b. Kill orphaned host processes (Vite, node) that survive Docker teardown\n if (shouldDeleteWorkspace) {\n results.push(await killOrphanedProcesses(workspacePath));\n }\n\n // 6. Clear planning marker (before workspace deletion, or when preserving workspace)\n results.push(await clearPlanningMarker(workspacePath));\n\n // 6b. Beads lifecycle: sync or clear depending on context (PAN-412)\n // Normal completion (approve/closeOut): sync beads to project root to preserve history.\n // Destructive wipe: clear beads so the user starts fresh.\n if (opts.clearBeads) {\n results.push(await clearProjectBeads(ctx.projectPath, issueLower));\n } else if (shouldDeleteWorkspace) {\n results.push(await syncWorkspaceBeads(ctx.projectPath, workspacePath, issueLower));\n }\n\n // 7-8: Project-specific cleanup (tunnel, Hume) — only when deleting workspace and config provided\n if (shouldDeleteWorkspace && (opts.workspaceConfig?.tunnel || opts.workspaceConfig?.hume)) {\n const placeholders = buildPlaceholders(ctx, opts, workspacePath);\n\n if (opts.workspaceConfig.tunnel) {\n results.push(await removeTunnelConfig(opts.workspaceConfig.tunnel, placeholders));\n }\n if (opts.workspaceConfig.hume) {\n results.push(await removeHumeEviConfig(opts.workspaceConfig.hume, placeholders));\n }\n }\n\n // 9. Remove worktree + workspace directory (only if deleting workspace)\n if (shouldDeleteWorkspace) {\n results.push(await removeWorktree(ctx.projectPath, workspacePath));\n }\n } else {\n results.push(stepSkipped('teardown:workspace', ['No workspace found to clean up']));\n }\n\n // 10. Remove agent state\n results.push(await removeAgentState(issueLower));\n\n // 11. Delete branches (only if explicitly requested)\n if (opts.deleteBranches) {\n results.push(await deleteBranches(ctx.projectPath, issueLower));\n }\n\n return results;\n}\n","/**\n * Lifecycle workflows — Compose atomic operations into complete workflows.\n *\n * approve() — Post-merge: archive + close + teardown + compact-beads\n * close() — Simple close: close-issue + teardown\n * closeOut() — Full ceremony: verify-merged + archive + teardown + close + label + clear-status\n * deepWipe() — Destructive: teardown(deleteBranches) + delete agent state + reset issue\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { PANOPTICON_HOME } from '../paths.js';\nimport type {\n LifecycleContext,\n WorkflowResult,\n StepResult,\n ApproveOptions,\n DeepWipeOptions,\n ArchiveOptions,\n} from './types.js';\nimport { stepOk, stepSkipped, stepFailed, getLinearApiKey } from './types.js';\nimport { archivePlanning, findWorkspacePath } from './archive-planning.js';\nimport { closeIssue, type CloseIssueOptions } from './close-issue.js';\nimport { teardownWorkspace } from './teardown-workspace.js';\nimport { compactBeads } from './compact-beads.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Build a WorkflowResult from collected steps.\n */\nfunction buildResult(\n workflow: WorkflowResult['workflow'],\n issueId: string,\n steps: StepResult[],\n startTime: number,\n): WorkflowResult {\n return {\n workflow,\n issueId,\n success: steps.every(s => s.success),\n steps,\n duration: Date.now() - startTime,\n };\n}\n\n/**\n * approve() — Post-merge lifecycle.\n *\n * 1. Archive planning artifacts (PRD move + .planning/ preservation)\n * 2. Close issue on tracker\n * 3. Teardown workspace\n * 4. Compact beads\n * 5. Clear review status\n *\n * Note: The actual merge step is NOT included here — the merge-agent\n * handles merge validation. This workflow runs AFTER merge completes.\n */\nexport async function approve(\n ctx: LifecycleContext,\n opts: ApproveOptions & CloseIssueOptions & ArchiveOptions = {},\n): Promise<WorkflowResult> {\n const start = Date.now();\n const allSteps: StepResult[] = [];\n\n // 1. Archive planning\n const archiveSteps = await archivePlanning(ctx, opts);\n allSteps.push(...archiveSteps);\n\n // If archive failed, stop — don't destroy unarchived artifacts\n const archiveFailed = archiveSteps.some(s => !s.success && !s.skipped);\n if (archiveFailed) {\n allSteps.push(stepFailed('approve:abort', 'Stopped — archiving failed, workspace preserved'));\n return buildResult('approve', ctx.issueId, allSteps, start);\n }\n\n // 2. Close issue\n const closeSteps = await closeIssue(ctx, {\n tracker: opts.tracker,\n comment: 'Merged to main via Panopticon lifecycle',\n applyLabel: true,\n });\n allSteps.push(...closeSteps);\n\n // 3. Teardown workspace\n const teardownSteps = await teardownWorkspace(ctx);\n allSteps.push(...teardownSteps);\n\n // 4. Compact beads (non-blocking — failure doesn't affect workflow success)\n if (!opts.skipBeadsCompaction) {\n const beadsResult = await compactBeads(ctx);\n allSteps.push(beadsResult);\n }\n\n // 5. Clear review status\n const clearResult = await clearReviewStatusStep(ctx.issueId);\n allSteps.push(clearResult);\n\n return buildResult('approve', ctx.issueId, allSteps, start);\n}\n\n/**\n * close() — Simple issue close with teardown.\n *\n * Used when an issue is being closed without merge (canceled, won't-do, etc.)\n * Does NOT archive workspace artifacts.\n *\n * 1. Close issue on tracker\n * 2. Teardown workspace\n * 3. Clear review status\n */\nexport async function close(\n ctx: LifecycleContext,\n opts: CloseIssueOptions = {},\n): Promise<WorkflowResult> {\n const start = Date.now();\n const allSteps: StepResult[] = [];\n\n // 1. Close issue\n const closeSteps = await closeIssue(ctx, {\n tracker: opts.tracker,\n reason: opts.reason,\n applyLabel: false,\n });\n allSteps.push(...closeSteps);\n\n // 2. Teardown workspace\n const teardownSteps = await teardownWorkspace(ctx);\n allSteps.push(...teardownSteps);\n\n // 3. Clear review status\n const clearResult = await clearReviewStatusStep(ctx.issueId);\n allSteps.push(clearResult);\n\n return buildResult('close', ctx.issueId, allSteps, start);\n}\n\n/**\n * closeOut() — Full close-out ceremony.\n *\n * This is the human-gated verification and cleanup workflow.\n * Replaces the monolithic executeCloseOut() function.\n *\n * 1. Verify branch merged (hard fail if not — must pass before any cleanup)\n * 2. Move PRD + archive workspace artifacts (hard fail if archiving fails)\n * 3. Clean up workspace (tmux, TLDR, Docker, worktree)\n * 4. Clean up agent state\n * 5. Close issue on tracker\n * 6. Apply closed-out label\n * 7. Clear review status\n */\nexport async function closeOut(\n ctx: LifecycleContext,\n opts: CloseIssueOptions & ArchiveOptions = {},\n): Promise<WorkflowResult> {\n const start = Date.now();\n const allSteps: StepResult[] = [];\n\n // 1. Verify branch merged (hard fail — must pass before we archive or clean up)\n const mergeVerify = await verifyBranchMerged(ctx);\n allSteps.push(mergeVerify);\n if (!mergeVerify.success && !mergeVerify.skipped) {\n return buildResult('close-out', ctx.issueId, allSteps, start);\n }\n\n // 2. Move PRD + archive workspace artifacts\n const archiveSteps = await archivePlanning(ctx, opts);\n allSteps.push(...archiveSteps);\n\n // Hard fail on archive failure — don't destroy unarchived artifacts\n const archiveFailed = archiveSteps.some(s => !s.success && !s.skipped);\n if (archiveFailed) {\n allSteps.push(stepFailed('close-out:abort', 'Stopped — archiving failed, workspace preserved'));\n return buildResult('close-out', ctx.issueId, allSteps, start);\n }\n\n // 4+5. Teardown workspace + agent state\n const teardownSteps = await teardownWorkspace(ctx);\n allSteps.push(...teardownSteps);\n\n // 6+7. Close issue + apply label\n const closeSteps = await closeIssue(ctx, {\n tracker: opts.tracker,\n comment: 'Closed via close-out ceremony',\n applyLabel: true,\n });\n allSteps.push(...closeSteps);\n\n // 8. Clear review status\n const clearResult = await clearReviewStatusStep(ctx.issueId);\n allSteps.push(clearResult);\n\n return buildResult('close-out', ctx.issueId, allSteps, start);\n}\n\n/**\n * deepWipe() — Destructive cleanup for abandoned workspaces.\n *\n * 1. Teardown workspace (with branch deletion)\n * 2. (Optional) Reset issue to backlog/open\n * 3. Clear review status\n */\nexport async function deepWipe(\n ctx: LifecycleContext,\n opts: DeepWipeOptions = {},\n): Promise<WorkflowResult> {\n const start = Date.now();\n const allSteps: StepResult[] = [];\n const { deleteWorkspace = true, deleteBranches = true, resetIssue = true, onProgress } = opts;\n\n const TOTAL_STEPS = 3 + (resetIssue ? 1 : 0);\n let stepNum = 0;\n\n const progress = (label: string, detail: string, status: 'active' | 'complete' | 'error' = 'active') => {\n onProgress?.({ step: stepNum, total: TOTAL_STEPS, label, detail, status });\n };\n\n // 1. Teardown workspace (aggressive — delete branches, project-specific cleanup, clear beads)\n stepNum = 1;\n progress('Tearing down workspace', 'Killing agents, stopping services, removing files');\n const teardownSteps = await teardownWorkspace(ctx, {\n deleteWorkspace,\n deleteBranches,\n clearBeads: true,\n workspaceConfig: opts.workspaceConfig,\n projectName: opts.projectName,\n });\n allSteps.push(...teardownSteps);\n const teardownFailed = teardownSteps.some(s => !s.success && !s.skipped);\n progress('Tearing down workspace', teardownFailed ? 'Some steps failed' : 'Workspace torn down', teardownFailed ? 'error' : 'complete');\n\n // 2. Delete git branches\n stepNum = 2;\n progress('Deleting git branches', `feature/${ctx.issueId.toLowerCase()}`);\n // Branch deletion is already handled in teardownWorkspace when deleteBranches is true,\n // but we report it as a separate visible step\n progress('Deleting git branches', deleteBranches ? 'Branches removed' : 'Skipped', 'complete');\n\n // 3. Reset issue to open/backlog\n if (resetIssue) {\n stepNum = 3;\n progress('Resetting issue status', `${ctx.issueId} → Todo`);\n const resetResult = await resetIssueToTodo(ctx);\n allSteps.push(resetResult);\n progress('Resetting issue status', resetResult.success ? 'Issue reset to Todo' : (resetResult.error || 'Failed'), resetResult.success ? 'complete' : 'error');\n }\n\n // 4. Clear review status\n stepNum = resetIssue ? 4 : 3;\n progress('Clearing review status', 'Removing specialist state');\n const clearResult = await clearReviewStatusStep(ctx.issueId);\n allSteps.push(clearResult);\n progress('Clearing review status', 'Review status cleared', 'complete');\n\n return buildResult('deep-wipe', ctx.issueId, allSteps, start);\n}\n\n// --- Internal helpers ---\n\n/**\n * Verify feature branch is merged into main.\n */\nasync function verifyBranchMerged(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-out:verify-merged';\n const issueLower = ctx.issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n try {\n // Check review-status first — the merge specialist validates before marking merged\n try {\n const { loadReviewStatuses } = await import('../review-status.js');\n const statuses = loadReviewStatuses();\n const issueKey = ctx.issueId.toUpperCase();\n if (statuses[issueKey]?.mergeStatus === 'merged') {\n return stepOk(step, ['Merge specialist confirmed merge completed']);\n }\n } catch {\n // review-status.json may not exist, continue with git checks\n }\n\n\n // Check if branch exists locally\n const { stdout: branchExists } = await execAsync(\n `git branch --list \"${branchName}\" 2>/dev/null || true`,\n { cwd: ctx.projectPath, encoding: 'utf-8' },\n );\n\n if (branchExists.trim()) {\n // Use merge-base --is-ancestor: checks if the branch tip is reachable from main\n // This works for regular merges, squash merges, and cherry-picks\n try {\n await execAsync(\n `git merge-base --is-ancestor ${branchName} main`,\n { cwd: ctx.projectPath, encoding: 'utf-8' },\n );\n return stepOk(step, ['All commits merged to main']);\n } catch {\n // Not an ancestor — branch has unmerged work\n const { stdout: unmerged } = await execAsync(\n `git log main..${branchName} --oneline 2>/dev/null || true`,\n { cwd: ctx.projectPath, encoding: 'utf-8' },\n );\n const count = unmerged.trim() ? unmerged.trim().split('\\n').length : 0;\n return stepFailed(step, `${count} unmerged commit(s) on ${branchName}. Merge before closing out.`);\n }\n }\n\n // Check remote\n const { stdout: remoteBranch } = await execAsync(\n `git ls-remote --heads origin \"${branchName}\" 2>/dev/null || true`,\n { cwd: ctx.projectPath, encoding: 'utf-8' },\n );\n\n if (remoteBranch.trim()) {\n await execAsync(`git fetch origin ${branchName}`, { cwd: ctx.projectPath }).catch(() => {});\n try {\n await execAsync(\n `git merge-base --is-ancestor origin/${branchName} main`,\n { cwd: ctx.projectPath, encoding: 'utf-8' },\n );\n return stepOk(step, ['Remote branch fully merged']);\n } catch {\n const { stdout: remoteUnmerged } = await execAsync(\n `git log main..origin/${branchName} --oneline 2>/dev/null || true`,\n { cwd: ctx.projectPath, encoding: 'utf-8' },\n );\n const count = remoteUnmerged.trim() ? remoteUnmerged.trim().split('\\n').length : 0;\n return stepFailed(step, `${count} unmerged commit(s) on remote ${branchName}.`);\n }\n }\n\n // No branch at all — assume squash-merged and branch deleted\n return stepOk(step, ['Branch already cleaned up (squash-merged)']);\n } catch (err) {\n return stepFailed(step, `Could not verify merge: ${(err as Error).message}`);\n }\n}\n\n/**\n * Reset issue back to open/backlog state (for deep-wipe).\n */\nasync function resetIssueToTodo(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'deep-wipe:reset-issue';\n try {\n if (ctx.github) {\n const { owner, repo, number } = ctx.github;\n // Reopen the issue\n await execAsync(\n `gh issue reopen ${number} --repo ${owner}/${repo}`,\n { encoding: 'utf-8' },\n ).catch(() => {}); // May already be open\n // Remove lifecycle labels\n const labelsToRemove = ['in-review', 'in-progress', 'planned', 'planning', 'Review: Approved', 'Review: Failed', 'ready-for-merge'];\n for (const label of labelsToRemove) {\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --remove-label \"${label}\"`,\n { encoding: 'utf-8' },\n ).catch(() => {}); // Label may not exist\n }\n return stepOk(step, [`Reset GitHub issue #${number}: reopened and cleared labels`]);\n }\n\n // Linear: reopen to Todo\n const linearApiKey = getLinearApiKey();\n if (linearApiKey) {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey: linearApiKey });\n const issueNum = parseInt(ctx.issueId.split('-').pop() || '0', 10);\n const teamKey = ctx.issueId.split('-')[0].toUpperCase();\n const results = await client.issues({\n filter: {\n number: { eq: issueNum },\n team: { key: { eq: teamKey } },\n },\n first: 1,\n });\n if (results.nodes.length > 0) {\n const issue = results.nodes[0];\n const team = await issue.team;\n if (team) {\n const states = await team.states();\n const todoState = states.nodes.find(s => s.type === 'unstarted' && s.name === 'Todo') ||\n states.nodes.find(s => s.type === 'unstarted');\n if (todoState) {\n await issue.update({ stateId: todoState.id });\n }\n }\n }\n return stepOk(step, [`Reset Linear issue ${ctx.issueId} to Todo`]);\n }\n\n return stepSkipped(step, ['No tracker available to reset issue']);\n } catch (err) {\n return stepFailed(step, `Failed to reset issue: ${(err as Error).message}`);\n }\n}\n\n/**\n * Clear review status for an issue.\n */\nasync function clearReviewStatusStep(issueId: string): Promise<StepResult> {\n const step = 'clear-review-status';\n try {\n const { clearReviewStatus } = await import('../review-status.js');\n clearReviewStatus(issueId.toUpperCase());\n return stepOk(step, ['Review status cleared']);\n } catch {\n // Fallback: direct file manipulation\n try {\n const statusFile = join(PANOPTICON_HOME, 'review-status.json');\n if (existsSync(statusFile)) {\n const data = JSON.parse(readFileSync(statusFile, 'utf-8'));\n const upperKey = issueId.toUpperCase();\n if (data[upperKey]) {\n delete data[upperKey];\n const { writeFileSync } = await import('fs');\n writeFileSync(statusFile, JSON.stringify(data, null, 2));\n }\n }\n return stepOk(step, ['Review status cleared (direct)']);\n } catch (innerErr) {\n return stepSkipped(step, [`Failed to clear review status (non-fatal): ${(innerErr as Error).message}`]);\n }\n }\n}\n\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-config-cmp5_ipD.js","names":[],"sources":["../../src/lib/workspace-config.ts"],"sourcesContent":["/**\n * Workspace Configuration Types\n *\n * Defines the schema for project workspace configuration in projects.yaml\n */\n\nexport interface RepoConfig {\n /** Name of the repo in the workspace (e.g., 'fe', 'api') */\n name: string;\n /** Path to source repo relative to project root */\n path: string;\n /** Branch prefix for feature branches (default: 'feature/') */\n branch_prefix?: string;\n /** Default branch to create feature branches from (default: 'main') */\n default_branch?: string;\n}\n\nexport interface DnsConfig {\n /** Base domain (e.g., 'myn.test') */\n domain: string;\n /**\n * DNS entry patterns. Supports placeholders:\n * - {{FEATURE_FOLDER}}: e.g., 'feature-min-123'\n * - {{FEATURE_NAME}}: e.g., 'min-123'\n * - {{DOMAIN}}: the domain value\n */\n entries: string[];\n /** How to sync DNS: 'wsl2hosts' | 'hosts_file' | 'dnsmasq' */\n sync_method?: 'wsl2hosts' | 'hosts_file' | 'dnsmasq';\n}\n\nexport interface PortConfig {\n /** Port range [start, end] */\n range: [number, number];\n}\n\nexport interface DockerConfig {\n /** Path to Traefik compose file (relative to project root) */\n traefik?: string;\n /** Path to devcontainer template directory */\n compose_template?: string;\n}\n\nexport interface AgentTemplateConfig {\n /** Path to agent template directory */\n template_dir: string;\n /** Files to process with placeholder replacement */\n templates?: Array<{\n source: string;\n target: string;\n }>;\n /** Directories to copy from project template into workspace */\n copy_dirs?: string[];\n /** @deprecated Use copy_dirs instead */\n symlinks?: string[];\n}\n\nexport interface EnvConfig {\n /** Environment variable template with placeholders */\n template?: string;\n /** Additional env vars from secrets */\n secrets_file?: string;\n}\n\nexport interface ServiceConfig {\n /** Service name (e.g., 'api', 'frontend') */\n name: string;\n /** Path relative to workspace (e.g., 'api', 'fe') */\n path: string;\n /** Command to start the service natively (e.g., './run-dev.sh', 'pnpm start') */\n start_command: string;\n /** Command to start inside Docker container (if different) */\n docker_command?: string;\n /** Health check URL pattern (supports placeholders) */\n health_url?: string;\n /** Port the service runs on */\n port?: number;\n}\n\nexport interface TestConfig {\n /** Test type: 'maven' | 'vitest' | 'playwright' | 'jest' | 'pytest' | 'cargo' */\n type: string;\n /** Path to test directory (relative to workspace) */\n path: string;\n /** Command to run tests */\n command: string;\n /** Run inside container for feature workspaces */\n container?: boolean;\n /** Container name pattern (uses {{FEATURE_FOLDER}}) */\n container_name?: string;\n /** Additional environment variables */\n env?: Record<string, string>;\n}\n\nexport interface QualityGateConfig {\n /** Command to run (e.g., 'pnpm lint', 'pnpm typecheck') */\n command: string;\n /** Path relative to workspace (e.g., 'frontend' for polyrepo) */\n path?: string;\n /** If true, merge is blocked on failure (default: true) */\n required?: boolean;\n /** Additional environment variables */\n env?: Record<string, string>;\n /** When to run: before push (default) or after push */\n phase?: 'pre_push' | 'post_push';\n /** Gate type: shell command (default) or HTTP health check */\n type?: 'command' | 'http_health';\n /** URL for http_health type */\n url?: string;\n /** Seconds to wait for deployment before checking (http_health only) */\n wait?: number;\n /** Expected HTTP status code (http_health only, default: 200) */\n expect_status?: number;\n /** Run command inside a Docker container (uses docker exec) */\n container?: boolean;\n /** Container name pattern (supports {{FEATURE_FOLDER}} etc.) */\n container_name?: string;\n}\n\nexport interface DatabaseConfig {\n /** Path to seed file for database initialization */\n seed_file?: string;\n /** Command to run after loading seed (e.g., sanitization script) */\n seed_command?: string;\n /** Command to create snapshots from external source (e.g., kubectl exec pg_dump) */\n snapshot_command?: string;\n /** External database connection for direct access */\n external_db?: {\n host: string;\n port?: number;\n database: string;\n user?: string;\n /** Environment variable name containing password */\n password_env?: string;\n };\n /** Container name pattern (supports {{PROJECT}} placeholder) */\n container_name?: string;\n /** Migration tool configuration */\n migrations?: {\n type: 'flyway' | 'liquibase' | 'prisma' | 'typeorm' | 'custom';\n path?: string;\n command?: string;\n };\n}\n\nexport interface TunnelHostname {\n /** Hostname pattern (supports {{FEATURE_FOLDER}} etc.) e.g., \"api-{{FEATURE_FOLDER}}.mindyournow.com\" */\n pattern: string;\n /** HTTP Host header for Traefik routing e.g., \"api-{{FEATURE_FOLDER}}.myn.localhost\" */\n http_host_header?: string;\n /** Skip TLS verification for local dev (default: true) */\n no_tls_verify?: boolean;\n}\n\nexport interface TunnelConfig {\n /** Tunnel provider (currently only Cloudflare) */\n provider: 'cloudflare';\n /** Cloudflare tunnel ID */\n tunnel_id: string;\n /** Cloudflare account ID */\n account_id: string;\n /** Cloudflare zone ID */\n zone_id: string;\n /** Path to credentials file (cert.pem) containing API token */\n credentials_file: string;\n /** Service target for ingress rules (e.g., \"https://localhost\") */\n service_target: string;\n /** Hostnames to create ingress rules + DNS records for */\n hostnames: TunnelHostname[];\n}\n\nexport interface HumeConfig {\n /** Env var name containing the Hume API key (default: HUME_API_KEY) */\n api_key_env?: string;\n /** Config ID of the production/template config to clone from */\n template_config_id: string;\n /** Config name pattern for workspaces (supports placeholders) */\n name_pattern: string;\n /** BYOLLM callback URL pattern (supports placeholders) */\n byollm_url_pattern: string;\n}\n\nexport interface WorkspaceConfig {\n /** Workspace type: 'polyrepo' (multiple git repos) or 'monorepo' (single repo, default) */\n type?: 'polyrepo' | 'monorepo';\n /** Where to create workspaces (relative to project path) */\n workspaces_dir?: string;\n /** Default branch for all repos (default: 'main'). Can be overridden per-repo. */\n default_branch?: string;\n /** Git repositories to include (for polyrepo) */\n repos?: RepoConfig[];\n /** DNS configuration */\n dns?: DnsConfig;\n /** Port assignments for services */\n ports?: Record<string, PortConfig>;\n /** Docker configuration */\n docker?: DockerConfig;\n /** Database seeding configuration */\n database?: DatabaseConfig;\n /** Agent configuration templates */\n agent?: AgentTemplateConfig;\n /** Environment variables */\n env?: EnvConfig;\n /** Service definitions for startup commands */\n services?: ServiceConfig[];\n /** Cloudflare tunnel configuration for external access */\n tunnel?: TunnelConfig;\n /** Hume EVI config management for workspace lifecycle */\n hume?: HumeConfig;\n /** PRD directory path (relative to project path, default: 'docs/prds') */\n prdDir?: string;\n}\n\nexport interface TestsConfig {\n [name: string]: TestConfig;\n}\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;\n gitlab_repo?: string;\n\n /** Workspace configuration */\n workspace?: WorkspaceConfig;\n\n /** Test configuration */\n tests?: TestsConfig;\n\n /** Issue routing rules */\n issue_routing?: Array<{\n labels?: string[];\n path: string;\n default?: boolean;\n }>;\n\n /** Legacy: custom workspace command (deprecated, use workspace config) */\n workspace_command?: string;\n workspace_remove_command?: string;\n\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 */\n workspace_packages?: Array<{ path: string; build_command: string }>;\n}\n\nexport interface ProjectsConfig {\n projects: Record<string, ProjectConfig>;\n}\n\n/**\n * Template placeholders that can be used in configuration\n */\nexport interface TemplatePlaceholders {\n FEATURE_NAME: string; // e.g., 'min-123'\n FEATURE_FOLDER: string; // e.g., 'feature-min-123'\n BRANCH_NAME: string; // e.g., 'feature/min-123'\n COMPOSE_PROJECT: string; // e.g., 'myn-feature-min-123'\n DOMAIN: string; // e.g., 'myn.test'\n PROJECT_NAME: string; // e.g., 'myn'\n PROJECT_PATH: string; // e.g., '/home/user/Projects/myn'\n PROJECTS_DIR: string; // e.g., '/home/user/Projects' (parent of PROJECT_PATH)\n WORKSPACE_PATH: string; // e.g., '/home/user/Projects/myn/workspaces/feature-min-123'\n HOME?: string; // e.g., '/home/user' (for docker-compose path sanitization)\n}\n\n/**\n * Replace template placeholders in a string\n */\nexport function replacePlaceholders(template: string, placeholders: TemplatePlaceholders): string {\n let result = template;\n for (const [key, value] of Object.entries(placeholders)) {\n result = result.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, 'g'), value);\n }\n return result;\n}\n\n/**\n * Get default workspace config for a monorepo project\n */\nexport function getDefaultWorkspaceConfig(): WorkspaceConfig {\n return {\n type: 'monorepo',\n workspaces_dir: 'workspaces',\n };\n}\n\n/**\n * Service templates for common project types\n * These provide sensible defaults that can be overridden\n */\nexport const SERVICE_TEMPLATES: Record<string, Partial<ServiceConfig>> = {\n // Frontend frameworks\n 'react': {\n start_command: 'npm start',\n docker_command: 'npm start',\n port: 3000,\n },\n 'react-vite': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 5173,\n },\n 'react-pnpm': {\n start_command: 'pnpm start',\n docker_command: 'pnpm start',\n port: 3000,\n },\n 'nextjs': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 3000,\n },\n 'vue': {\n start_command: 'npm run dev',\n docker_command: 'npm run dev',\n port: 5173,\n },\n 'angular': {\n start_command: 'ng serve',\n docker_command: 'ng serve',\n port: 4200,\n },\n\n // Backend frameworks\n 'spring-boot-maven': {\n start_command: './mvnw spring-boot:run',\n docker_command: './mvnw spring-boot:run',\n port: 8080,\n },\n 'spring-boot-gradle': {\n start_command: './gradlew bootRun',\n docker_command: './gradlew bootRun',\n port: 8080,\n },\n 'express': {\n start_command: 'npm start',\n docker_command: 'npm start',\n port: 3000,\n },\n 'fastapi': {\n start_command: 'uvicorn main:app --reload',\n docker_command: 'uvicorn main:app --host 0.0.0.0 --reload',\n port: 8000,\n },\n 'django': {\n start_command: 'python manage.py runserver',\n docker_command: 'python manage.py runserver 0.0.0.0:8000',\n port: 8000,\n },\n 'rails': {\n start_command: 'rails server',\n docker_command: 'rails server -b 0.0.0.0',\n port: 3000,\n },\n 'go': {\n start_command: 'go run .',\n docker_command: 'go run .',\n port: 8080,\n },\n 'rust-cargo': {\n start_command: 'cargo run',\n docker_command: 'cargo run',\n port: 8080,\n },\n};\n\n/**\n * Get service config from template with overrides\n */\nexport function getServiceFromTemplate(\n templateName: string,\n overrides: Partial<ServiceConfig>\n): ServiceConfig {\n const template = SERVICE_TEMPLATES[templateName] || {};\n return {\n name: overrides.name || templateName,\n path: overrides.path || '.',\n start_command: overrides.start_command || template.start_command || 'npm start',\n docker_command: overrides.docker_command || template.docker_command,\n health_url: overrides.health_url,\n port: overrides.port || template.port,\n };\n}\n"],"mappings":";;;;;AA+QA,SAAgB,oBAAoB,UAAkB,cAA4C;CAChG,IAAI,SAAS;AACb,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,aAAa,CACrD,UAAS,OAAO,QAAQ,IAAI,OAAO,SAAS,IAAI,SAAS,IAAI,EAAE,MAAM;AAEvE,QAAO;;;;;AAMT,SAAgB,4BAA6C;AAC3D,QAAO;EACL,MAAM;EACN,gBAAgB;EACjB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-manager-D_y9ZmW_.js","names":["execAsync"],"sources":["../../src/lib/platform.ts","../../src/lib/dns.ts","../../src/lib/manifest.ts","../../src/lib/skills-merge.ts","../../src/lib/workspace-manager.ts"],"sourcesContent":["/**\n * Platform Detection\n *\n * Shared platform detection utility. Distinguishes between\n * native Linux, macOS, Windows, and WSL2.\n */\n\nimport { readFileSync } from 'fs';\nimport { platform } from 'os';\n\nexport type Platform = 'linux' | 'darwin' | 'win32' | 'wsl';\n\nexport function detectPlatform(): Platform {\n const os = platform();\n if (os === 'linux') {\n // Check for WSL\n try {\n const release = readFileSync('/proc/version', 'utf8').toLowerCase();\n if (release.includes('microsoft') || release.includes('wsl')) {\n return 'wsl';\n }\n } catch {}\n return 'linux';\n }\n return os as 'darwin' | 'win32';\n}\n","/**\n * DNS Management\n *\n * Centralized DNS entry management for Panopticon.\n * Supports three sync methods:\n * - wsl2hosts: WSL2 → Windows hosts file sync via PowerShell scheduled task\n * - hosts_file: Direct /etc/hosts manipulation with managed block markers\n * - dnsmasq: System-wide dnsmasq configuration (Linux/macOS)\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { exec, execSync } from 'child_process';\nimport { promisify } from 'util';\nimport { detectPlatform } from './platform.js';\n\nconst execAsync = promisify(exec);\n\nexport type DnsSyncMethod = 'wsl2hosts' | 'hosts_file' | 'dnsmasq';\n\n// ---- Detection ----\n\n/**\n * Detect the best DNS sync method for the current platform.\n */\nexport function detectDnsSyncMethod(): DnsSyncMethod {\n const plat = detectPlatform();\n switch (plat) {\n case 'wsl':\n return 'wsl2hosts';\n case 'darwin':\n return isDnsmasqInstalled() ? 'dnsmasq' : 'hosts_file';\n case 'linux':\n return isDnsmasqInstalled() ? 'dnsmasq' : 'hosts_file';\n default:\n return 'hosts_file';\n }\n}\n\n/**\n * Check if dnsmasq is installed.\n * Note: Uses execSync intentionally — this only runs in CLI context (pan install/up),\n * never from the dashboard server.\n */\nfunction isDnsmasqInstalled(): boolean {\n try {\n execSync('which dnsmasq', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n}\n\n// ---- wsl2hosts method ----\n\nexport function addWsl2HostEntry(hostname: string): boolean {\n const wsl2hostsPath = join(homedir(), '.wsl2hosts');\n\n try {\n let content = '';\n if (existsSync(wsl2hostsPath)) {\n content = readFileSync(wsl2hostsPath, 'utf-8');\n }\n\n if (!content.includes(hostname)) {\n writeFileSync(wsl2hostsPath, content + (content.endsWith('\\n') ? '' : '\\n') + hostname + '\\n');\n }\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removeWsl2HostEntry(hostname: string): boolean {\n const wsl2hostsPath = join(homedir(), '.wsl2hosts');\n\n try {\n if (!existsSync(wsl2hostsPath)) return true;\n\n const content = readFileSync(wsl2hostsPath, 'utf-8');\n const lines = content.split('\\n').filter(line => line.trim() !== hostname);\n writeFileSync(wsl2hostsPath, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function syncDnsToWindows(): Promise<boolean> {\n try {\n await execAsync('powershell.exe -Command \"Start-ScheduledTask -TaskName \\'PanopticonWsl2HostsSync\\'\"');\n return true;\n } catch {\n // Fall back to legacy task name\n try {\n await execAsync('powershell.exe -Command \"Start-ScheduledTask -TaskName \\'SyncMynHosts\\'\"');\n return true;\n } catch {\n return false;\n }\n }\n}\n\n// ---- hosts_file method ----\n\nconst HOSTS_FILE = '/etc/hosts';\nconst MARKER_START = '# BEGIN panopticon managed entries';\nconst MARKER_END = '# END panopticon managed entries';\n\nexport function addHostsFileEntry(hostname: string, ip: string = '127.0.0.1'): boolean {\n try {\n let content = existsSync(HOSTS_FILE) ? readFileSync(HOSTS_FILE, 'utf-8') : '';\n const entry = `${ip}\\t${hostname}`;\n\n // Already present anywhere in file\n if (content.includes(`\\t${hostname}`) || content.includes(` ${hostname}`)) return true;\n\n const startIdx = content.indexOf(MARKER_START);\n const endIdx = content.indexOf(MARKER_END);\n\n if (startIdx !== -1 && endIdx !== -1) {\n // Managed block exists — insert entry before MARKER_END\n const before = content.substring(0, endIdx);\n const after = content.substring(endIdx);\n content = before + entry + '\\n' + after;\n } else {\n // Create managed block at end\n content = content.trimEnd() + '\\n\\n' + MARKER_START + '\\n' + entry + '\\n' + MARKER_END + '\\n';\n }\n\n writeFileSync(HOSTS_FILE, content);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removeHostsFileEntry(hostname: string): boolean {\n try {\n if (!existsSync(HOSTS_FILE)) return true;\n\n const content = readFileSync(HOSTS_FILE, 'utf-8');\n const lines = content.split('\\n').filter(line => {\n const parts = line.trim().split(/\\s+/);\n return parts[1] !== hostname;\n });\n writeFileSync(HOSTS_FILE, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\n// ---- dnsmasq method ----\n\nfunction getDnsmasqConfigDir(): string {\n const plat = detectPlatform();\n if (plat === 'darwin') {\n // Homebrew Intel location; Apple Silicon uses /opt/homebrew/etc/dnsmasq.d\n const brewPrefix = existsSync('/opt/homebrew') ? '/opt/homebrew' : '/usr/local';\n return `${brewPrefix}/etc/dnsmasq.d`;\n }\n return '/etc/dnsmasq.d';\n}\n\nconst PANOPTICON_DNSMASQ_CONF = 'panopticon.conf';\n\nexport function addDnsmasqEntry(hostname: string, ip: string = '127.0.0.1'): boolean {\n try {\n const configDir = getDnsmasqConfigDir();\n mkdirSync(configDir, { recursive: true });\n const confPath = join(configDir, PANOPTICON_DNSMASQ_CONF);\n\n let content = '';\n if (existsSync(confPath)) {\n content = readFileSync(confPath, 'utf-8');\n }\n\n const entry = `address=/${hostname}/${ip}`;\n if (content.includes(entry)) return true;\n\n content = content.trimEnd() + (content.length > 0 ? '\\n' : '') + entry + '\\n';\n writeFileSync(confPath, content);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function removeDnsmasqEntry(hostname: string): boolean {\n try {\n const configDir = getDnsmasqConfigDir();\n const confPath = join(configDir, PANOPTICON_DNSMASQ_CONF);\n if (!existsSync(confPath)) return true;\n\n const content = readFileSync(confPath, 'utf-8');\n const lines = content.split('\\n').filter(line => !line.includes(`/${hostname}/`));\n writeFileSync(confPath, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function restartDnsmasq(): Promise<boolean> {\n const plat = detectPlatform();\n try {\n if (plat === 'darwin') {\n await execAsync('brew services restart dnsmasq');\n } else {\n await execAsync('sudo systemctl restart dnsmasq');\n }\n return true;\n } catch {\n return false;\n }\n}\n\n// ---- Unified interface ----\n\n/**\n * Add a DNS entry using the specified sync method.\n */\nexport function addDnsEntry(method: DnsSyncMethod, hostname: string): boolean {\n switch (method) {\n case 'wsl2hosts':\n return addWsl2HostEntry(hostname);\n case 'hosts_file':\n return addHostsFileEntry(hostname);\n case 'dnsmasq':\n return addDnsmasqEntry(hostname);\n }\n}\n\n/**\n * Remove a DNS entry using the specified sync method.\n */\nexport function removeDnsEntry(method: DnsSyncMethod, hostname: string): boolean {\n switch (method) {\n case 'wsl2hosts':\n return removeWsl2HostEntry(hostname);\n case 'hosts_file':\n return removeHostsFileEntry(hostname);\n case 'dnsmasq':\n return removeDnsmasqEntry(hostname);\n }\n}\n\n/**\n * Ensure the base Panopticon domain is resolvable.\n * Called during `pan install` and `pan up`.\n */\nexport function ensureBaseDomain(method: DnsSyncMethod, domain: string = 'pan.localhost'): boolean {\n return addDnsEntry(method, domain);\n}\n","import { createHash } from 'crypto';\nimport { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs';\nimport { join, relative } from 'path';\n\n/**\n * Manifest entry for a single distributed file.\n */\nexport interface ManifestEntry {\n hash: string; // sha256:<hex>\n source: string; // \"panopticon\" | \"project-template\" | custom\n installed_at: string; // ISO 8601 timestamp\n}\n\n/**\n * The manifest schema: tracks what Panopticon placed at a target location.\n */\nexport interface Manifest {\n version: 1;\n managed_by: 'panopticon';\n installed: Record<string, ManifestEntry>;\n}\n\n/**\n * Result of comparing a file against the manifest.\n */\nexport type FileStatus =\n | { action: 'new' } // File doesn't exist at target — safe to copy\n | { action: 'update'; currentHash: string } // File exists, hash matches manifest — we placed it, user didn't modify\n | { action: 'modified'; currentHash: string; manifestHash: string } // File exists, hash differs from manifest — user modified\n | { action: 'user-owned' } // File exists but NOT in manifest — user placed it, never touch\n ;\n\n/**\n * Compute SHA-256 hash of a file, prefixed with \"sha256:\".\n */\nexport function hashFile(filePath: string): string {\n const content = readFileSync(filePath);\n const hex = createHash('sha256').update(content).digest('hex');\n return `sha256:${hex}`;\n}\n\n/**\n * Create an empty manifest.\n */\nexport function createEmptyManifest(): Manifest {\n return {\n version: 1,\n managed_by: 'panopticon',\n installed: {},\n };\n}\n\n/**\n * Read a manifest from disk. Returns empty manifest if file doesn't exist or is invalid.\n */\nexport function readManifest(manifestPath: string): Manifest {\n if (!existsSync(manifestPath)) {\n return createEmptyManifest();\n }\n\n try {\n const raw = JSON.parse(readFileSync(manifestPath, 'utf-8'));\n if (raw.version === 1 && raw.managed_by === 'panopticon' && typeof raw.installed === 'object') {\n return raw as Manifest;\n }\n return createEmptyManifest();\n } catch {\n return createEmptyManifest();\n }\n}\n\n/**\n * Write a manifest to disk (creates parent directories if needed).\n */\nexport function writeManifest(manifestPath: string, manifest: Manifest): void {\n mkdirSync(join(manifestPath, '..'), { recursive: true });\n writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\\n', 'utf-8');\n}\n\n/**\n * Add or update an entry in a manifest.\n */\nexport function setManifestEntry(\n manifest: Manifest,\n relativePath: string,\n hash: string,\n source: string,\n): void {\n manifest.installed[relativePath] = {\n hash,\n source,\n installed_at: new Date().toISOString(),\n };\n}\n\n/**\n * Remove an entry from a manifest.\n */\nexport function removeManifestEntry(manifest: Manifest, relativePath: string): void {\n delete manifest.installed[relativePath];\n}\n\n/**\n * Compare a file on disk against the manifest to determine what action to take.\n *\n * @param targetFile - Absolute path to the file at the target location\n * @param relativePath - Relative path used as key in the manifest (e.g., \"skills/beads/SKILL.md\")\n * @param manifest - The manifest to compare against\n */\nexport function compareFileToManifest(\n targetFile: string,\n relativePath: string,\n manifest: Manifest,\n): FileStatus {\n if (!existsSync(targetFile)) {\n return { action: 'new' };\n }\n\n const entry = manifest.installed[relativePath];\n if (!entry) {\n return { action: 'user-owned' };\n }\n\n const currentHash = hashFile(targetFile);\n if (currentHash === entry.hash) {\n return { action: 'update', currentHash };\n }\n\n return { action: 'modified', currentHash, manifestHash: entry.hash };\n}\n\n/**\n * Walk a source directory and collect all files with their relative paths.\n * Used to build the list of files to distribute.\n *\n * @param sourceDir - Root directory to walk\n * @param prefix - Prefix for relative paths (e.g., \"skills/\" or \"agents/\")\n * @returns Array of { absolutePath, relativePath } for each file found\n */\nexport function collectSourceFiles(\n sourceDir: string,\n prefix: string,\n): Array<{ absolutePath: string; relativePath: string }> {\n const results: Array<{ absolutePath: string; relativePath: string }> = [];\n\n if (!existsSync(sourceDir)) {\n return results;\n }\n\n function walk(dir: string): void {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n const rel = relative(sourceDir, fullPath);\n results.push({\n absolutePath: fullPath,\n relativePath: `${prefix}${rel}`,\n });\n }\n }\n }\n\n walk(sourceDir);\n return results;\n}\n\n/**\n * Build a manifest from a directory by hashing all files.\n * Useful for generating the initial cache manifest.\n *\n * @param baseDir - The directory to scan (e.g., ~/.panopticon/)\n * @param categories - Which subdirectories to include (e.g., [\"skills\", \"agents\", \"rules\"])\n * @param source - The source label for all entries (e.g., \"panopticon\")\n */\nexport function buildManifestFromDirectory(\n baseDir: string,\n categories: string[],\n source: string,\n): Manifest {\n const manifest = createEmptyManifest();\n\n for (const category of categories) {\n const categoryDir = join(baseDir, category);\n const files = collectSourceFiles(categoryDir, `${category}/`);\n for (const file of files) {\n const hash = hashFile(file.absolutePath);\n setManifestEntry(manifest, file.relativePath, hash, source);\n }\n }\n\n return manifest;\n}\n","import {\n existsSync,\n readdirSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n copyFileSync,\n statSync,\n} from 'fs';\nimport { join, relative, dirname } from 'path';\nimport { SKILLS_DIR, CACHE_AGENTS_DIR, CACHE_RULES_DIR } from './paths.js';\nimport {\n readManifest,\n writeManifest,\n collectSourceFiles,\n hashFile,\n setManifestEntry,\n compareFileToManifest,\n type Manifest,\n} from './manifest.js';\n\nexport interface MergeResult {\n added: string[];\n updated: string[];\n skipped: string[];\n overlayed: string[];\n}\n\n/**\n * Copy all files from a source directory into a target directory,\n * preserving subdirectory structure. Returns the list of relative paths copied.\n */\nfunction copyTree(sourceDir: string, targetDir: string): string[] {\n const copied: string[] = [];\n if (!existsSync(sourceDir)) return copied;\n\n function walk(dir: string): void {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n const rel = relative(sourceDir, fullPath);\n const targetPath = join(targetDir, rel);\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(fullPath, targetPath);\n copied.push(rel);\n }\n }\n }\n\n walk(sourceDir);\n return copied;\n}\n\n/**\n * Merge Panopticon skills, agents, and rules into a workspace using file copies.\n *\n * Flow:\n * 1. Copy from cache (skills, agent-definitions, rules) → workspace/.claude/\n * 2. Write manifest tracking what was placed\n *\n * Project template overlay is handled separately by workspace-manager.ts\n * (processTemplates + createSymlinks → now also copy-based).\n */\nexport function mergeSkillsIntoWorkspace(workspacePath: string): MergeResult {\n const claudeDir = join(workspacePath, '.claude');\n const manifestPath = join(claudeDir, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n\n const result: MergeResult = {\n added: [],\n updated: [],\n skipped: [],\n overlayed: [],\n };\n\n // Ensure base directories exist\n mkdirSync(join(claudeDir, 'skills'), { recursive: true });\n mkdirSync(join(claudeDir, 'agents'), { recursive: true });\n\n // Sources to copy: category → source cache directory\n const sources: Array<{ category: string; sourceDir: string; targetSubdir: string }> = [\n { category: 'skills', sourceDir: SKILLS_DIR, targetSubdir: 'skills' },\n { category: 'agents', sourceDir: CACHE_AGENTS_DIR, targetSubdir: 'agents' },\n { category: 'rules', sourceDir: CACHE_RULES_DIR, targetSubdir: 'rules' },\n ];\n\n for (const { category, sourceDir, targetSubdir } of sources) {\n if (!existsSync(sourceDir)) continue;\n\n const prefix = targetSubdir ? `${targetSubdir}/` : '';\n const files = collectSourceFiles(sourceDir, '');\n\n for (const file of files) {\n const relativePath = `${prefix}${file.relativePath}`;\n const targetPath = join(claudeDir, relativePath);\n const sourceHash = hashFile(file.absolutePath);\n\n // Check status against manifest\n const status = compareFileToManifest(targetPath, relativePath, manifest);\n\n switch (status.action) {\n case 'new':\n // File doesn't exist at target — copy it\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(file.absolutePath, targetPath);\n setManifestEntry(manifest, relativePath, sourceHash, 'panopticon');\n result.added.push(relativePath);\n break;\n\n case 'update':\n // File exists and matches manifest — safe to overwrite with latest\n copyFileSync(file.absolutePath, targetPath);\n setManifestEntry(manifest, relativePath, sourceHash, 'panopticon');\n result.updated.push(relativePath);\n break;\n\n case 'modified':\n // User modified the file — skip to preserve their changes\n result.skipped.push(`${relativePath} (modified by user)`);\n break;\n\n case 'user-owned':\n // File exists but wasn't placed by us — never touch\n result.skipped.push(`${relativePath} (user-owned)`);\n break;\n }\n }\n }\n\n // Write updated manifest\n writeManifest(manifestPath, manifest);\n\n return result;\n}\n\n/**\n * Apply project template overlay on top of Panopticon base files in a workspace.\n *\n * This copies files from the project's agent template directory into\n * workspace/.claude/, overwriting Panopticon files where the project\n * provides its own version. Updates the manifest with source=\"project-template\".\n *\n * @param workspacePath - Path to the workspace\n * @param templateDir - Absolute path to the project's agent template directory\n * @param templates - Optional list of specific template files to process (source → target mappings)\n */\nexport function applyProjectTemplateOverlay(\n workspacePath: string,\n templateDir: string,\n templates?: Array<{ source: string; target: string }>,\n): string[] {\n const claudeDir = join(workspacePath, '.claude');\n const manifestPath = join(claudeDir, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n const overlayed: string[] = [];\n\n if (!existsSync(templateDir)) return overlayed;\n\n if (templates && templates.length > 0) {\n // Process specific template mappings\n for (const { source, target } of templates) {\n const sourcePath = join(templateDir, source);\n if (!existsSync(sourcePath)) continue;\n\n const targetPath = join(workspacePath, target);\n mkdirSync(dirname(targetPath), { recursive: true });\n\n // Read template content and check if it's a template file\n if (source.endsWith('.template')) {\n // Template files are handled by workspace-manager's processTemplates\n // We just track them in the manifest after they're processed\n continue;\n }\n\n copyFileSync(sourcePath, targetPath);\n\n // Track in manifest if it's under .claude/\n if (target.startsWith('.claude/')) {\n const relativePath = target.slice('.claude/'.length);\n const hash = hashFile(targetPath);\n setManifestEntry(manifest, relativePath, hash, 'project-template');\n overlayed.push(relativePath);\n }\n }\n } else {\n // Copy all .claude/ subdirectories from template dir\n const claudeInTemplate = join(templateDir, '.claude');\n if (existsSync(claudeInTemplate)) {\n const copied = copyTree(claudeInTemplate, claudeDir);\n for (const rel of copied) {\n const targetPath = join(claudeDir, rel);\n const hash = hashFile(targetPath);\n setManifestEntry(manifest, rel, hash, 'project-template');\n overlayed.push(rel);\n }\n }\n }\n\n // Write updated manifest\n writeManifest(manifestPath, manifest);\n\n return overlayed;\n}\n\n// ─── Legacy exports (kept for migration, to be removed in future) ───\n\n/**\n * @deprecated No longer needed — skills are copies, not symlinks. Kept for migration.\n */\nexport function cleanupGitignore(gitignorePath: string): {\n cleaned: boolean;\n duplicatesRemoved: number;\n entriesAfter: number;\n} {\n if (!existsSync(gitignorePath)) {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n const PANOPTICON_HEADER = '# Panopticon-managed symlinks (not committed)';\n let content: string;\n try {\n content = readFileSync(gitignorePath, 'utf-8');\n } catch {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n // If no Panopticon section, nothing to clean\n if (!content.includes(PANOPTICON_HEADER)) {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n\n // Remove the entire Panopticon section (skills are copies now, not symlinks)\n const lines = content.split('\\n');\n const newLines: string[] = [];\n let inPanopticonSection = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === PANOPTICON_HEADER) {\n inPanopticonSection = true;\n continue;\n }\n if (inPanopticonSection) {\n if (trimmed.startsWith('#') && trimmed !== '') {\n inPanopticonSection = false;\n newLines.push(line);\n } else if (trimmed === '') {\n // Skip blank lines in Panopticon section\n continue;\n }\n // Skip entries in Panopticon section\n continue;\n }\n newLines.push(line);\n }\n\n // Write cleaned file\n try {\n writeFileSync(gitignorePath, newLines.join('\\n'), 'utf-8');\n return { cleaned: true, duplicatesRemoved: 0, entriesAfter: 0 };\n } catch {\n return { cleaned: false, duplicatesRemoved: 0, entriesAfter: 0 };\n }\n}\n\n/**\n * @deprecated No longer needed — skills are copies, not symlinks. Kept for migration.\n */\nexport function cleanupWorkspaceGitignore(workspacePath: string): {\n cleaned: boolean;\n duplicatesRemoved: number;\n entriesAfter: number;\n} {\n const gitignorePath = join(workspacePath, '.claude', 'skills', '.gitignore');\n return cleanupGitignore(gitignorePath);\n}\n\n/**\n * Merge project-local skills from .pan/skills/ into a workspace's .claude/skills/.\n *\n * Precedence (highest wins):\n * 1. .claude/skills/<name>/ already in workspace (user-owned or project template) → skip\n * 2. .pan/skills/<name>/ in project repo → copy into workspace .claude/skills/\n * 3. Global cache (handled by mergeSkillsIntoWorkspace) → baseline\n *\n * This should be called AFTER mergeSkillsIntoWorkspace so that project-local skills\n * can override global cache skills (but never overwrite user-owned content).\n */\nexport function mergePanSkillsIntoWorkspace(projectPath: string, workspacePath: string): MergeResult {\n const result: MergeResult = { added: [], updated: [], skipped: [], overlayed: [] };\n const panSkillsDir = join(projectPath, '.pan', 'skills');\n if (!existsSync(panSkillsDir)) return result;\n\n const claudeSkillsDir = join(workspacePath, '.claude', 'skills');\n const manifestPath = join(workspacePath, '.claude', '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n\n const skillDirs = readdirSync(panSkillsDir, { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => e.name);\n\n for (const skillName of skillDirs) {\n const sourceSkillDir = join(panSkillsDir, skillName);\n const targetSkillDir = join(claudeSkillsDir, skillName);\n\n // Rule #1: if target already exists (user-owned or project-template), never overwrite\n if (existsSync(targetSkillDir)) {\n result.skipped.push(`skills/${skillName} (already exists in .claude/skills/)`);\n continue;\n }\n\n // Rule #2: copy from .pan/skills/<name>/ to workspace .claude/skills/<name>/\n const files = collectSourceFiles(sourceSkillDir, '');\n mkdirSync(targetSkillDir, { recursive: true });\n let anyAdded = false;\n for (const file of files) {\n const targetPath = join(targetSkillDir, file.relativePath);\n mkdirSync(dirname(targetPath), { recursive: true });\n copyFileSync(file.absolutePath, targetPath);\n const hash = hashFile(targetPath);\n setManifestEntry(manifest, `skills/${skillName}/${file.relativePath}`, hash, 'pan-skills');\n result.added.push(`skills/${skillName}/${file.relativePath}`);\n anyAdded = true;\n }\n if (anyAdded) {\n result.overlayed.push(skillName);\n }\n }\n\n writeManifest(manifestPath, manifest);\n return result;\n}\n","/**\n * Workspace Manager\n *\n * Handles workspace creation and removal for both monorepo and polyrepo projects.\n */\n\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, copyFileSync, symlinkSync, chmodSync, realpathSync, rmSync, rmdirSync, statSync, renameSync } from 'fs';\nimport { join, dirname, basename, extname, resolve } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n ProjectConfig,\n WorkspaceConfig,\n TemplatePlaceholders,\n replacePlaceholders,\n getDefaultWorkspaceConfig,\n} from './workspace-config.js';\nimport { addDnsEntry, removeDnsEntry, syncDnsToWindows } from './dns.js';\nimport { addTunnelIngress, removeTunnelIngress } from './tunnel.js';\nimport { createHumeConfig, deleteHumeConfig } from './hume.js';\nimport { mergeSkillsIntoWorkspace, mergePanSkillsIntoWorkspace } from './skills-merge.js';\n\nconst execAsync = promisify(exec);\n\nexport interface PanMigrationResult {\n /** Subdirectories migrated from .panopticon/ to .pan/ */\n migrated: string[];\n /** Subdirectories skipped because .pan/<subdir> already exists */\n skipped: string[];\n /** Errors encountered during migration */\n errors: string[];\n}\n\n/**\n * Migrate existing .panopticon/<subdir> directories to .pan/<subdir> within a project.\n *\n * Safety rules:\n * - If old path exists and new path does NOT exist → move old to new.\n * - If both old and new exist → log warning and skip (never overwrite silently).\n * - If neither exists → nothing to do.\n * - Only migrates the specific runtime subdirs (events, convoy, prompts).\n * .pan/skills/ is not migrated here since it may not have existed before.\n */\nexport function migratePanopticonToPan(projectPath: string): PanMigrationResult {\n const result: PanMigrationResult = { migrated: [], skipped: [], errors: [] };\n\n // Map legacy .panopticon/<subdir> paths to new .pan/<subdir> paths,\n // including convoy unification (triage + health → convoy)\n const legacyMappings: Array<{ old: string; new: string }> = [\n { old: '.panopticon/events', new: '.pan/events' },\n { old: '.panopticon/triage', new: '.pan/convoy' },\n { old: '.panopticon/health', new: '.pan/convoy' },\n { old: '.panopticon/convoy-output', new: '.pan/convoy' },\n { old: '.panopticon/prompts', new: '.pan/prompts' },\n ];\n\n for (const { old: oldRelPath, new: newRelPath } of legacyMappings) {\n const oldPath = join(projectPath, oldRelPath);\n const newPath = join(projectPath, newRelPath);\n\n if (!existsSync(oldPath)) continue;\n\n if (existsSync(newPath)) {\n const msg = `Migration skipped: both ${oldRelPath} and ${newRelPath} exist in ${projectPath} — remove one manually`;\n console.warn(`[panopticon] ${msg}`);\n result.skipped.push(oldRelPath);\n continue;\n }\n\n try {\n // Ensure parent directory exists\n const parentDir = dirname(newPath);\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true });\n }\n renameSync(oldPath, newPath);\n result.migrated.push(`${oldRelPath} → ${newRelPath}`);\n } catch (err: any) {\n result.errors.push(`${oldRelPath}: ${err.message}`);\n }\n }\n\n // Clean up empty .panopticon/ dir if nothing remains\n const panopticonDir = join(projectPath, '.panopticon');\n if (existsSync(panopticonDir)) {\n try {\n const remaining = readdirSync(panopticonDir);\n if (remaining.length === 0) {\n rmdirSync(panopticonDir);\n result.migrated.push('.panopticon/ (empty dir removed)');\n }\n } catch {\n // Non-fatal — dir may have been removed already\n }\n }\n\n return result;\n}\n\n/**\n * Ensure .pan/events/, .pan/convoy/, and .pan/prompts/ are excluded from git tracking\n * in the given project root's .gitignore. .pan/skills/ is intentionally NOT excluded\n * since project-specific skills should be committed.\n */\nexport function ensurePanGitignore(projectPath: string): void {\n const gitignorePath = join(projectPath, '.gitignore');\n const requiredEntries = ['.pan/events/', '.pan/convoy/', '.pan/prompts/'];\n\n let content = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf-8') : '';\n const lines = content.split('\\n');\n\n const missing = requiredEntries.filter(entry => !lines.some(l => l.trim() === entry));\n if (missing.length === 0) return;\n\n // Append missing entries with a section header if we're adding for the first time\n if (!content.endsWith('\\n') && content.length > 0) {\n content += '\\n';\n }\n if (!lines.some(l => l.includes('.pan/'))) {\n content += '\\n# Panopticon runtime artifacts (ephemeral, not tracked)\\n';\n }\n content += missing.join('\\n') + '\\n';\n\n writeFileSync(gitignorePath, content, 'utf-8');\n}\n\n/** Progress event emitted during workspace creation. */\nexport interface WorkspaceProgress {\n label: string;\n detail: string;\n status: 'active' | 'complete' | 'error';\n}\n\nexport interface WorkspaceCreateOptions {\n projectConfig: ProjectConfig;\n featureName: string;\n startDocker?: boolean;\n dryRun?: boolean;\n /** Optional callback for streaming progress events during creation. */\n onProgress?: (event: WorkspaceProgress) => void;\n}\n\nexport interface WorkspaceCreateResult {\n success: boolean;\n workspacePath: string;\n errors: string[];\n steps: string[];\n}\n\n/**\n * Create placeholders for template substitution\n */\nfunction createPlaceholders(\n projectConfig: ProjectConfig,\n featureName: string,\n workspacePath: string\n): TemplatePlaceholders {\n const featureFolder = `feature-${featureName}`;\n const domain = projectConfig.workspace?.dns?.domain || 'localhost';\n\n return {\n FEATURE_NAME: featureName,\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${featureName}`,\n COMPOSE_PROJECT: `${basename(projectConfig.path)}-${featureFolder}`,\n DOMAIN: domain,\n PROJECT_NAME: basename(projectConfig.path),\n PROJECT_PATH: projectConfig.path,\n PROJECTS_DIR: dirname(projectConfig.path),\n WORKSPACE_PATH: workspacePath,\n HOME: homedir(),\n };\n}\n\n/**\n * Sanitize docker-compose files to use platform-agnostic paths\n * Replaces hardcoded /home/username paths with ${HOME}\n */\nfunction sanitizeComposeFile(filePath: string): void {\n if (!existsSync(filePath)) return;\n\n let content = readFileSync(filePath, 'utf-8');\n const originalContent = content;\n\n // Pattern to match hardcoded home paths like /home/username or /Users/username\n // Replace with ${HOME} which docker-compose expands\n const homePatterns = [\n /\\/home\\/[a-zA-Z0-9_-]+\\//g, // Linux: /home/username/\n /\\/Users\\/[a-zA-Z0-9_-]+\\//g, // macOS: /Users/username/\n ];\n\n for (const pattern of homePatterns) {\n content = content.replace(pattern, '${HOME}/');\n }\n\n if (content !== originalContent) {\n writeFileSync(filePath, content, 'utf-8');\n }\n}\n\n/**\n * Validate feature name (alphanumeric and hyphens only)\n */\nfunction validateFeatureName(name: string): boolean {\n return /^[a-zA-Z0-9-]+$/.test(name);\n}\n\n/**\n * Create a git worktree\n * @param repoPath Path to the source git repository\n * @param targetPath Where to create the worktree\n * @param branchName Name of the feature branch to create/checkout\n * @param defaultBranch Base branch to create new branches from (default: 'main')\n */\nasync function createWorktree(\n repoPath: string,\n targetPath: string,\n branchName: string,\n defaultBranch: string = 'main'\n): Promise<{ success: boolean; message: string }> {\n try {\n // Fetch latest from origin\n await execAsync('git fetch origin', { cwd: repoPath });\n\n // Prune stale worktree entries (e.g., from deleted workspaces)\n await execAsync('git worktree prune', { cwd: repoPath });\n\n // Check if branch exists locally or remotely (exact match, not substring)\n const { stdout: localBranches } = await execAsync('git branch --list', { cwd: repoPath });\n const { stdout: remoteBranches } = await execAsync('git branch -r --list', { cwd: repoPath });\n\n const localList = localBranches.split('\\n').map(b => b.replace(/^[*+\\s]+/, '').trim()).filter(Boolean);\n const remoteList = remoteBranches.split('\\n').map(b => b.trim()).filter(Boolean);\n const branchExists =\n localList.includes(branchName) ||\n remoteList.includes(`origin/${branchName}`);\n\n if (branchExists) {\n await execAsync(`git worktree add \"${targetPath}\" \"${branchName}\"`, { cwd: repoPath });\n } else {\n // Create new branch from the configured default branch\n await execAsync(`git worktree add \"${targetPath}\" -b \"${branchName}\" \"${defaultBranch}\"`, { cwd: repoPath });\n }\n\n // Clear unstaged deletions from the new worktree (e.g. .planning/ files that exist on the\n // feature branch but not on main appear as deleted in a fresh worktree). Without this,\n // `git rebase origin/main` fails immediately with \"unstaged changes\" (PAN-495).\n await execAsync('git restore .', { cwd: targetPath }).catch(() => {});\n\n // Configure beads role so agents don't get \"beads.role not configured\" warnings\n await execAsync('git config beads.role contributor', { cwd: targetPath }).catch(() => {});\n\n return { success: true, message: `Created worktree at ${targetPath}` };\n } catch (error) {\n return { success: false, message: `Failed to create worktree: ${error}` };\n }\n}\n\n/**\n * Remove a git worktree\n */\nasync function removeWorktree(\n repoPath: string,\n targetPath: string,\n branchName: string\n): Promise<{ success: boolean; message: string }> {\n try {\n // Remove worktree\n await execAsync(`git worktree remove \"${targetPath}\" --force`, { cwd: repoPath }).catch(() => {});\n\n // Optionally delete the branch\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: repoPath }).catch(() => {});\n\n return { success: true, message: `Removed worktree at ${targetPath}` };\n } catch (error) {\n return { success: false, message: `Failed to remove worktree: ${error}` };\n }\n}\n\n// DNS functions (addWsl2HostEntry, removeWsl2HostEntry, syncDnsToWindows)\n// are now in src/lib/dns.ts and imported above\n\n/**\n * Assign a port from a range\n */\nfunction assignPort(\n portFile: string,\n featureFolder: string,\n range: [number, number]\n): number {\n // Ensure port file exists\n if (!existsSync(portFile)) {\n mkdirSync(dirname(portFile), { recursive: true });\n writeFileSync(portFile, '');\n }\n\n const content = readFileSync(portFile, 'utf-8');\n const lines = content.split('\\n').filter(Boolean);\n\n // Check if already assigned\n for (const line of lines) {\n const [folder, port] = line.split(':');\n if (folder === featureFolder) {\n return parseInt(port, 10);\n }\n }\n\n // Find next available port\n const usedPorts = new Set(lines.map(l => parseInt(l.split(':')[1], 10)));\n for (let port = range[0]; port <= range[1]; port++) {\n if (!usedPorts.has(port)) {\n writeFileSync(portFile, content + (content.endsWith('\\n') ? '' : '\\n') + `${featureFolder}:${port}\\n`);\n return port;\n }\n }\n\n throw new Error(`No available ports in range ${range[0]}-${range[1]}`);\n}\n\n/**\n * Release a port assignment\n */\nfunction releasePort(portFile: string, featureFolder: string): boolean {\n try {\n if (!existsSync(portFile)) return true;\n\n let content = readFileSync(portFile, 'utf-8');\n const lines = content.split('\\n').filter(line => !line.startsWith(`${featureFolder}:`));\n writeFileSync(portFile, lines.join('\\n'));\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Process template files with placeholder replacement\n */\nfunction processTemplates(\n templateDir: string,\n targetDir: string,\n placeholders: TemplatePlaceholders,\n templates?: Array<{ source: string; target: string }>\n): string[] {\n const steps: string[] = [];\n\n if (!existsSync(templateDir)) {\n return steps;\n }\n\n // If specific templates are defined, process those\n if (templates && templates.length > 0) {\n for (const { source, target } of templates) {\n const sourcePath = join(templateDir, source);\n const targetPath = join(targetDir, target);\n\n if (existsSync(sourcePath)) {\n const content = readFileSync(sourcePath, 'utf-8');\n const processed = replacePlaceholders(content, placeholders);\n mkdirSync(dirname(targetPath), { recursive: true });\n writeFileSync(targetPath, processed);\n steps.push(`Processed template: ${source} -> ${target}`);\n }\n }\n } else {\n // Process all .template files\n const files = readdirSync(templateDir);\n for (const file of files) {\n if (file.endsWith('.template')) {\n const sourcePath = join(templateDir, file);\n const targetPath = join(targetDir, file.replace('.template', ''));\n\n const content = readFileSync(sourcePath, 'utf-8');\n const processed = replacePlaceholders(content, placeholders);\n writeFileSync(targetPath, processed);\n // Shell scripts need execute permission\n const targetName = file.replace('.template', '');\n if (targetName === 'dev' || targetName.endsWith('.sh')) {\n chmodSync(targetPath, 0o755);\n }\n steps.push(`Processed template: ${file}`);\n }\n }\n }\n\n return steps;\n}\n\n/**\n * @deprecated Use copyProjectTemplateDirs instead. Kept for non-.claude paths.\n */\nfunction createSymlinks(\n sourceDir: string,\n targetDir: string,\n symlinks: string[]\n): string[] {\n const steps: string[] = [];\n\n for (const symlink of symlinks) {\n const sourcePath = join(sourceDir, symlink);\n const targetPath = join(targetDir, symlink);\n\n if (existsSync(sourcePath)) {\n mkdirSync(dirname(targetPath), { recursive: true });\n try {\n symlinkSync(sourcePath, targetPath);\n steps.push(`Created symlink: ${symlink}`);\n } catch {\n // Symlink might already exist\n }\n }\n }\n\n return steps;\n}\n\n/**\n * Copy project template directories into workspace (replaces symlinks).\n * Recursively copies all files from each source directory.\n */\nconst TEXT_EXTENSIONS = new Set([\n '.md', '.sh', '.yml', '.yaml', '.json', '.ts', '.js', '.env', '.txt', '.toml', '.template',\n]);\n\nfunction copyProjectTemplateDirs(\n sourceDir: string,\n targetDir: string,\n dirs: string[],\n placeholders?: TemplatePlaceholders\n): string[] {\n const steps: string[] = [];\n\n for (const dir of dirs) {\n const sourcePath = join(sourceDir, dir);\n const targetPath = join(targetDir, dir);\n\n if (!existsSync(sourcePath)) continue;\n\n // Recursively copy all files, applying placeholder substitution to text files\n function copyDir(src: string, dest: string): number {\n let count = 0;\n mkdirSync(dest, { recursive: true });\n const entries = readdirSync(src, { withFileTypes: true });\n for (const entry of entries) {\n const srcEntry = join(src, entry.name);\n const destEntry = join(dest, entry.name);\n if (entry.isDirectory()) {\n count += copyDir(srcEntry, destEntry);\n } else if (entry.isFile()) {\n const ext = extname(entry.name).toLowerCase();\n if (placeholders && TEXT_EXTENSIONS.has(ext)) {\n const content = readFileSync(srcEntry, 'utf-8');\n writeFileSync(destEntry, replacePlaceholders(content, placeholders));\n } else {\n copyFileSync(srcEntry, destEntry);\n }\n count++;\n }\n }\n return count;\n }\n\n const count = copyDir(sourcePath, targetPath);\n steps.push(`Copied ${count} files from project template: ${dir}`);\n }\n\n return steps;\n}\n\n/**\n * Create a workspace\n */\nexport async function createWorkspace(options: WorkspaceCreateOptions): Promise<WorkspaceCreateResult> {\n const { projectConfig, featureName, startDocker, dryRun, onProgress } = options;\n const progress = (label: string, detail: string, status: 'active' | 'complete' | 'error' = 'active') => {\n onProgress?.({ label, detail, status });\n };\n const result: WorkspaceCreateResult = {\n success: true,\n workspacePath: '',\n errors: [],\n steps: [],\n };\n\n // Validate feature name\n if (!validateFeatureName(featureName)) {\n result.success = false;\n result.errors.push('Invalid feature name. Use alphanumeric and hyphens only.');\n return result;\n }\n\n // Reject 'main' as feature name\n if (featureName === 'main') {\n result.success = false;\n result.errors.push('Cannot create workspace for \"main\". Use base repos directly.');\n return result;\n }\n\n const workspaceConfig = projectConfig.workspace || getDefaultWorkspaceConfig();\n const workspacesDir = join(projectConfig.path, workspaceConfig.workspaces_dir || 'workspaces');\n const featureFolder = `feature-${featureName}`;\n const workspacePath = join(workspacesDir, featureFolder);\n result.workspacePath = workspacePath;\n\n // Check if workspace already exists\n if (existsSync(workspacePath)) {\n result.success = false;\n result.errors.push(`Workspace already exists at ${workspacePath}`);\n return result;\n }\n\n if (dryRun) {\n result.steps.push('[DRY RUN] Would create workspace at: ' + workspacePath);\n return result;\n }\n\n // Create placeholders\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n\n // Create workspace directory\n progress('Creating git worktree', `feature/${featureName}`);\n mkdirSync(workspacePath, { recursive: true });\n result.steps.push('Created workspace directory');\n\n // Handle polyrepo vs monorepo\n if (workspaceConfig.type === 'polyrepo' && workspaceConfig.repos) {\n // Create worktrees for each repo\n for (const repo of workspaceConfig.repos) {\n // Resolve symlinks to get the actual git repository path\n // (e.g., myn/frontend -> ../frontend needs to resolve to actual path)\n const rawRepoPath = join(projectConfig.path, repo.path);\n const repoPath = existsSync(rawRepoPath) ? realpathSync(rawRepoPath) : rawRepoPath;\n const targetPath = join(workspacePath, repo.name);\n const branchPrefix = repo.branch_prefix || 'feature/';\n const branchName = `${branchPrefix}${featureName}`;\n // Per-repo default_branch overrides workspace-level, falls back to 'main'\n const defaultBranch = repo.default_branch || workspaceConfig.default_branch || 'main';\n\n const worktreeResult = await createWorktree(repoPath, targetPath, branchName, defaultBranch);\n if (worktreeResult.success) {\n result.steps.push(`Created worktree for ${repo.name}: ${branchName} (from ${defaultBranch})`);\n } else {\n result.errors.push(`${repo.name}: ${worktreeResult.message}`);\n result.success = false; // Fail the entire workspace creation if any worktree fails\n }\n }\n } else {\n // Monorepo: create single worktree\n const branchName = `feature/${featureName}`;\n const defaultBranch = workspaceConfig.default_branch || 'main';\n const worktreeResult = await createWorktree(projectConfig.path, workspacePath, branchName, defaultBranch);\n if (worktreeResult.success) {\n result.steps.push(`Created worktree: ${branchName} (from ${defaultBranch})`);\n } else {\n result.errors.push(worktreeResult.message);\n result.success = false; // Fail the entire workspace creation if worktree fails\n }\n }\n\n progress('Creating git worktree', 'Worktree ready', 'complete');\n\n // Remove stale .planning/ directory inherited from main branch.\n // This contains STATE.md and other planning artifacts from a PREVIOUS issue.\n // If left in place, the new agent reads it and works on the wrong issue.\n // SAFETY: resolve() to absolute path and verify it's under a known workspace prefix\n // to prevent path traversal from ever reaching rmSync.\n const resolvedWorkspace = resolve(workspacePath);\n const resolvedPlanning = resolve(resolvedWorkspace, '.planning');\n const isUnderWorkspacesDir = resolvedWorkspace.match(/\\/workspaces\\/feature-[a-z0-9-]+$/);\n if (\n isUnderWorkspacesDir &&\n resolvedPlanning === join(resolvedWorkspace, '.planning') &&\n existsSync(join(resolvedWorkspace, '.git')) &&\n existsSync(resolvedPlanning)\n ) {\n rmSync(resolvedPlanning, { recursive: true, force: true });\n result.steps.push('Removed stale .planning/ directory from previous issue');\n }\n\n // Ensure .pan/events/, .pan/convoy/, .pan/prompts/ are in the project's .gitignore\n try {\n ensurePanGitignore(projectConfig.path);\n result.steps.push('Verified .pan/ runtime paths are in .gitignore');\n } catch (gitignoreErr: any) {\n // Non-fatal — log but don't block workspace creation\n result.steps.push(`Warning: could not update .gitignore: ${gitignoreErr.message}`);\n }\n\n // Sanitize any docker-compose files in the workspace to use platform-agnostic paths\n // This handles files inherited from worktrees that may have hardcoded home paths\n const devcontainerDir = join(workspacePath, '.devcontainer');\n if (existsSync(devcontainerDir)) {\n const composeFiles = readdirSync(devcontainerDir)\n .filter(f => f.includes('compose') && (f.endsWith('.yml') || f.endsWith('.yaml')));\n for (const composeFile of composeFiles) {\n sanitizeComposeFile(join(devcontainerDir, composeFile));\n }\n if (composeFiles.length > 0) {\n result.steps.push(`Sanitized ${composeFiles.length} compose file(s) for platform compatibility`);\n }\n }\n\n // Install dependencies using the project's package manager\n progress('Installing dependencies', projectConfig.package_manager || 'detecting...');\n const pkgManager = projectConfig.package_manager || (existsSync(join(workspacePath, 'bun.lock')) ? 'bun' : 'npm');\n const installCmd = pkgManager === 'bun' ? 'bun install' : `${pkgManager} install`;\n try {\n await execAsync(installCmd, { cwd: workspacePath, encoding: 'utf-8', timeout: 60000 });\n result.steps.push(`Installed dependencies (${pkgManager})`);\n progress('Installing dependencies', `${pkgManager} — done`, 'complete');\n } catch (installErr: any) {\n result.steps.push(`Dependency install warning: ${installErr.message?.slice(0, 100)}`);\n progress('Installing dependencies', 'Warning (non-fatal)', 'complete');\n }\n\n // Build workspace packages (e.g., @panopticon/contracts) so types resolve correctly\n const workspacePackages = projectConfig.workspace_packages;\n if (workspacePackages && workspacePackages.length > 0) {\n progress('Building workspace packages', workspacePackages.map(p => p.path).join(', '));\n for (const pkg of workspacePackages) {\n try {\n await execAsync(pkg.build_command, { cwd: join(workspacePath, pkg.path), encoding: 'utf-8', timeout: 30000 });\n result.steps.push(`Built workspace package: ${pkg.path}`);\n } catch (buildErr: any) {\n result.steps.push(`Build warning (${pkg.path}): ${buildErr.message?.slice(0, 100)}`);\n }\n }\n progress('Building workspace packages', 'Packages built', 'complete');\n }\n\n // Setup TLDR code analysis for workspace (after worktree creation to ensure directory is ready)\n try {\n // Check if python3 is available\n await execAsync('python3 --version');\n const venvPath = join(workspacePath, '.venv');\n const tldrBin = join(venvPath, 'bin', 'tldr');\n\n // Check if main branch already has a working venv with llm-tldr\n const mainVenvTldr = join(projectConfig.path, '.venv', 'bin', 'tldr');\n const mainVenvExists = existsSync(mainVenvTldr);\n\n if (mainVenvExists) {\n // Copy the entire venv from main — faster than pip install (seconds vs 30s+)\n await execAsync(`cp -a \"${join(projectConfig.path, '.venv')}\" \"${venvPath}\"`);\n result.steps.push('Copied Python venv from main branch');\n } else {\n // Create fresh venv and install llm-tldr\n await execAsync(`python3 -m venv \"${venvPath}\"`, { cwd: workspacePath });\n const pipPath = join(venvPath, 'bin', 'pip');\n await execAsync(`\"${pipPath}\" install llm-tldr`, { cwd: workspacePath, timeout: 120000 });\n result.steps.push('Created Python venv and installed llm-tldr');\n\n // Apply .tsx/.jsx support patch (upstream llm-tldr only checks .ts)\n const patchScript = join(projectConfig.path, 'scripts', 'patches', 'llm-tldr-tsx-support.py');\n if (existsSync(patchScript)) {\n await execAsync(`python3 \"${patchScript}\" \"${venvPath}\"`);\n result.steps.push('Applied llm-tldr .tsx/.jsx patch');\n }\n }\n\n // Verify tldr binary exists after setup\n if (!existsSync(tldrBin)) {\n result.steps.push('TLDR setup incomplete: tldr binary not found after venv creation');\n } else {\n // Copy .tldr index from main branch if it exists\n const mainTldrDir = join(projectConfig.path, '.tldr');\n const workspaceTldrDir = join(workspacePath, '.tldr');\n\n if (existsSync(mainTldrDir)) {\n await execAsync(`cp -r \"${mainTldrDir}\" \"${workspaceTldrDir}\"`);\n result.steps.push('Copied TLDR index from main branch');\n }\n\n // Start TLDR daemon for this workspace\n const { getTldrDaemonService } = await import('./tldr-daemon.js');\n const tldrService = getTldrDaemonService(workspacePath, venvPath);\n await tldrService.start(true);\n result.steps.push('Started TLDR daemon');\n\n // Warm the index in the background — ensures workspaces always have a working index\n // even when the main branch cache was empty (nothing to copy)\n try {\n await tldrService.warm(true); // background=true: non-blocking\n result.steps.push('TLDR index warm initiated (background)');\n } catch {\n // Non-fatal — daemon may not support warm yet\n }\n }\n } catch (error: any) {\n // TLDR setup is optional — don't fail workspace creation, but log clearly\n if (error.message?.includes('python3')) {\n result.steps.push('Skipped TLDR setup (python3 not available)');\n } else {\n console.warn(`⚠ TLDR setup failed: ${error.message}`);\n result.steps.push(`TLDR setup failed: ${error.message}`);\n }\n }\n\n // Configure DNS\n if (workspaceConfig.dns) {\n const dnsMethod = workspaceConfig.dns.sync_method || 'wsl2hosts';\n for (const entryPattern of workspaceConfig.dns.entries) {\n const hostname = replacePlaceholders(entryPattern, placeholders);\n\n if (addDnsEntry(dnsMethod, hostname)) {\n result.steps.push(`Added DNS entry: ${hostname} (${dnsMethod})`);\n }\n }\n\n // Sync to Windows if using wsl2hosts method\n if (dnsMethod === 'wsl2hosts') {\n const synced = await syncDnsToWindows();\n if (synced) {\n result.steps.push('Synced DNS to Windows hosts file');\n }\n }\n }\n\n // Assign ports\n if (workspaceConfig.ports) {\n for (const [portName, portConfig] of Object.entries(workspaceConfig.ports)) {\n const portFile = join(projectConfig.path, `.${portName}-ports`);\n try {\n const port = assignPort(portFile, featureFolder, portConfig.range);\n result.steps.push(`Assigned ${portName} port: ${port}`);\n // Add to placeholders for use in templates\n (placeholders as any)[`${portName.toUpperCase()}_PORT`] = String(port);\n } catch (error) {\n result.errors.push(`Failed to assign ${portName} port: ${error}`);\n }\n }\n }\n\n // Install base Panopticon skills/agents/rules from cache\n progress('Installing skills & templates', 'Panopticon skills, agents, rules');\n const mergeResult = mergeSkillsIntoWorkspace(workspacePath);\n const mergeTotal = mergeResult.added.length + mergeResult.updated.length;\n if (mergeTotal > 0) {\n result.steps.push(`Installed ${mergeTotal} Panopticon files (${mergeResult.added.length} new, ${mergeResult.updated.length} updated)`);\n }\n\n // Overlay project-local skills from .pan/skills/ (higher precedence than global cache)\n const panMergeResult = mergePanSkillsIntoWorkspace(projectConfig.path, workspacePath);\n if (panMergeResult.added.length > 0) {\n result.steps.push(`Installed ${panMergeResult.added.length} project-local skill file(s) from .pan/skills/ (${panMergeResult.overlayed.join(', ')})`);\n }\n\n // Process agent templates (project template overlay — wins over Panopticon base)\n if (workspaceConfig.agent?.template_dir) {\n const templateDir = join(projectConfig.path, workspaceConfig.agent.template_dir);\n\n // Process template files\n const templateSteps = processTemplates(\n templateDir,\n workspacePath,\n placeholders,\n workspaceConfig.agent.templates\n );\n result.steps.push(...templateSteps);\n\n // Copy .claude/ directories from project template (copy_dirs replaces legacy symlinks)\n const dirsToSync = workspaceConfig.agent.copy_dirs || workspaceConfig.agent.symlinks;\n if (dirsToSync) {\n const copySteps = copyProjectTemplateDirs(templateDir, workspacePath, dirsToSync, placeholders);\n result.steps.push(...copySteps);\n }\n }\n\n // Generate .env file\n if (workspaceConfig.env?.template) {\n const envContent = replacePlaceholders(workspaceConfig.env.template, placeholders);\n writeFileSync(join(workspacePath, '.env'), envContent);\n result.steps.push('Created .env file');\n }\n\n // Process Docker compose templates\n if (workspaceConfig.docker?.compose_template) {\n const templateDir = join(projectConfig.path, workspaceConfig.docker.compose_template);\n const devcontainerDir = join(workspacePath, '.devcontainer');\n mkdirSync(devcontainerDir, { recursive: true });\n\n const templateSteps = processTemplates(templateDir, devcontainerDir, placeholders);\n result.steps.push(...templateSteps);\n\n // Copy non-template files (like Dockerfile)\n if (existsSync(templateDir)) {\n const files = readdirSync(templateDir);\n for (const file of files) {\n if (!file.endsWith('.template')) {\n const sourcePath = join(templateDir, file);\n const targetPath = join(devcontainerDir, file);\n copyFileSync(sourcePath, targetPath);\n }\n }\n }\n\n // Sanitize docker-compose files to use platform-agnostic paths\n // This fixes hardcoded /home/username or /Users/username paths\n const composeFiles = readdirSync(devcontainerDir)\n .filter(f => f.includes('compose') && (f.endsWith('.yml') || f.endsWith('.yaml')));\n for (const composeFile of composeFiles) {\n sanitizeComposeFile(join(devcontainerDir, composeFile));\n }\n if (composeFiles.length > 0) {\n result.steps.push(`Sanitized ${composeFiles.length} compose file(s) for platform compatibility`);\n }\n\n // Create ./dev symlink at workspace root pointing to .devcontainer/dev\n // Symlink keeps changes in sync - editing ./dev updates .devcontainer/dev\n const devScriptInContainer = join(devcontainerDir, 'dev');\n const devScriptAtRoot = join(workspacePath, 'dev');\n if (existsSync(devScriptInContainer) && !existsSync(devScriptAtRoot)) {\n try {\n symlinkSync('.devcontainer/dev', devScriptAtRoot);\n chmodSync(devScriptInContainer, 0o755); // Make executable\n result.steps.push('Created ./dev symlink');\n } catch (error) {\n result.errors.push(`Failed to create ./dev symlink: ${error}`);\n }\n }\n }\n\n // Note: Beads initialization is handled by the calling command (workspace.ts)\n // With beads v0.47.1+, worktrees use shared database with labels for isolation\n // The workspace.ts command creates a bead with workspace:issue-id label\n\n // Set up Cloudflare tunnel for external access (before Docker so containers can use tunnel URLs)\n if (workspaceConfig.tunnel) {\n const tunnelResult = await addTunnelIngress(workspaceConfig.tunnel, placeholders);\n result.steps.push(...tunnelResult.steps);\n if (!tunnelResult.success) {\n result.errors.push('Tunnel setup had failures (see steps for details)');\n }\n }\n\n // Create Hume EVI config and write env file for Docker (before Docker so containers pick up the config ID)\n if (workspaceConfig.hume) {\n const humeResult = await createHumeConfig(workspaceConfig.hume, placeholders);\n result.steps.push(...humeResult.steps);\n if (humeResult.configId) {\n writeFileSync(\n join(workspacePath, '.hume-config'),\n `HUME_CONFIG_ID=${humeResult.configId}\\nVITE_HUME_CONFIG_ID=${humeResult.configId}\\n`,\n );\n result.steps.push('Wrote .hume-config with Hume EVI config ID');\n }\n if (!humeResult.success) {\n result.errors.push('Hume EVI config setup had failures (see steps for details)');\n }\n }\n\n progress('Installing skills & templates', 'Skills and templates ready', 'complete');\n\n // Start Docker containers if requested\n if (startDocker) {\n progress('Starting Docker containers', 'Building and starting services');\n // Check for Traefik\n if (workspaceConfig.docker?.traefik) {\n // Always use the installed Traefik location (~/.panopticon/traefik/), not the\n // template source in projects.yaml. The template is copied to ~/.panopticon/traefik/\n // during `pan install`, and the installed copy has the correct volume mounts\n // (dynamic configs, certs) relative to ~/.panopticon/traefik/.\n const traefikPath = join(homedir(), '.panopticon', 'traefik', 'docker-compose.yml');\n if (existsSync(traefikPath)) {\n try {\n await execAsync(`docker compose -f \"${traefikPath}\" up -d`, { cwd: join(homedir(), '.panopticon', 'traefik') });\n result.steps.push('Started Traefik');\n } catch (error: any) {\n const msg = error?.message || String(error);\n if (msg.includes('port is already allocated') || msg.includes('address already in use')) {\n // Traefik (or another reverse proxy) is already running — not an error\n result.steps.push('Traefik already running (port in use)');\n } else {\n result.errors.push(`Failed to start Traefik: ${error}`);\n }\n }\n }\n }\n\n // Start workspace containers\n const composeLocations = [\n join(workspacePath, 'docker-compose.yml'),\n join(workspacePath, 'docker-compose.yaml'),\n join(workspacePath, '.devcontainer', 'docker-compose.yml'),\n join(workspacePath, '.devcontainer', 'docker-compose.devcontainer.yml'),\n ];\n\n for (const composePath of composeLocations) {\n if (existsSync(composePath)) {\n try {\n // Don't pass -p: the compose file's `name:` field is the authority.\n // Passing -p with a different value creates a second Docker project\n // on container restart, splitting services onto separate networks.\n await execAsync(`docker compose -f \"${composePath}\" up -d --build`, { cwd: dirname(composePath), timeout: 300000 });\n result.steps.push(`Started containers from ${basename(composePath)}`);\n } catch (error) {\n result.errors.push(`Failed to start containers: ${error}`);\n }\n break;\n }\n }\n }\n\n if (startDocker) {\n progress('Starting Docker containers', 'Containers running', 'complete');\n }\n\n // Pre-trust workspace directory in Claude Code so agents don't get the trust prompt\n try {\n preTrustDirectory(workspacePath);\n result.steps.push('Pre-trusted workspace in Claude Code');\n } catch {\n // Non-fatal — agent can still work, user will just see trust prompt\n }\n\n result.success = result.errors.length === 0;\n return result;\n}\n\n/**\n * Pre-register a directory as trusted in Claude Code's ~/.claude.json.\n * This prevents the \"Quick safety check: Is this a project you created or one you trust?\" prompt\n * when agents are spawned in dynamically-created workspace directories.\n */\nexport function preTrustDirectory(dirPath: string): void {\n const claudeJsonPath = join(homedir(), '.claude.json');\n if (!existsSync(claudeJsonPath)) return;\n\n const data = JSON.parse(readFileSync(claudeJsonPath, 'utf8'));\n if (!data.projects) data.projects = {};\n\n // Only add if not already present\n if (data.projects[dirPath]) {\n if (!data.projects[dirPath].hasTrustDialogAccepted) {\n data.projects[dirPath].hasTrustDialogAccepted = true;\n writeFileSync(claudeJsonPath, JSON.stringify(data, null, 2), 'utf8');\n }\n return;\n }\n\n data.projects[dirPath] = {\n allowedTools: [],\n mcpContextUris: [],\n mcpServers: {},\n enabledMcpjsonServers: [],\n disabledMcpjsonServers: [],\n hasTrustDialogAccepted: true,\n projectOnboardingSeenCount: 0,\n hasClaudeMdExternalIncludesApproved: false,\n hasClaudeMdExternalIncludesWarningShown: false,\n };\n\n writeFileSync(claudeJsonPath, JSON.stringify(data, null, 2), 'utf8');\n}\n\nexport interface WorkspaceRemoveOptions {\n projectConfig: ProjectConfig;\n featureName: string;\n dryRun?: boolean;\n}\n\nexport interface WorkspaceRemoveResult {\n success: boolean;\n errors: string[];\n steps: string[];\n}\n\n/**\n * Result of Docker container cleanup for a workspace.\n */\nexport interface DockerCleanupResult {\n /** Whether compose files were found (containers may or may not have been running) */\n containersFound: boolean;\n /** Human-readable log of cleanup steps taken */\n steps: string[];\n}\n\n/**\n * Stop Docker containers and clean up Docker-created files for a workspace.\n *\n * Extracted as a standalone function so it can be used by:\n * - removeWorkspace() during normal workspace removal\n * - deep-wipe endpoint for complete issue cleanup\n * - workspace-migrate for pre-migration cleanup\n *\n * Failures are logged but never thrown — callers should not fail if Docker is unavailable.\n */\nexport async function stopWorkspaceDocker(\n workspacePath: string,\n projectName: string,\n featureName: string,\n): Promise<DockerCleanupResult> {\n const result: DockerCleanupResult = {\n containersFound: false,\n steps: [],\n };\n\n // Find all compose files in devcontainer directory (some projects use multiple)\n const devcontainerDir = join(workspacePath, '.devcontainer');\n const composeFiles: string[] = [];\n\n if (existsSync(devcontainerDir)) {\n const possibleFiles = [\n 'docker-compose.devcontainer.yml',\n 'docker-compose.yml',\n 'compose.yml',\n 'compose.infra.yml',\n 'compose.override.yml',\n ];\n for (const file of possibleFiles) {\n const fullPath = join(devcontainerDir, file);\n if (existsSync(fullPath)) {\n composeFiles.push(fullPath);\n }\n }\n }\n\n // Fallback: check for compose file in workspace root\n if (composeFiles.length === 0) {\n const rootCompose = join(workspacePath, 'docker-compose.yml');\n if (existsSync(rootCompose)) {\n composeFiles.push(rootCompose);\n }\n }\n\n if (composeFiles.length > 0) {\n result.containersFound = true;\n try {\n const fileFlags = composeFiles.map(f => `-f \"${f}\"`).join(' ');\n const cwd = existsSync(devcontainerDir) ? devcontainerDir : workspacePath;\n\n // Derive compose project name from the dev script (same logic as dashboard)\n // or fall back to \"{projectName}-feature-{featureName}\" convention.\n let composeProjectName = `${projectName}-feature-${featureName}`;\n const devScriptPaths = [\n join(workspacePath, '.devcontainer', 'dev'),\n join(workspacePath, 'dev'),\n ];\n for (const devPath of devScriptPaths) {\n try {\n if (existsSync(devPath)) {\n const content = readFileSync(devPath, 'utf-8');\n const match = content.match(/COMPOSE_PROJECT_NAME=\"([^$\"]*)\\$\\{FEATURE_FOLDER\\}\"/);\n if (match) {\n composeProjectName = `${match[1]}feature-${featureName}`;\n break;\n }\n const literalMatch = content.match(/COMPOSE_PROJECT_NAME=\"([^\"]+)\"/);\n if (literalMatch) {\n composeProjectName = literalMatch[1];\n break;\n }\n }\n } catch {\n // Fall through to default\n }\n }\n\n await execAsync(`docker compose ${fileFlags} -p \"${composeProjectName}\" down -v --remove-orphans`, {\n cwd,\n timeout: 60000,\n });\n result.steps.push(`Stopped Docker containers (${composeFiles.length} compose files)`);\n } catch (error: any) {\n // Log but don't fail — containers might not be running\n result.steps.push(`Docker cleanup attempted (${error.message?.split('\\n')[0] || 'containers may not be running'})`);\n }\n }\n\n // Clean up Docker-created files (root-owned in containers)\n try {\n await execAsync(\n `docker run --rm -v \"${workspacePath}:/workspace\" alpine sh -c \"find /workspace -user root -delete 2>&1 | tail -100 || true\"`,\n { timeout: 30000, maxBuffer: 10 * 1024 * 1024 }\n );\n result.steps.push('Cleaned up Docker-created files');\n } catch {\n // Alpine container might not be available\n }\n\n return result;\n}\n\n/**\n * Remove a workspace\n */\nexport async function removeWorkspace(options: WorkspaceRemoveOptions): Promise<WorkspaceRemoveResult> {\n const { projectConfig, featureName, dryRun } = options;\n const result: WorkspaceRemoveResult = {\n success: true,\n errors: [],\n steps: [],\n };\n\n const workspaceConfig = projectConfig.workspace || getDefaultWorkspaceConfig();\n const workspacesDir = join(projectConfig.path, workspaceConfig.workspaces_dir || 'workspaces');\n const featureFolder = `feature-${featureName}`;\n const workspacePath = join(workspacesDir, featureFolder);\n\n if (!existsSync(workspacePath)) {\n result.success = false;\n result.errors.push(`Workspace not found at ${workspacePath}`);\n return result;\n }\n\n if (dryRun) {\n result.steps.push('[DRY RUN] Would remove workspace at: ' + workspacePath);\n return result;\n }\n\n // Stop TLDR daemon for workspace (if it exists)\n const venvPath = join(workspacePath, '.venv');\n if (existsSync(venvPath)) {\n try {\n const { getTldrDaemonService } = await import('./tldr-daemon.js');\n const tldrService = getTldrDaemonService(workspacePath, venvPath);\n await tldrService.stop();\n result.steps.push('Stopped TLDR daemon');\n } catch (error: any) {\n // Non-fatal - daemon may not be running\n console.warn(`⚠ Failed to stop TLDR daemon: ${error?.message}`);\n }\n }\n\n // Stop Docker containers and clean up Docker-created files\n const dockerResult = await stopWorkspaceDocker(workspacePath, projectConfig.name || 'workspace', featureName);\n result.steps.push(...dockerResult.steps);\n\n // Remove worktrees\n if (workspaceConfig.type === 'polyrepo' && workspaceConfig.repos) {\n for (const repo of workspaceConfig.repos) {\n const repoPath = join(projectConfig.path, repo.path);\n const targetPath = join(workspacePath, repo.name);\n const branchPrefix = repo.branch_prefix || 'feature/';\n const branchName = `${branchPrefix}${featureName}`;\n\n const worktreeResult = await removeWorktree(repoPath, targetPath, branchName);\n if (worktreeResult.success) {\n result.steps.push(`Removed worktree for ${repo.name}`);\n } else {\n result.errors.push(worktreeResult.message);\n }\n }\n } else {\n // Monorepo: remove single worktree\n const branchName = `feature/${featureName}`;\n const worktreeResult = await removeWorktree(projectConfig.path, workspacePath, branchName);\n if (worktreeResult.success) {\n result.steps.push('Removed worktree');\n } else {\n result.errors.push(worktreeResult.message);\n }\n }\n\n // Remove DNS entries\n if (workspaceConfig.dns) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n\n const dnsMethod = workspaceConfig.dns.sync_method || 'wsl2hosts';\n for (const entryPattern of workspaceConfig.dns.entries) {\n const hostname = replacePlaceholders(entryPattern, placeholders);\n if (removeDnsEntry(dnsMethod, hostname)) {\n result.steps.push(`Removed DNS entry: ${hostname}`);\n }\n }\n }\n\n // Remove Cloudflare tunnel entries\n if (workspaceConfig.tunnel) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n const tunnelResult = await removeTunnelIngress(workspaceConfig.tunnel, placeholders);\n result.steps.push(...tunnelResult.steps);\n }\n\n // Remove Hume EVI config\n if (workspaceConfig.hume) {\n const placeholders = createPlaceholders(projectConfig, featureName, workspacePath);\n const humeResult = await deleteHumeConfig(workspaceConfig.hume, placeholders);\n result.steps.push(...humeResult.steps);\n }\n\n // Release ports\n if (workspaceConfig.ports) {\n for (const [portName] of Object.entries(workspaceConfig.ports)) {\n const portFile = join(projectConfig.path, `.${portName}-ports`);\n if (releasePort(portFile, featureFolder)) {\n result.steps.push(`Released ${portName} port`);\n }\n }\n }\n\n // Remove workspace directory\n try {\n await execAsync(`rm -rf \"${workspacePath}\"`, { maxBuffer: 10 * 1024 * 1024 });\n result.steps.push('Removed workspace directory');\n } catch (error) {\n result.errors.push(`Failed to remove workspace directory: ${error}`);\n }\n\n result.success = result.errors.length === 0;\n return result;\n}\n"],"mappings":"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"}
|
package/dist/hume-BjmwmJ9E.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"label-cleanup-31ElPqqv.js","names":[],"sources":["../src/lib/lifecycle/label-cleanup.ts"],"sourcesContent":["/**\n * label-cleanup — Remove workflow labels and apply 'merged' label after merge.\n *\n * Runs as part of postMergeLifecycle (step 3b), independently of close-issue.\n * Labels are cleaned even if the issue close step fails.\n *\n * Removes: in-review, in-progress, merge-agent\n * Adds: merged\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport type { LifecycleContext, StepResult } from './types.js';\nimport { stepOk, stepSkipped, stepFailed, getLinearApiKey } from './types.js';\n\nconst execAsync = promisify(exec);\n\nconst MERGED_LABEL = 'merged';\nconst MERGED_COLOR = '0e8a16'; // green\nconst LABELS_TO_REMOVE = ['in-review', 'in-progress', 'merge-agent'];\n\n/**\n * Remove workflow labels and apply 'merged' label.\n * Non-fatal: label management failure does not block the merge lifecycle.\n */\nexport async function cleanupMergedLabels(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'label-cleanup:merged';\n\n if (ctx.github) {\n return cleanupLabelsGitHub(ctx);\n }\n\n const linearApiKey = getLinearApiKey();\n if (linearApiKey) {\n return cleanupLabelsLinear(ctx, linearApiKey);\n }\n\n return stepSkipped(step, ['No tracker available for label cleanup']);\n}\n\nasync function cleanupLabelsGitHub(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'label-cleanup:merged';\n if (!ctx.github) return stepSkipped(step);\n const { owner, repo, number } = ctx.github;\n\n try {\n // Ensure merged label exists\n await execAsync(\n `gh label create \"${MERGED_LABEL}\" --repo ${owner}/${repo} --color \"${MERGED_COLOR}\" --description \"Merged to main\" --force 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n\n // Add merged label\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --add-label \"${MERGED_LABEL}\"`,\n { encoding: 'utf-8' },\n );\n\n // Remove workflow labels (best-effort — skip if not present)\n for (const label of LABELS_TO_REMOVE) {\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --remove-label \"${label}\" 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n }\n\n return stepOk(step, [\n `Applied '${MERGED_LABEL}' label on GitHub #${number}`,\n `Removed: ${LABELS_TO_REMOVE.join(', ')}`,\n ]);\n } catch (err) {\n return stepFailed(step, `Label cleanup failed: ${(err as Error).message}`);\n }\n}\n\nasync function cleanupLabelsLinear(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'label-cleanup:merged';\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNum = parseInt(ctx.issueId.split('-').pop() || '0', 10);\n const teamKey = ctx.issueId.split('-')[0].toUpperCase();\n const results = await client.issues({\n filter: {\n number: { eq: issueNum },\n team: { key: { eq: teamKey } },\n },\n first: 1,\n });\n\n if (results.nodes.length === 0) {\n return stepSkipped(step, ['Issue not found for label cleanup']);\n }\n\n const issue = results.nodes[0];\n\n // Find or create merged label\n const labelSearch = await client.issueLabels({ filter: { name: { eq: MERGED_LABEL } } });\n let mergedLabelId: string;\n if (labelSearch.nodes.length > 0) {\n mergedLabelId = labelSearch.nodes[0].id;\n } else {\n const created = await client.createIssueLabel({ name: MERGED_LABEL, color: `#${MERGED_COLOR}` });\n const createdLabel = await created.issueLabel;\n mergedLabelId = createdLabel ? createdLabel.id : '';\n }\n\n if (mergedLabelId) {\n const existingLabels = await issue.labels();\n // Remove workflow labels, add merged\n const filteredIds = existingLabels.nodes\n .filter(l => !LABELS_TO_REMOVE.includes(l.name))\n .map(l => l.id);\n if (!filteredIds.includes(mergedLabelId)) {\n filteredIds.push(mergedLabelId);\n }\n await issue.update({ labelIds: filteredIds });\n }\n\n return stepOk(step, [\n `Applied '${MERGED_LABEL}' label on Linear ${ctx.issueId}`,\n `Removed: ${LABELS_TO_REMOVE.join(', ')}`,\n ]);\n } catch (err) {\n return stepFailed(step, `Linear label cleanup failed: ${(err as Error).message}`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,YAAY,UAAU,KAAK;AAEjC,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,mBAAmB;CAAC;CAAa;CAAe;CAAc;;;;;AAMpE,eAAsB,oBAAoB,KAA4C;CACpF,MAAM,OAAO;AAEb,KAAI,IAAI,OACN,QAAO,oBAAoB,IAAI;CAGjC,MAAM,eAAe,iBAAiB;AACtC,KAAI,aACF,QAAO,oBAAoB,KAAK,aAAa;AAG/C,QAAO,YAAY,MAAM,CAAC,yCAAyC,CAAC;;AAGtE,eAAe,oBAAoB,KAA4C;CAC7E,MAAM,OAAO;AACb,KAAI,CAAC,IAAI,OAAQ,QAAO,YAAY,KAAK;CACzC,MAAM,EAAE,OAAO,MAAM,WAAW,IAAI;AAEpC,KAAI;AAEF,QAAM,UACJ,oBAAoB,aAAa,WAAW,MAAM,GAAG,KAAK,YAAY,aAAa,+DACnF,EAAE,UAAU,SAAS,CACtB;AAGD,QAAM,UACJ,iBAAiB,OAAO,UAAU,MAAM,GAAG,KAAK,gBAAgB,aAAa,IAC7E,EAAE,UAAU,SAAS,CACtB;AAGD,OAAK,MAAM,SAAS,iBAClB,OAAM,UACJ,iBAAiB,OAAO,UAAU,MAAM,GAAG,KAAK,mBAAmB,MAAM,wBACzE,EAAE,UAAU,SAAS,CACtB;AAGH,SAAO,OAAO,MAAM,CAClB,YAAY,aAAa,qBAAqB,UAC9C,YAAY,iBAAiB,KAAK,KAAK,GACxC,CAAC;UACK,KAAK;AACZ,SAAO,WAAW,MAAM,yBAA0B,IAAc,UAAU;;;AAI9E,eAAe,oBAAoB,KAAuB,QAAqC;CAC7F,MAAM,OAAO;AACb,KAAI;EACF,MAAM,EAAE,iBAAiB,MAAM,OAAO;EACtC,MAAM,SAAS,IAAI,aAAa,EAAE,QAAQ,CAAC;EAE3C,MAAM,WAAW,SAAS,IAAI,QAAQ,MAAM,IAAI,CAAC,KAAK,IAAI,KAAK,GAAG;EAClE,MAAM,UAAU,IAAI,QAAQ,MAAM,IAAI,CAAC,GAAG,aAAa;EACvD,MAAM,UAAU,MAAM,OAAO,OAAO;GAClC,QAAQ;IACN,QAAQ,EAAE,IAAI,UAAU;IACxB,MAAM,EAAE,KAAK,EAAE,IAAI,SAAS,EAAE;IAC/B;GACD,OAAO;GACR,CAAC;AAEF,MAAI,QAAQ,MAAM,WAAW,EAC3B,QAAO,YAAY,MAAM,CAAC,oCAAoC,CAAC;EAGjE,MAAM,QAAQ,QAAQ,MAAM;EAG5B,MAAM,cAAc,MAAM,OAAO,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,cAAc,EAAE,EAAE,CAAC;EACxF,IAAI;AACJ,MAAI,YAAY,MAAM,SAAS,EAC7B,iBAAgB,YAAY,MAAM,GAAG;OAChC;GAEL,MAAM,eAAe,OADL,MAAM,OAAO,iBAAiB;IAAE,MAAM;IAAc,OAAO,IAAI;IAAgB,CAAC,EAC7D;AACnC,mBAAgB,eAAe,aAAa,KAAK;;AAGnD,MAAI,eAAe;GAGjB,MAAM,eAFiB,MAAM,MAAM,QAAQ,EAER,MAChC,QAAO,MAAK,CAAC,iBAAiB,SAAS,EAAE,KAAK,CAAC,CAC/C,KAAI,MAAK,EAAE,GAAG;AACjB,OAAI,CAAC,YAAY,SAAS,cAAc,CACtC,aAAY,KAAK,cAAc;AAEjC,SAAM,MAAM,OAAO,EAAE,UAAU,aAAa,CAAC;;AAG/C,SAAO,OAAO,MAAM,CAClB,YAAY,aAAa,oBAAoB,IAAI,WACjD,YAAY,iBAAiB,KAAK,KAAK,GACxC,CAAC;UACK,KAAK;AACZ,SAAO,WAAW,MAAM,gCAAiC,IAAc,UAAU"}
|