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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-BcWPTrua.js","names":[],"sources":["../src/lib/backup.ts","../src/lib/sync.ts","../src/lib/settings.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, cpSync, rmSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { BACKUPS_DIR } from './paths.js';\n\nexport interface BackupInfo {\n timestamp: string;\n path: string;\n targets: string[];\n}\n\nexport function createBackupTimestamp(): string {\n return new Date().toISOString().replace(/[:.]/g, '-');\n}\n\nexport function createBackup(sourceDirs: string[]): BackupInfo {\n const timestamp = createBackupTimestamp();\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n mkdirSync(backupPath, { recursive: true });\n\n const targets: string[] = [];\n\n for (const sourceDir of sourceDirs) {\n if (!existsSync(sourceDir)) continue;\n\n const targetName = basename(sourceDir);\n const targetPath = join(backupPath, targetName);\n\n // Use filter to skip symlinks — sync targets (e.g. ~/.claude/skills/)\n // contain symlinks back into ~/.panopticon/skills/ which causes cpSync\n // to fail with \"cannot copy to a subdirectory of self\".\n cpSync(sourceDir, targetPath, {\n recursive: true,\n filter: (src) => !lstatSync(src).isSymbolicLink(),\n });\n targets.push(targetName);\n }\n\n return {\n timestamp,\n path: backupPath,\n targets,\n };\n}\n\nexport function listBackups(): BackupInfo[] {\n if (!existsSync(BACKUPS_DIR)) return [];\n\n const entries = readdirSync(BACKUPS_DIR, { withFileTypes: true });\n\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => {\n const backupPath = join(BACKUPS_DIR, e.name);\n const contents = readdirSync(backupPath);\n\n return {\n timestamp: e.name,\n path: backupPath,\n targets: contents,\n };\n })\n .sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n}\n\nexport function restoreBackup(timestamp: string, targetDirs: Record<string, string>): void {\n const backupPath = join(BACKUPS_DIR, timestamp);\n\n if (!existsSync(backupPath)) {\n throw new Error(`Backup not found: ${timestamp}`);\n }\n\n const contents = readdirSync(backupPath, { withFileTypes: true });\n\n for (const entry of contents) {\n if (!entry.isDirectory()) continue;\n\n const sourcePath = join(backupPath, entry.name);\n const targetPath = targetDirs[entry.name];\n\n if (!targetPath) continue;\n\n // Remove existing and restore from backup\n if (existsSync(targetPath)) {\n rmSync(targetPath, { recursive: true });\n }\n\n cpSync(sourcePath, targetPath, { recursive: true });\n }\n}\n\nexport function cleanOldBackups(keepCount: number = 10): number {\n const backups = listBackups();\n\n if (backups.length <= keepCount) return 0;\n\n const toRemove = backups.slice(keepCount);\n let removed = 0;\n\n for (const backup of toRemove) {\n rmSync(backup.path, { recursive: true });\n removed++;\n }\n\n return removed;\n}\n","import { existsSync, mkdirSync, readdirSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, rmSync, copyFileSync, chmodSync, readFileSync, writeFileSync } from 'fs';\nimport { join, basename, dirname, relative } from 'path';\nimport { homedir } from 'os';\nimport {\n SKILLS_DIR, COMMANDS_DIR, AGENTS_DIR, BIN_DIR,\n SOURCE_SCRIPTS_DIR, SOURCE_DEV_SKILLS_DIR, SOURCE_SKILLS_DIR, SOURCE_AGENTS_DIR, SOURCE_RULES_DIR,\n CACHE_AGENTS_DIR, CACHE_RULES_DIR, CACHE_MANIFEST,\n SYNC_TARGET, isDevMode,\n} from './paths.js';\nimport {\n buildManifestFromDirectory, writeManifest, readManifest, hashFile,\n setManifestEntry, collectSourceFiles,\n type Manifest, type FileStatus,\n compareFileToManifest,\n} from './manifest.js';\nimport { getDevrootPath } from './config.js';\n\nexport interface SyncItem {\n name: string;\n sourcePath: string;\n targetPath: string;\n status: 'new' | 'exists' | 'conflict' | 'symlink';\n}\n\nexport interface SyncPlan {\n skills: SyncItem[];\n commands: SyncItem[];\n agents: SyncItem[];\n rules: SyncItem[];\n devSkills: SyncItem[]; // Developer-only skills (only synced in dev mode)\n}\n\n/**\n * Remove a file, symlink, or directory safely\n */\nfunction removeTarget(targetPath: string): void {\n const stats = lstatSync(targetPath);\n if (stats.isDirectory() && !stats.isSymbolicLink()) {\n // It's a real directory, remove recursively\n rmSync(targetPath, { recursive: true, force: true });\n } else {\n // It's a file or symlink\n unlinkSync(targetPath);\n }\n}\n\n/**\n * Check if a path is a Panopticon-managed symlink\n */\nexport function isPanopticonSymlink(targetPath: string): boolean {\n if (!existsSync(targetPath)) return false;\n\n try {\n const stats = lstatSync(targetPath);\n if (!stats.isSymbolicLink()) return false;\n\n const linkTarget = readlinkSync(targetPath);\n // It's ours if it points to our skills/commands dir\n return linkTarget.includes('.panopticon');\n } catch {\n return false;\n }\n}\n\nexport interface MigrationResult {\n removedSymlinks: string[];\n preservedUserContent: string[];\n errors: string[];\n}\n\n/**\n * One-time migration: remove Panopticon-managed symlinks from ~/.claude/.\n *\n * Detects symlinks in ~/.claude/skills/ and ~/.claude/agents/ that point to\n * .panopticon directories. Removes only those symlinks, preserving any\n * user-created content (real files/directories).\n *\n * This is safe to run multiple times — it's a no-op if nothing remains to clean up.\n *\n * Removes two kinds of stale Panopticon content from ~/.claude/:\n * 1. Symlinks pointing to .panopticon or panopticon-cli (legacy sync method)\n * 2. Plain directories that also exist in the devroot (stale copies from before\n * the devroot migration — these cause duplicate skill listings)\n */\nexport function migrateStalePersonalContent(): MigrationResult {\n const claudeDir = join(homedir(), '.claude');\n const result: MigrationResult = {\n removedSymlinks: [],\n preservedUserContent: [],\n errors: [],\n };\n\n // Build a set of skill/agent/command names that exist in the devroot\n // so we can identify stale copies in ~/.claude/\n const devrootNames = new Set<string>();\n const devroot = getDevrootPath();\n if (devroot) {\n for (const subdir of ['skills', 'commands', 'agents']) {\n const devrootDir = join(devroot, '.claude', subdir);\n if (existsSync(devrootDir)) {\n try {\n for (const entry of readdirSync(devrootDir)) {\n devrootNames.add(`${subdir}/${entry}`);\n }\n } catch {\n // Ignore read errors on devroot\n }\n }\n }\n }\n\n for (const subdir of ['skills', 'commands', 'agents']) {\n const dir = join(claudeDir, subdir);\n if (!existsSync(dir)) continue;\n\n try {\n const entries = readdirSync(dir);\n for (const entry of entries) {\n const entryPath = join(dir, entry);\n try {\n const stats = lstatSync(entryPath);\n if (stats.isSymbolicLink()) {\n const linkTarget = readlinkSync(entryPath);\n if (linkTarget.includes('.panopticon') || linkTarget.includes('panopticon-cli')) {\n unlinkSync(entryPath);\n result.removedSymlinks.push(`${subdir}/${entry}`);\n } else {\n // Symlink to somewhere else — leave it\n result.preservedUserContent.push(`${subdir}/${entry}`);\n }\n } else if (stats.isDirectory() && devrootNames.has(`${subdir}/${entry}`)) {\n // Plain directory that also exists in devroot — stale Panopticon copy.\n // The devroot copy is the canonical one; this personal copy causes\n // duplicate listings and violates principle #4 (never touch ~/.claude/).\n rmSync(entryPath, { recursive: true, force: true });\n result.removedSymlinks.push(`${subdir}/${entry} (stale copy)`);\n } else {\n // Real file/directory with no devroot counterpart — user content, never touch\n result.preservedUserContent.push(`${subdir}/${entry}`);\n }\n } catch (err: any) {\n result.errors.push(`${subdir}/${entry}: ${err.message}`);\n }\n }\n } catch (err: any) {\n result.errors.push(`${subdir}: ${err.message}`);\n }\n }\n\n return result;\n}\n\nexport interface RefreshCacheResult {\n skills: { copied: number; total: number };\n agents: { copied: number; total: number };\n rules: { copied: number; total: number };\n}\n\n/**\n * Recursively copy a directory, overwriting existing files.\n */\nfunction copyDirectoryRecursive(source: string, dest: string): number {\n if (!existsSync(source)) return 0;\n\n mkdirSync(dest, { recursive: true });\n let count = 0;\n\n const entries = readdirSync(source, { withFileTypes: true });\n for (const entry of entries) {\n const srcPath = join(source, entry.name);\n const dstPath = join(dest, entry.name);\n if (entry.isDirectory()) {\n count += copyDirectoryRecursive(srcPath, dstPath);\n } else if (entry.isFile()) {\n copyFileSync(srcPath, dstPath);\n count++;\n }\n }\n return count;\n}\n\n/**\n * Refresh the ~/.panopticon/ cache from the repo source.\n *\n * Always copies (overwrites) skills, agents, and rules from the package's\n * source directories to the cache. Generates ~/.panopticon/.manifest.json\n * tracking all cached files.\n *\n * This replaces the old \"skip if exists\" behavior in `pan install`.\n */\nexport function refreshCache(): RefreshCacheResult {\n const result: RefreshCacheResult = {\n skills: { copied: 0, total: 0 },\n agents: { copied: 0, total: 0 },\n rules: { copied: 0, total: 0 },\n };\n\n // Copy skills from repo to cache (always overwrite)\n if (existsSync(SOURCE_SKILLS_DIR)) {\n const skillDirs = readdirSync(SOURCE_SKILLS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n result.skills.total = skillDirs.length;\n for (const skillDir of skillDirs) {\n const src = join(SOURCE_SKILLS_DIR, skillDir.name);\n const dst = join(SKILLS_DIR, skillDir.name);\n copyDirectoryRecursive(src, dst);\n result.skills.copied++;\n }\n }\n\n // Copy dev-skills to cache too (in dev mode only)\n if (isDevMode() && existsSync(SOURCE_DEV_SKILLS_DIR)) {\n const devSkillDirs = readdirSync(SOURCE_DEV_SKILLS_DIR, { withFileTypes: true })\n .filter((d) => d.isDirectory());\n\n for (const skillDir of devSkillDirs) {\n const src = join(SOURCE_DEV_SKILLS_DIR, skillDir.name);\n const dst = join(SKILLS_DIR, skillDir.name);\n copyDirectoryRecursive(src, dst);\n result.skills.copied++;\n result.skills.total++;\n }\n }\n\n // Copy agent definitions from repo to cache\n if (existsSync(SOURCE_AGENTS_DIR)) {\n mkdirSync(CACHE_AGENTS_DIR, { recursive: true });\n const agents = readdirSync(SOURCE_AGENTS_DIR, { withFileTypes: true })\n .filter((entry) => entry.isFile() && entry.name.endsWith('.md'));\n\n result.agents.total = agents.length;\n for (const agent of agents) {\n copyFileSync(join(SOURCE_AGENTS_DIR, agent.name), join(CACHE_AGENTS_DIR, agent.name));\n result.agents.copied++;\n }\n }\n\n // Copy rules from repo to cache (directory may not exist yet)\n if (existsSync(SOURCE_RULES_DIR)) {\n const ruleFiles = readdirSync(SOURCE_RULES_DIR, { withFileTypes: true })\n .filter((entry) => entry.isFile());\n\n result.rules.total = ruleFiles.length;\n for (const rule of ruleFiles) {\n mkdirSync(CACHE_RULES_DIR, { recursive: true });\n copyFileSync(join(SOURCE_RULES_DIR, rule.name), join(CACHE_RULES_DIR, rule.name));\n result.rules.copied++;\n }\n }\n\n // Generate cache manifest\n const manifest = buildManifestFromDirectory(\n join(SKILLS_DIR, '..'), // ~/.panopticon/\n ['skills', 'agent-definitions', 'rules'],\n 'panopticon',\n );\n writeManifest(CACHE_MANIFEST, manifest);\n\n return result;\n}\n\n/**\n * Devroot sync item — represents a single file to distribute.\n */\nexport interface DevrootSyncItem {\n /** Relative path from .claude/ (e.g., \"skills/beads/SKILL.md\") */\n relativePath: string;\n /** Absolute path to source file in cache */\n sourcePath: string;\n /** Absolute path to target file at devroot */\n targetPath: string;\n /** What action to take */\n status: FileStatus;\n}\n\n/**\n * Plan what would be synced to devroot (dry run).\n * Reads from cache, targets <devroot>/.claude/, uses manifest comparison.\n */\nexport function planSync(): SyncPlan {\n const plan: SyncPlan = {\n skills: [],\n commands: [],\n agents: [],\n rules: [],\n devSkills: [],\n };\n\n const devrootPath = getDevrootPath();\n if (!devrootPath) return plan;\n\n const targetBase = join(devrootPath, '.claude');\n const manifestPath = join(targetBase, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n\n // Plan skills\n const skillFiles = collectSourceFiles(SKILLS_DIR, 'skills/');\n for (const file of skillFiles) {\n const targetFile = join(targetBase, file.relativePath);\n const status = compareFileToManifest(targetFile, file.relativePath, manifest);\n const skillName = file.relativePath.split('/')[1] || file.relativePath;\n\n let syncStatus: SyncItem['status'] = 'new';\n if (status.action === 'update') syncStatus = 'symlink'; // reusing 'symlink' for \"managed, safe to update\"\n else if (status.action === 'modified') syncStatus = 'conflict';\n else if (status.action === 'user-owned') syncStatus = 'conflict';\n\n plan.skills.push({\n name: file.relativePath,\n sourcePath: file.absolutePath,\n targetPath: targetFile,\n status: syncStatus,\n });\n }\n\n // Plan agents\n const agentFiles = collectSourceFiles(CACHE_AGENTS_DIR, 'agents/');\n for (const file of agentFiles) {\n const targetFile = join(targetBase, file.relativePath);\n const status = compareFileToManifest(targetFile, file.relativePath, manifest);\n\n let syncStatus: SyncItem['status'] = 'new';\n if (status.action === 'update') syncStatus = 'symlink';\n else if (status.action === 'modified') syncStatus = 'conflict';\n else if (status.action === 'user-owned') syncStatus = 'conflict';\n\n plan.agents.push({\n name: file.relativePath,\n sourcePath: file.absolutePath,\n targetPath: targetFile,\n status: syncStatus,\n });\n }\n\n // Plan rules\n const ruleFiles = collectSourceFiles(CACHE_RULES_DIR, 'rules/');\n for (const file of ruleFiles) {\n const targetFile = join(targetBase, file.relativePath);\n const status = compareFileToManifest(targetFile, file.relativePath, manifest);\n\n let syncStatus: SyncItem['status'] = 'new';\n if (status.action === 'update') syncStatus = 'symlink';\n else if (status.action === 'modified') syncStatus = 'conflict';\n else if (status.action === 'user-owned') syncStatus = 'conflict';\n\n plan.rules.push({\n name: file.relativePath,\n sourcePath: file.absolutePath,\n targetPath: targetFile,\n status: syncStatus,\n });\n }\n\n return plan;\n}\n\nexport interface SyncOptions {\n force?: boolean;\n diff?: boolean;\n dryRun?: boolean;\n}\n\nexport interface SyncResult {\n created: string[];\n updated: string[];\n skipped: string[];\n conflicts: string[];\n diffs: Array<{ path: string; sourceContent: string; targetContent: string }>;\n}\n\n/**\n * Execute sync to devroot: copy from cache to <devroot>/.claude/.\n * Uses manifest-based conflict resolution. NEVER touches ~/.claude/.\n */\nexport function executeSync(options: SyncOptions = {}): SyncResult {\n const result: SyncResult = {\n created: [],\n updated: [],\n skipped: [],\n conflicts: [],\n diffs: [],\n };\n\n const devrootPath = getDevrootPath();\n if (!devrootPath) {\n return result;\n }\n\n const targetBase = join(devrootPath, '.claude');\n const manifestPath = join(targetBase, '.panopticon-manifest.json');\n const manifest = readManifest(manifestPath);\n\n // Collect all source files from cache\n const allFiles = [\n ...collectSourceFiles(SKILLS_DIR, 'skills/'),\n ...collectSourceFiles(CACHE_AGENTS_DIR, 'agents/'),\n ...collectSourceFiles(CACHE_RULES_DIR, 'rules/'),\n ];\n\n for (const file of allFiles) {\n const targetFile = join(targetBase, file.relativePath);\n const status = compareFileToManifest(targetFile, file.relativePath, manifest);\n\n switch (status.action) {\n case 'new': {\n // File doesn't exist at target — copy it\n mkdirSync(dirname(targetFile), { recursive: true });\n copyFileSync(file.absolutePath, targetFile);\n const hash = hashFile(targetFile);\n setManifestEntry(manifest, file.relativePath, hash, 'panopticon');\n result.created.push(file.relativePath);\n break;\n }\n\n case 'update': {\n // File exists, hash matches manifest — safe to overwrite (user didn't modify)\n mkdirSync(dirname(targetFile), { recursive: true });\n copyFileSync(file.absolutePath, targetFile);\n const hash = hashFile(targetFile);\n setManifestEntry(manifest, file.relativePath, hash, 'panopticon');\n result.updated.push(file.relativePath);\n break;\n }\n\n case 'modified': {\n // File was modified since we placed it\n if (options.diff) {\n result.diffs.push({\n path: file.relativePath,\n sourceContent: readFileSync(file.absolutePath, 'utf-8'),\n targetContent: readFileSync(targetFile, 'utf-8'),\n });\n }\n\n if (options.force) {\n mkdirSync(dirname(targetFile), { recursive: true });\n copyFileSync(file.absolutePath, targetFile);\n const hash = hashFile(targetFile);\n setManifestEntry(manifest, file.relativePath, hash, 'panopticon');\n result.updated.push(file.relativePath);\n } else {\n result.conflicts.push(file.relativePath);\n }\n break;\n }\n\n case 'user-owned': {\n // User placed this file, never touch it\n result.skipped.push(file.relativePath);\n break;\n }\n }\n }\n\n // Write updated manifest\n writeManifest(manifestPath, manifest);\n\n return result;\n}\n\n/**\n * Hook item for sync planning\n */\nexport interface HookItem {\n name: string;\n sourcePath: string;\n targetPath: string;\n status: 'new' | 'updated' | 'current';\n}\n\n/**\n * Plan hooks sync (checks what would be updated)\n */\nexport function planHooksSync(): HookItem[] {\n const hooks: HookItem[] = [];\n\n if (!existsSync(SOURCE_SCRIPTS_DIR)) {\n return hooks;\n }\n\n // Sync hook scripts (no extension) and bundled JS scripts (.js)\n // Skip source files (.ts), shell helpers (.sh), and other non-hook files (.mjs)\n const scripts = readdirSync(SOURCE_SCRIPTS_DIR, { withFileTypes: true })\n .filter((entry) => entry.isFile() && !entry.name.startsWith('.')\n && (!entry.name.includes('.') || entry.name.endsWith('.js')));\n\n for (const script of scripts) {\n const sourcePath = join(SOURCE_SCRIPTS_DIR, script.name);\n const targetPath = join(BIN_DIR, script.name);\n\n let status: HookItem['status'] = 'new';\n\n if (existsSync(targetPath)) {\n // Could compare file contents/timestamps here for 'current' vs 'updated'\n // For now, always update to ensure latest version\n status = 'updated';\n }\n\n hooks.push({ name: script.name, sourcePath, targetPath, status });\n }\n\n return hooks;\n}\n\n/**\n * Sync hooks (copy scripts to ~/.panopticon/bin/)\n */\nexport function syncHooks(): { synced: string[]; errors: string[] } {\n const result = { synced: [] as string[], errors: [] as string[] };\n\n // Ensure bin directory exists\n mkdirSync(BIN_DIR, { recursive: true });\n\n const hooks = planHooksSync();\n\n for (const hook of hooks) {\n try {\n copyFileSync(hook.sourcePath, hook.targetPath);\n chmodSync(hook.targetPath, 0o755); // Make executable\n result.synced.push(hook.name);\n } catch (error) {\n result.errors.push(`${hook.name}: ${error}`);\n }\n }\n\n return result;\n}\n\n/**\n * Runtime-specific statusline configurations\n * Maps runtime to: config dir, statusline filename, settings file\n */\nconst STATUSLINE_TARGETS: Record<string, { configDir: string; scriptName: string; settingsFile: string }> = {\n claude: {\n configDir: join(homedir(), '.claude'),\n scriptName: 'statusline-command.sh',\n settingsFile: join(homedir(), '.claude', 'settings.json'),\n },\n // Other runtimes can be added as they support statusline\n};\n\n/**\n * Sync statusline script to all supported runtimes\n * Copies the canonical statusline.sh from panopticon scripts to each runtime's config dir\n * and ensures the runtime's settings.json references it.\n */\nexport function syncStatusline(): { synced: string[]; errors: string[] } {\n const result = { synced: [] as string[], errors: [] as string[] };\n\n const sourceScript = join(SOURCE_SCRIPTS_DIR, 'statusline.sh');\n if (!existsSync(sourceScript)) {\n return result;\n }\n\n for (const [runtime, target] of Object.entries(STATUSLINE_TARGETS)) {\n try {\n // Ensure config dir exists\n mkdirSync(target.configDir, { recursive: true });\n\n // Copy statusline script\n const targetScript = join(target.configDir, target.scriptName);\n copyFileSync(sourceScript, targetScript);\n chmodSync(targetScript, 0o755);\n\n // Update settings.json to reference the statusline\n updateSettingsStatusline(target.settingsFile, targetScript);\n\n result.synced.push(runtime);\n } catch (error) {\n result.errors.push(`${runtime}: ${error}`);\n }\n }\n\n return result;\n}\n\n/**\n * Update a settings.json file to include the statusLine configuration\n * Preserves all existing settings (hooks, etc.)\n */\nfunction updateSettingsStatusline(settingsFile: string, scriptPath: string): void {\n let settings: Record<string, any> = {};\n\n if (existsSync(settingsFile)) {\n try {\n settings = JSON.parse(readFileSync(settingsFile, 'utf-8'));\n } catch {\n // If settings file is corrupt, start fresh but preserve the file\n settings = {};\n }\n }\n\n // Only update if statusLine is missing or points to a different script\n const currentCommand = settings.statusLine?.command;\n if (currentCommand === scriptPath && settings.statusLine?.type === 'command') {\n return; // Already configured correctly\n }\n\n settings.statusLine = {\n type: 'command',\n command: scriptPath,\n padding: 0,\n };\n\n mkdirSync(dirname(settingsFile), { recursive: true });\n writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n}\n","import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { SETTINGS_FILE } from './paths.js';\n\n// Model identifiers\nexport type AnthropicModel = 'claude-opus-4-6' | 'claude-sonnet-4-6' | 'claude-sonnet-4-5' | 'claude-haiku-4-5';\nexport type OpenAIModel = 'gpt-5.2-codex' | 'o3-deep-research' | 'gpt-4o' | 'gpt-4o-mini';\nexport type GoogleModel = 'gemini-3-pro-preview' | 'gemini-3-flash-preview' | 'gemini-2.5-pro' | 'gemini-2.5-flash';\nexport type ZAIModel = 'glm-4.7-flash';\nexport type KimiModel = 'kimi-k2' | 'kimi-k2.5';\nexport type MiniMaxModel = 'minimax-m2.7' | 'minimax-m2.7-highspeed';\nexport type ModelId = AnthropicModel | OpenAIModel | GoogleModel | ZAIModel | KimiModel | MiniMaxModel;\n\n// Task complexity levels\nexport type ComplexityLevel = 'trivial' | 'simple' | 'medium' | 'complex' | 'expert';\n\n// Specialist agent types\nexport interface SpecialistModels {\n review_agent: ModelId;\n test_agent: ModelId;\n merge_agent: ModelId;\n}\n\n// Complexity-based model mapping\nexport type ComplexityModels = {\n [K in ComplexityLevel]: ModelId;\n};\n\n// All model configuration\nexport interface ModelsConfig {\n specialists: SpecialistModels;\n status_review: ModelId;\n complexity: ComplexityModels;\n}\n\n// API keys for external providers\nexport interface ApiKeysConfig {\n openai?: string;\n google?: string;\n zai?: string;\n kimi?: string;\n}\n\n// Complete settings structure\nexport interface SettingsConfig {\n models: ModelsConfig;\n api_keys: ApiKeysConfig;\n}\n\n// Default settings - match optimal defaults from settings-api.ts\nconst DEFAULT_SETTINGS: SettingsConfig = {\n models: {\n specialists: {\n review_agent: 'claude-opus-4-6',\n test_agent: 'claude-sonnet-4-6',\n merge_agent: 'claude-sonnet-4-6',\n },\n status_review: 'claude-opus-4-6',\n complexity: {\n trivial: 'claude-haiku-4-5',\n simple: 'claude-haiku-4-5',\n medium: 'kimi-k2.5',\n complex: 'kimi-k2.5',\n expert: 'claude-opus-4-6',\n },\n },\n api_keys: {},\n};\n\n/**\n * Deep merge utility that recursively merges objects.\n * - Recursively merges nested objects\n * - User values take precedence over defaults\n */\nfunction deepMerge<T extends object>(defaults: T, overrides: Partial<T>): T {\n const result = { ...defaults };\n\n for (const key of Object.keys(overrides) as (keyof T)[]) {\n const defaultVal = defaults[key];\n const overrideVal = overrides[key];\n\n // Skip undefined values in overrides\n if (overrideVal === undefined) continue;\n\n // Deep merge if both values are non-array objects\n if (\n typeof defaultVal === 'object' &&\n defaultVal !== null &&\n !Array.isArray(defaultVal) &&\n typeof overrideVal === 'object' &&\n overrideVal !== null &&\n !Array.isArray(overrideVal)\n ) {\n result[key] = deepMerge(defaultVal, overrideVal as any);\n } else {\n // For primitives or null - override wins\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Load settings from ~/.panopticon/settings.json\n * Returns default settings if file doesn't exist or is invalid\n * Also loads API keys from environment variables as fallback\n */\nexport function loadSettings(): SettingsConfig {\n let settings: SettingsConfig;\n\n if (!existsSync(SETTINGS_FILE)) {\n settings = getDefaultSettings();\n } else {\n try {\n const content = readFileSync(SETTINGS_FILE, 'utf8');\n const parsed = JSON.parse(content) as Partial<SettingsConfig>;\n settings = deepMerge(DEFAULT_SETTINGS, parsed);\n } catch (error) {\n console.error('Warning: Failed to parse settings.json, using defaults');\n settings = getDefaultSettings();\n }\n }\n\n // Load API keys from environment variables as fallback\n // This allows using ~/.panopticon.env for API keys\n const envApiKeys: ApiKeysConfig = {};\n if (process.env.OPENAI_API_KEY) envApiKeys.openai = process.env.OPENAI_API_KEY;\n if (process.env.GOOGLE_API_KEY) envApiKeys.google = process.env.GOOGLE_API_KEY;\n if (process.env.ZAI_API_KEY) envApiKeys.zai = process.env.ZAI_API_KEY;\n if (process.env.KIMI_API_KEY) envApiKeys.kimi = process.env.KIMI_API_KEY;\n\n // Merge env vars as fallback (settings.json takes precedence)\n settings.api_keys = {\n ...envApiKeys,\n ...settings.api_keys,\n };\n\n return settings;\n}\n\n/**\n * Save settings to ~/.panopticon/settings.json\n * Writes with pretty formatting (2-space indent)\n */\nexport function saveSettings(settings: SettingsConfig): void {\n const content = JSON.stringify(settings, null, 2);\n writeFileSync(SETTINGS_FILE, content, 'utf8');\n}\n\n/**\n * Validate settings structure and model IDs\n * Returns error message if invalid, null if valid\n */\nexport function validateSettings(settings: SettingsConfig): string | null {\n // Validate models structure\n if (!settings.models) {\n return 'Missing models configuration';\n }\n\n // Validate specialists\n if (!settings.models.specialists) {\n return 'Missing specialists configuration';\n }\n const specialists = settings.models.specialists;\n if (!specialists.review_agent || !specialists.test_agent || !specialists.merge_agent) {\n return 'Missing specialist agent model configuration';\n }\n\n // Validate complexity levels\n if (!settings.models.complexity) {\n return 'Missing complexity configuration';\n }\n const complexity = settings.models.complexity;\n const requiredLevels: ComplexityLevel[] = ['trivial', 'simple', 'medium', 'complex', 'expert'];\n for (const level of requiredLevels) {\n if (!complexity[level]) {\n return `Missing complexity level: ${level}`;\n }\n }\n\n // Validate api_keys structure (optional keys)\n if (!settings.api_keys) {\n return 'Missing api_keys configuration';\n }\n\n return null;\n}\n\n/**\n * Get a deep copy of the default settings\n */\nexport function getDefaultSettings(): SettingsConfig {\n return JSON.parse(JSON.stringify(DEFAULT_SETTINGS));\n}\n\n/**\n * Get available models for a provider based on configured API keys\n * Returns empty array if provider API key is not configured\n */\nexport function getAvailableModels(settings: SettingsConfig): {\n anthropic: AnthropicModel[];\n openai: OpenAIModel[];\n google: GoogleModel[];\n zai: ZAIModel[];\n kimi: KimiModel[];\n} {\n const anthropicModels: AnthropicModel[] = [\n 'claude-opus-4-6',\n 'claude-sonnet-4-6',\n 'claude-haiku-4-5',\n ];\n\n const openaiModels: OpenAIModel[] = settings.api_keys.openai\n ? ['gpt-5.2-codex', 'o3-deep-research', 'gpt-4o', 'gpt-4o-mini']\n : [];\n\n const googleModels: GoogleModel[] = settings.api_keys.google\n ? ['gemini-3-pro-preview', 'gemini-3-flash-preview']\n : [];\n\n const zaiModels: ZAIModel[] = settings.api_keys.zai\n ? ['glm-4.7-flash']\n : [];\n\n const kimiModels: KimiModel[] = settings.api_keys.kimi\n ? ['kimi-k2', 'kimi-k2.5']\n : [];\n\n return {\n anthropic: anthropicModels,\n openai: openaiModels,\n google: googleModels,\n zai: zaiModels,\n kimi: kimiModels,\n };\n}\n\n/**\n * Check if a model ID is an Anthropic model\n * Anthropic models can be run directly with `claude` CLI\n */\nexport function isAnthropicModel(modelId: ModelId | string): boolean {\n return modelId.startsWith('claude-');\n}\n\n/**\n * Get the Claude CLI model flag for an Anthropic model\n * Maps our model IDs to Claude's expected format\n */\nexport function getClaudeModelFlag(modelId: ModelId | string): string {\n const modelMap: Record<string, string> = {\n 'claude-opus-4-6': 'opus',\n 'claude-sonnet-4-6': 'sonnet',\n 'claude-sonnet-4-5': 'sonnet',\n 'claude-haiku-4-5': 'haiku',\n };\n return modelMap[modelId] || 'sonnet';\n}\n\n/**\n * Get the command to run an agent with a specific model\n * Always uses 'claude' CLI — non-Anthropic models work via ANTHROPIC_BASE_URL env var\n * pointing to their Anthropic-compatible endpoint.\n */\nexport function getAgentCommand(modelId: ModelId | string): { command: string; args: string[] } {\n if (isAnthropicModel(modelId)) {\n return {\n command: 'claude',\n args: ['--model', getClaudeModelFlag(modelId)],\n };\n }\n // Non-Anthropic direct providers: use claude CLI with the model name as-is.\n // The caller must set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN env vars.\n return {\n command: 'claude',\n args: ['--model', modelId],\n };\n}\n"],"mappings":";;;;;;;YAEyC;AAQzC,SAAgB,wBAAgC;AAC9C,yBAAO,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI;;AAGvD,SAAgB,aAAa,YAAkC;CAC7D,MAAM,YAAY,uBAAuB;CACzC,MAAM,aAAa,KAAK,aAAa,UAAU;AAE/C,WAAU,YAAY,EAAE,WAAW,MAAM,CAAC;CAE1C,MAAM,UAAoB,EAAE;AAE5B,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,CAAC,WAAW,UAAU,CAAE;EAE5B,MAAM,aAAa,SAAS,UAAU;AAMtC,SAAO,WALY,KAAK,YAAY,WAAW,EAKjB;GAC5B,WAAW;GACX,SAAS,QAAQ,CAAC,UAAU,IAAI,CAAC,gBAAgB;GAClD,CAAC;AACF,UAAQ,KAAK,WAAW;;AAG1B,QAAO;EACL;EACA,MAAM;EACN;EACD;;AAGH,SAAgB,cAA4B;AAC1C,KAAI,CAAC,WAAW,YAAY,CAAE,QAAO,EAAE;AAIvC,QAFgB,YAAY,aAAa,EAAE,eAAe,MAAM,CAAC,CAG9D,QAAQ,MAAM,EAAE,aAAa,CAAC,CAC9B,KAAK,MAAM;EACV,MAAM,aAAa,KAAK,aAAa,EAAE,KAAK;EAC5C,MAAM,WAAW,YAAY,WAAW;AAExC,SAAO;GACL,WAAW,EAAE;GACb,MAAM;GACN,SAAS;GACV;GACD,CACD,MAAM,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,UAAU,CAAC;;AAG3D,SAAgB,cAAc,WAAmB,YAA0C;CACzF,MAAM,aAAa,KAAK,aAAa,UAAU;AAE/C,KAAI,CAAC,WAAW,WAAW,CACzB,OAAM,IAAI,MAAM,qBAAqB,YAAY;CAGnD,MAAM,WAAW,YAAY,YAAY,EAAE,eAAe,MAAM,CAAC;AAEjE,MAAK,MAAM,SAAS,UAAU;AAC5B,MAAI,CAAC,MAAM,aAAa,CAAE;EAE1B,MAAM,aAAa,KAAK,YAAY,MAAM,KAAK;EAC/C,MAAM,aAAa,WAAW,MAAM;AAEpC,MAAI,CAAC,WAAY;AAGjB,MAAI,WAAW,WAAW,CACxB,QAAO,YAAY,EAAE,WAAW,MAAM,CAAC;AAGzC,SAAO,YAAY,YAAY,EAAE,WAAW,MAAM,CAAC;;;AAIvD,SAAgB,gBAAgB,YAAoB,IAAY;CAC9D,MAAM,UAAU,aAAa;AAE7B,KAAI,QAAQ,UAAU,UAAW,QAAO;CAExC,MAAM,WAAW,QAAQ,MAAM,UAAU;CACzC,IAAI,UAAU;AAEd,MAAK,MAAM,UAAU,UAAU;AAC7B,SAAO,OAAO,MAAM,EAAE,WAAW,MAAM,CAAC;AACxC;;AAGF,QAAO;;;;YChGW;eAMG;aACsB;;;;AAkC7C,SAAgB,oBAAoB,YAA6B;AAC/D,KAAI,CAAC,WAAW,WAAW,CAAE,QAAO;AAEpC,KAAI;AAEF,MAAI,CADU,UAAU,WAAW,CACxB,gBAAgB,CAAE,QAAO;AAIpC,SAFmB,aAAa,WAAW,CAEzB,SAAS,cAAc;SACnC;AACN,SAAO;;;;;;;;;;;;;;;;;AAwBX,SAAgB,8BAA+C;CAC7D,MAAM,YAAY,KAAK,SAAS,EAAE,UAAU;CAC5C,MAAM,SAA0B;EAC9B,iBAAiB,EAAE;EACnB,sBAAsB,EAAE;EACxB,QAAQ,EAAE;EACX;CAID,MAAM,+BAAe,IAAI,KAAa;CACtC,MAAM,UAAU,gBAAgB;AAChC,KAAI,QACF,MAAK,MAAM,UAAU;EAAC;EAAU;EAAY;EAAS,EAAE;EACrD,MAAM,aAAa,KAAK,SAAS,WAAW,OAAO;AACnD,MAAI,WAAW,WAAW,CACxB,KAAI;AACF,QAAK,MAAM,SAAS,YAAY,WAAW,CACzC,cAAa,IAAI,GAAG,OAAO,GAAG,QAAQ;UAElC;;AAOd,MAAK,MAAM,UAAU;EAAC;EAAU;EAAY;EAAS,EAAE;EACrD,MAAM,MAAM,KAAK,WAAW,OAAO;AACnC,MAAI,CAAC,WAAW,IAAI,CAAE;AAEtB,MAAI;GACF,MAAM,UAAU,YAAY,IAAI;AAChC,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,YAAY,KAAK,KAAK,MAAM;AAClC,QAAI;KACF,MAAM,QAAQ,UAAU,UAAU;AAClC,SAAI,MAAM,gBAAgB,EAAE;MAC1B,MAAM,aAAa,aAAa,UAAU;AAC1C,UAAI,WAAW,SAAS,cAAc,IAAI,WAAW,SAAS,iBAAiB,EAAE;AAC/E,kBAAW,UAAU;AACrB,cAAO,gBAAgB,KAAK,GAAG,OAAO,GAAG,QAAQ;YAGjD,QAAO,qBAAqB,KAAK,GAAG,OAAO,GAAG,QAAQ;gBAE/C,MAAM,aAAa,IAAI,aAAa,IAAI,GAAG,OAAO,GAAG,QAAQ,EAAE;AAIxE,aAAO,WAAW;OAAE,WAAW;OAAM,OAAO;OAAM,CAAC;AACnD,aAAO,gBAAgB,KAAK,GAAG,OAAO,GAAG,MAAM,eAAe;WAG9D,QAAO,qBAAqB,KAAK,GAAG,OAAO,GAAG,QAAQ;aAEjD,KAAU;AACjB,YAAO,OAAO,KAAK,GAAG,OAAO,GAAG,MAAM,IAAI,IAAI,UAAU;;;WAGrD,KAAU;AACjB,UAAO,OAAO,KAAK,GAAG,OAAO,IAAI,IAAI,UAAU;;;AAInD,QAAO;;;;;AAYT,SAAS,uBAAuB,QAAgB,MAAsB;AACpE,KAAI,CAAC,WAAW,OAAO,CAAE,QAAO;AAEhC,WAAU,MAAM,EAAE,WAAW,MAAM,CAAC;CACpC,IAAI,QAAQ;CAEZ,MAAM,UAAU,YAAY,QAAQ,EAAE,eAAe,MAAM,CAAC;AAC5D,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,UAAU,KAAK,QAAQ,MAAM,KAAK;EACxC,MAAM,UAAU,KAAK,MAAM,MAAM,KAAK;AACtC,MAAI,MAAM,aAAa,CACrB,UAAS,uBAAuB,SAAS,QAAQ;WACxC,MAAM,QAAQ,EAAE;AACzB,gBAAa,SAAS,QAAQ;AAC9B;;;AAGJ,QAAO;;;;;;;;;;;AAYT,SAAgB,eAAmC;CACjD,MAAM,SAA6B;EACjC,QAAQ;GAAE,QAAQ;GAAG,OAAO;GAAG;EAC/B,QAAQ;GAAE,QAAQ;GAAG,OAAO;GAAG;EAC/B,OAAO;GAAE,QAAQ;GAAG,OAAO;GAAG;EAC/B;AAGD,KAAI,WAAW,kBAAkB,EAAE;EACjC,MAAM,YAAY,YAAY,mBAAmB,EAAE,eAAe,MAAM,CAAC,CACtE,QAAQ,MAAM,EAAE,aAAa,CAAC;AAEjC,SAAO,OAAO,QAAQ,UAAU;AAChC,OAAK,MAAM,YAAY,WAAW;AAGhC,0BAFY,KAAK,mBAAmB,SAAS,KAAK,EACtC,KAAK,YAAY,SAAS,KAAK,CACX;AAChC,UAAO,OAAO;;;AAKlB,KAAI,WAAW,IAAI,WAAW,sBAAsB,EAAE;EACpD,MAAM,eAAe,YAAY,uBAAuB,EAAE,eAAe,MAAM,CAAC,CAC7E,QAAQ,MAAM,EAAE,aAAa,CAAC;AAEjC,OAAK,MAAM,YAAY,cAAc;AAGnC,0BAFY,KAAK,uBAAuB,SAAS,KAAK,EAC1C,KAAK,YAAY,SAAS,KAAK,CACX;AAChC,UAAO,OAAO;AACd,UAAO,OAAO;;;AAKlB,KAAI,WAAW,kBAAkB,EAAE;AACjC,YAAU,kBAAkB,EAAE,WAAW,MAAM,CAAC;EAChD,MAAM,SAAS,YAAY,mBAAmB,EAAE,eAAe,MAAM,CAAC,CACnE,QAAQ,UAAU,MAAM,QAAQ,IAAI,MAAM,KAAK,SAAS,MAAM,CAAC;AAElE,SAAO,OAAO,QAAQ,OAAO;AAC7B,OAAK,MAAM,SAAS,QAAQ;AAC1B,gBAAa,KAAK,mBAAmB,MAAM,KAAK,EAAE,KAAK,kBAAkB,MAAM,KAAK,CAAC;AACrF,UAAO,OAAO;;;AAKlB,KAAI,WAAW,iBAAiB,EAAE;EAChC,MAAM,YAAY,YAAY,kBAAkB,EAAE,eAAe,MAAM,CAAC,CACrE,QAAQ,UAAU,MAAM,QAAQ,CAAC;AAEpC,SAAO,MAAM,QAAQ,UAAU;AAC/B,OAAK,MAAM,QAAQ,WAAW;AAC5B,aAAU,iBAAiB,EAAE,WAAW,MAAM,CAAC;AAC/C,gBAAa,KAAK,kBAAkB,KAAK,KAAK,EAAE,KAAK,iBAAiB,KAAK,KAAK,CAAC;AACjF,UAAO,MAAM;;;AAUjB,eAAc,gBALG,2BACf,KAAK,YAAY,KAAK,EACtB;EAAC;EAAU;EAAqB;EAAQ,EACxC,aACD,CACsC;AAEvC,QAAO;;;;;;AAqBT,SAAgB,WAAqB;CACnC,MAAM,OAAiB;EACrB,QAAQ,EAAE;EACV,UAAU,EAAE;EACZ,QAAQ,EAAE;EACV,OAAO,EAAE;EACT,WAAW,EAAE;EACd;CAED,MAAM,cAAc,gBAAgB;AACpC,KAAI,CAAC,YAAa,QAAO;CAEzB,MAAM,aAAa,KAAK,aAAa,UAAU;CAE/C,MAAM,WAAW,aADI,KAAK,YAAY,4BAA4B,CACvB;CAG3C,MAAM,aAAa,mBAAmB,YAAY,UAAU;AAC5D,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,aAAa,KAAK,YAAY,KAAK,aAAa;EACtD,MAAM,SAAS,sBAAsB,YAAY,KAAK,cAAc,SAAS;AAC3D,OAAK,aAAa,MAAM,IAAI,CAAC,MAAM,KAAK;EAE1D,IAAI,aAAiC;AACrC,MAAI,OAAO,WAAW,SAAU,cAAa;WACpC,OAAO,WAAW,WAAY,cAAa;WAC3C,OAAO,WAAW,aAAc,cAAa;AAEtD,OAAK,OAAO,KAAK;GACf,MAAM,KAAK;GACX,YAAY,KAAK;GACjB,YAAY;GACZ,QAAQ;GACT,CAAC;;CAIJ,MAAM,aAAa,mBAAmB,kBAAkB,UAAU;AAClE,MAAK,MAAM,QAAQ,YAAY;EAC7B,MAAM,aAAa,KAAK,YAAY,KAAK,aAAa;EACtD,MAAM,SAAS,sBAAsB,YAAY,KAAK,cAAc,SAAS;EAE7E,IAAI,aAAiC;AACrC,MAAI,OAAO,WAAW,SAAU,cAAa;WACpC,OAAO,WAAW,WAAY,cAAa;WAC3C,OAAO,WAAW,aAAc,cAAa;AAEtD,OAAK,OAAO,KAAK;GACf,MAAM,KAAK;GACX,YAAY,KAAK;GACjB,YAAY;GACZ,QAAQ;GACT,CAAC;;CAIJ,MAAM,YAAY,mBAAmB,iBAAiB,SAAS;AAC/D,MAAK,MAAM,QAAQ,WAAW;EAC5B,MAAM,aAAa,KAAK,YAAY,KAAK,aAAa;EACtD,MAAM,SAAS,sBAAsB,YAAY,KAAK,cAAc,SAAS;EAE7E,IAAI,aAAiC;AACrC,MAAI,OAAO,WAAW,SAAU,cAAa;WACpC,OAAO,WAAW,WAAY,cAAa;WAC3C,OAAO,WAAW,aAAc,cAAa;AAEtD,OAAK,MAAM,KAAK;GACd,MAAM,KAAK;GACX,YAAY,KAAK;GACjB,YAAY;GACZ,QAAQ;GACT,CAAC;;AAGJ,QAAO;;;;;;AAqBT,SAAgB,YAAY,UAAuB,EAAE,EAAc;CACjE,MAAM,SAAqB;EACzB,SAAS,EAAE;EACX,SAAS,EAAE;EACX,SAAS,EAAE;EACX,WAAW,EAAE;EACb,OAAO,EAAE;EACV;CAED,MAAM,cAAc,gBAAgB;AACpC,KAAI,CAAC,YACH,QAAO;CAGT,MAAM,aAAa,KAAK,aAAa,UAAU;CAC/C,MAAM,eAAe,KAAK,YAAY,4BAA4B;CAClE,MAAM,WAAW,aAAa,aAAa;CAG3C,MAAM,WAAW;EACf,GAAG,mBAAmB,YAAY,UAAU;EAC5C,GAAG,mBAAmB,kBAAkB,UAAU;EAClD,GAAG,mBAAmB,iBAAiB,SAAS;EACjD;AAED,MAAK,MAAM,QAAQ,UAAU;EAC3B,MAAM,aAAa,KAAK,YAAY,KAAK,aAAa;AAGtD,UAFe,sBAAsB,YAAY,KAAK,cAAc,SAAS,CAE9D,QAAf;GACE,KAAK,OAAO;AAEV,cAAU,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,iBAAa,KAAK,cAAc,WAAW;IAC3C,MAAM,OAAO,SAAS,WAAW;AACjC,qBAAiB,UAAU,KAAK,cAAc,MAAM,aAAa;AACjE,WAAO,QAAQ,KAAK,KAAK,aAAa;AACtC;;GAGF,KAAK,UAAU;AAEb,cAAU,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,iBAAa,KAAK,cAAc,WAAW;IAC3C,MAAM,OAAO,SAAS,WAAW;AACjC,qBAAiB,UAAU,KAAK,cAAc,MAAM,aAAa;AACjE,WAAO,QAAQ,KAAK,KAAK,aAAa;AACtC;;GAGF,KAAK;AAEH,QAAI,QAAQ,KACV,QAAO,MAAM,KAAK;KAChB,MAAM,KAAK;KACX,eAAe,aAAa,KAAK,cAAc,QAAQ;KACvD,eAAe,aAAa,YAAY,QAAQ;KACjD,CAAC;AAGJ,QAAI,QAAQ,OAAO;AACjB,eAAU,QAAQ,WAAW,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,kBAAa,KAAK,cAAc,WAAW;KAC3C,MAAM,OAAO,SAAS,WAAW;AACjC,sBAAiB,UAAU,KAAK,cAAc,MAAM,aAAa;AACjE,YAAO,QAAQ,KAAK,KAAK,aAAa;UAEtC,QAAO,UAAU,KAAK,KAAK,aAAa;AAE1C;GAGF,KAAK;AAEH,WAAO,QAAQ,KAAK,KAAK,aAAa;AACtC;;;AAMN,eAAc,cAAc,SAAS;AAErC,QAAO;;;;;AAgBT,SAAgB,gBAA4B;CAC1C,MAAM,QAAoB,EAAE;AAE5B,KAAI,CAAC,WAAW,mBAAmB,CACjC,QAAO;CAKT,MAAM,UAAU,YAAY,oBAAoB,EAAE,eAAe,MAAM,CAAC,CACrE,QAAQ,UAAU,MAAM,QAAQ,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,KAC1D,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,MAAM,KAAK,SAAS,MAAM,EAAE;AAEjE,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,aAAa,KAAK,oBAAoB,OAAO,KAAK;EACxD,MAAM,aAAa,KAAK,SAAS,OAAO,KAAK;EAE7C,IAAI,SAA6B;AAEjC,MAAI,WAAW,WAAW,CAGxB,UAAS;AAGX,QAAM,KAAK;GAAE,MAAM,OAAO;GAAM;GAAY;GAAY;GAAQ,CAAC;;AAGnE,QAAO;;;;;AAMT,SAAgB,YAAoD;CAClE,MAAM,SAAS;EAAE,QAAQ,EAAE;EAAc,QAAQ,EAAE;EAAc;AAGjE,WAAU,SAAS,EAAE,WAAW,MAAM,CAAC;CAEvC,MAAM,QAAQ,eAAe;AAE7B,MAAK,MAAM,QAAQ,MACjB,KAAI;AACF,eAAa,KAAK,YAAY,KAAK,WAAW;AAC9C,YAAU,KAAK,YAAY,IAAM;AACjC,SAAO,OAAO,KAAK,KAAK,KAAK;UACtB,OAAO;AACd,SAAO,OAAO,KAAK,GAAG,KAAK,KAAK,IAAI,QAAQ;;AAIhD,QAAO;;;;;;AAOT,MAAM,qBAAsG,EAC1G,QAAQ;CACN,WAAW,KAAK,SAAS,EAAE,UAAU;CACrC,YAAY;CACZ,cAAc,KAAK,SAAS,EAAE,WAAW,gBAAgB;CAC1D,EAEF;;;;;;AAOD,SAAgB,iBAAyD;CACvE,MAAM,SAAS;EAAE,QAAQ,EAAE;EAAc,QAAQ,EAAE;EAAc;CAEjE,MAAM,eAAe,KAAK,oBAAoB,gBAAgB;AAC9D,KAAI,CAAC,WAAW,aAAa,CAC3B,QAAO;AAGT,MAAK,MAAM,CAAC,SAAS,WAAW,OAAO,QAAQ,mBAAmB,CAChE,KAAI;AAEF,YAAU,OAAO,WAAW,EAAE,WAAW,MAAM,CAAC;EAGhD,MAAM,eAAe,KAAK,OAAO,WAAW,OAAO,WAAW;AAC9D,eAAa,cAAc,aAAa;AACxC,YAAU,cAAc,IAAM;AAG9B,2BAAyB,OAAO,cAAc,aAAa;AAE3D,SAAO,OAAO,KAAK,QAAQ;UACpB,OAAO;AACd,SAAO,OAAO,KAAK,GAAG,QAAQ,IAAI,QAAQ;;AAI9C,QAAO;;;;;;AAOT,SAAS,yBAAyB,cAAsB,YAA0B;CAChF,IAAI,WAAgC,EAAE;AAEtC,KAAI,WAAW,aAAa,CAC1B,KAAI;AACF,aAAW,KAAK,MAAM,aAAa,cAAc,QAAQ,CAAC;SACpD;AAEN,aAAW,EAAE;;AAMjB,KADuB,SAAS,YAAY,YACrB,cAAc,SAAS,YAAY,SAAS,UACjE;AAGF,UAAS,aAAa;EACpB,MAAM;EACN,SAAS;EACT,SAAS;EACV;AAED,WAAU,QAAQ,aAAa,EAAE,EAAE,WAAW,MAAM,CAAC;AACrD,eAAc,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE,GAAG,MAAM,QAAQ;;;;YC7lBrC;AAgD3C,MAAM,mBAAmC;CACvC,QAAQ;EACN,aAAa;GACX,cAAc;GACd,YAAY;GACZ,aAAa;GACd;EACD,eAAe;EACf,YAAY;GACV,SAAS;GACT,QAAQ;GACR,QAAQ;GACR,SAAS;GACT,QAAQ;GACT;EACF;CACD,UAAU,EAAE;CACb;;;;;;AAOD,SAAS,UAA4B,UAAa,WAA0B;CAC1E,MAAM,SAAS,EAAE,GAAG,UAAU;AAE9B,MAAK,MAAM,OAAO,OAAO,KAAK,UAAU,EAAiB;EACvD,MAAM,aAAa,SAAS;EAC5B,MAAM,cAAc,UAAU;AAG9B,MAAI,gBAAgB,KAAA,EAAW;AAG/B,MACE,OAAO,eAAe,YACtB,eAAe,QACf,CAAC,MAAM,QAAQ,WAAW,IAC1B,OAAO,gBAAgB,YACvB,gBAAgB,QAChB,CAAC,MAAM,QAAQ,YAAY,CAE3B,QAAO,OAAO,UAAU,YAAY,YAAmB;MAGvD,QAAO,OAAO;;AAIlB,QAAO;;;;;;;AAQT,SAAgB,eAA+B;CAC7C,IAAI;AAEJ,KAAI,CAAC,WAAW,cAAc,CAC5B,YAAW,oBAAoB;KAE/B,KAAI;EACF,MAAM,UAAU,aAAa,eAAe,OAAO;AAEnD,aAAW,UAAU,kBADN,KAAK,MAAM,QAAQ,CACY;UACvC,OAAO;AACd,UAAQ,MAAM,yDAAyD;AACvE,aAAW,oBAAoB;;CAMnC,MAAM,aAA4B,EAAE;AACpC,KAAI,QAAQ,IAAI,eAAgB,YAAW,SAAS,QAAQ,IAAI;AAChE,KAAI,QAAQ,IAAI,eAAgB,YAAW,SAAS,QAAQ,IAAI;AAChE,KAAI,QAAQ,IAAI,YAAa,YAAW,MAAM,QAAQ,IAAI;AAC1D,KAAI,QAAQ,IAAI,aAAc,YAAW,OAAO,QAAQ,IAAI;AAG5D,UAAS,WAAW;EAClB,GAAG;EACH,GAAG,SAAS;EACb;AAED,QAAO;;;;;;AAOT,SAAgB,aAAa,UAAgC;AAE3D,eAAc,eADE,KAAK,UAAU,UAAU,MAAM,EAAE,EACX,OAAO;;;;;;AAO/C,SAAgB,iBAAiB,UAAyC;AAExE,KAAI,CAAC,SAAS,OACZ,QAAO;AAIT,KAAI,CAAC,SAAS,OAAO,YACnB,QAAO;CAET,MAAM,cAAc,SAAS,OAAO;AACpC,KAAI,CAAC,YAAY,gBAAgB,CAAC,YAAY,cAAc,CAAC,YAAY,YACvE,QAAO;AAIT,KAAI,CAAC,SAAS,OAAO,WACnB,QAAO;CAET,MAAM,aAAa,SAAS,OAAO;AAEnC,MAAK,MAAM,SAD+B;EAAC;EAAW;EAAU;EAAU;EAAW;EAAS,CAE5F,KAAI,CAAC,WAAW,OACd,QAAO,6BAA6B;AAKxC,KAAI,CAAC,SAAS,SACZ,QAAO;AAGT,QAAO;;;;;AAMT,SAAgB,qBAAqC;AACnD,QAAO,KAAK,MAAM,KAAK,UAAU,iBAAiB,CAAC;;;;;;AAOrD,SAAgB,mBAAmB,UAMjC;AAuBA,QAAO;EACL,WAvBwC;GACxC;GACA;GACA;GACD;EAoBC,QAlBkC,SAAS,SAAS,SAClD;GAAC;GAAiB;GAAoB;GAAU;GAAc,GAC9D,EAAE;EAiBJ,QAfkC,SAAS,SAAS,SAClD,CAAC,wBAAwB,yBAAyB,GAClD,EAAE;EAcJ,KAZ4B,SAAS,SAAS,MAC5C,CAAC,gBAAgB,GACjB,EAAE;EAWJ,MAT8B,SAAS,SAAS,OAC9C,CAAC,WAAW,YAAY,GACxB,EAAE;EAQL;;;;;;AAOH,SAAgB,iBAAiB,SAAoC;AACnE,QAAO,QAAQ,WAAW,UAAU;;;;;;AAOtC,SAAgB,mBAAmB,SAAmC;AAOpE,QANyC;EACvC,mBAAmB;EACnB,qBAAqB;EACrB,qBAAqB;EACrB,oBAAoB;EACrB,CACe,YAAY;;;;;;;AAQ9B,SAAgB,gBAAgB,SAAgE;AAC9F,KAAI,iBAAiB,QAAQ,CAC3B,QAAO;EACL,SAAS;EACT,MAAM,CAAC,WAAW,mBAAmB,QAAQ,CAAC;EAC/C;AAIH,QAAO;EACL,SAAS;EACT,MAAM,CAAC,WAAW,QAAQ;EAC3B"}
|
|
@@ -197,4 +197,4 @@ function getPendingSyncCount() {
|
|
|
197
197
|
//#endregion
|
|
198
198
|
export { isShadowed as a, needsSync as c, updateTrackerStatusCache as d, getUnsyncedHistory as i, removeShadowState as l, getPendingSyncCount as n, listShadowedIssues as o, getShadowState as r, markAsSynced as s, createShadowState as t, updateShadowState as u };
|
|
199
199
|
|
|
200
|
-
//# sourceMappingURL=shadow-state-
|
|
200
|
+
//# sourceMappingURL=shadow-state-CE3dQfll.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shadow-state-CFFHf05M.js","names":[],"sources":["../src/lib/shadow-state.ts"],"sourcesContent":["/**\n * Shadow State Storage Module\n *\n * Manages shadow state for issues - tracking status locally without updating\n * the issue tracker until explicitly synced.\n *\n * Storage Location: ~/.panopticon/shadow-state/\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { IssueState } from './tracker/interface.js';\n\n// Storage directory for shadow state files\nconst SHADOW_STATE_DIR = join(homedir(), '.panopticon', 'shadow-state');\n\n/**\n * Shadow history entry - tracks state transitions\n */\nexport interface ShadowHistoryEntry {\n /** Previous state */\n from: IssueState;\n /** New state */\n to: IssueState;\n /** When the transition occurred */\n at: string;\n /** Command that triggered the transition (e.g., \"pan work plan\", \"dashboard\") */\n by: string;\n /** Whether this transition was synced to the tracker */\n syncedToTracker: boolean;\n}\n\n/**\n * Canonical state for Kanban column placement\n */\nexport type CanonicalState = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';\n\n/**\n * Shadow state for an issue\n */\nexport interface ShadowState {\n /** Issue ID (e.g., \"MIN-123\") */\n issueId: string;\n /** Panopticon's view of the issue status */\n shadowStatus: IssueState;\n /** Target canonical state for Kanban column placement */\n targetCanonicalState?: CanonicalState;\n /** Last known tracker status (cached) */\n trackerStatus: IssueState;\n /** When tracker status was last fetched */\n trackerStatusUpdatedAt: string;\n /** When shadow mode was enabled for this issue */\n shadowedAt: string;\n /** When shadow state was last synced to tracker */\n syncedAt?: string;\n /** Audit trail of state transitions */\n history: ShadowHistoryEntry[];\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n success: boolean;\n /** The state that was synced */\n syncedState?: IssueState;\n /** Previous tracker state */\n previousState?: IssueState;\n /** Error message if sync failed */\n error?: string;\n /** Number of history entries marked as synced */\n entriesSynced?: number;\n}\n\n/**\n * Ensure the shadow state directory exists\n */\nfunction ensureShadowStateDir(): void {\n if (!existsSync(SHADOW_STATE_DIR)) {\n mkdirSync(SHADOW_STATE_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the file path for a shadow state file\n */\nfunction getShadowStatePath(issueId: string): string {\n // Normalize issue ID for filename (uppercase, replace special chars)\n const normalizedId = issueId.toUpperCase().replace(/[^A-Z0-9-]/g, '');\n return join(SHADOW_STATE_DIR, `${normalizedId}.json`);\n}\n\n/**\n * Get shadow state for an issue\n * @returns ShadowState or null if not shadowed\n */\nexport function getShadowState(issueId: string): ShadowState | null {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as ShadowState;\n } catch (error) {\n console.error(`Error reading shadow state for ${issueId}:`, error);\n return null;\n }\n}\n\n/**\n * Check if an issue is in shadow mode\n */\nexport function isShadowed(issueId: string): boolean {\n return getShadowState(issueId) !== null;\n}\n\n/**\n * Create a new shadow state for an issue\n */\nexport function createShadowState(\n issueId: string,\n initialTrackerStatus: IssueState = 'open',\n triggeredBy: string = 'unknown'\n): ShadowState {\n ensureShadowStateDir();\n\n const now = new Date().toISOString();\n\n const shadowState: ShadowState = {\n issueId: issueId.toUpperCase(),\n shadowStatus: initialTrackerStatus,\n trackerStatus: initialTrackerStatus,\n trackerStatusUpdatedAt: now,\n shadowedAt: now,\n history: [],\n };\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(shadowState, null, 2), 'utf-8');\n\n return shadowState;\n}\n\n/**\n * Update shadow state for an issue\n */\nexport function updateShadowState(\n issueId: string,\n newStatus: IssueState,\n triggeredBy: string,\n targetCanonicalState?: CanonicalState\n): ShadowState {\n ensureShadowStateDir();\n\n let state = getShadowState(issueId);\n\n // Create new shadow state if it doesn't exist\n if (!state) {\n state = {\n issueId: issueId.toUpperCase(),\n shadowStatus: newStatus,\n targetCanonicalState,\n trackerStatus: newStatus,\n trackerStatusUpdatedAt: new Date().toISOString(),\n shadowedAt: new Date().toISOString(),\n history: [],\n };\n }\n\n // Only record transition if status changed\n if (state.shadowStatus !== newStatus) {\n const transition: ShadowHistoryEntry = {\n from: state.shadowStatus,\n to: newStatus,\n at: new Date().toISOString(),\n by: triggeredBy,\n syncedToTracker: false,\n };\n\n state.history.push(transition);\n state.shadowStatus = newStatus;\n }\n\n // Always update target canonical state if provided\n if (targetCanonicalState) {\n state.targetCanonicalState = targetCanonicalState;\n }\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Update tracker status cache (refresh from tracker)\n */\nexport function updateTrackerStatusCache(\n issueId: string,\n trackerStatus: IssueState\n): ShadowState {\n const state = getShadowState(issueId);\n\n if (!state) {\n throw new Error(`Cannot update tracker status: ${issueId} is not in shadow mode`);\n }\n\n state.trackerStatus = trackerStatus;\n state.trackerStatusUpdatedAt = new Date().toISOString();\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Sync shadow state to tracker (mark as synced)\n * This is called after successfully updating the tracker\n */\nexport function markAsSynced(\n issueId: string,\n syncedState: IssueState,\n previousTrackerState?: IssueState\n): SyncResult {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n const now = new Date().toISOString();\n let entriesSynced = 0;\n\n // Mark all unsynced history entries as synced\n for (const entry of state.history) {\n if (!entry.syncedToTracker) {\n entry.syncedToTracker = true;\n entriesSynced++;\n }\n }\n\n // Update sync timestamp and tracker status\n state.syncedAt = now;\n state.trackerStatus = syncedState;\n state.trackerStatusUpdatedAt = now;\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return {\n success: true,\n syncedState,\n previousState: previousTrackerState,\n entriesSynced,\n };\n}\n\n/**\n * List all shadowed issues\n */\nexport function listShadowedIssues(): ShadowState[] {\n if (!existsSync(SHADOW_STATE_DIR)) {\n return [];\n }\n\n const files = readdirSync(SHADOW_STATE_DIR);\n const states: ShadowState[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n try {\n const content = readFileSync(join(SHADOW_STATE_DIR, file), 'utf-8');\n const state = JSON.parse(content) as ShadowState;\n states.push(state);\n } catch (error) {\n console.error(`Error reading shadow state file ${file}:`, error);\n }\n }\n\n // Sort by shadowedAt (newest first)\n return states.sort((a, b) =>\n new Date(b.shadowedAt).getTime() - new Date(a.shadowedAt).getTime()\n );\n}\n\n/**\n * Remove shadow state for an issue (unshadow)\n * @param syncFirst - If true, attempts to sync to tracker before removing\n */\nexport function removeShadowState(\n issueId: string,\n syncFirst: boolean = false\n): { success: boolean; error?: string; synced?: boolean } {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n try {\n // If syncFirst is true, we should have already synced by this point\n // This parameter is just for API clarity\n\n unlinkSync(filePath);\n return {\n success: true,\n synced: syncFirst,\n };\n } catch (error: any) {\n return {\n success: false,\n error: `Failed to remove shadow state: ${error.message}`,\n };\n }\n}\n\n/**\n * Get the display status for an issue\n * Returns shadow status with tracker status info if in shadow mode\n */\nexport function getDisplayStatus(\n issueId: string,\n trackerStatus: IssueState\n): {\n status: IssueState;\n isShadowed: boolean;\n trackerStatus?: IssueState;\n outOfSync?: boolean;\n} {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n status: trackerStatus,\n isShadowed: false,\n };\n }\n\n return {\n status: state.shadowStatus,\n isShadowed: true,\n trackerStatus: state.trackerStatus,\n outOfSync: state.shadowStatus !== state.trackerStatus,\n };\n}\n\n/**\n * Check if an issue needs to be synced to tracker\n * (shadow status differs from tracker status)\n */\nexport function needsSync(issueId: string): boolean {\n const state = getShadowState(issueId);\n\n if (!state) {\n return false;\n }\n\n return state.shadowStatus !== state.trackerStatus;\n}\n\n/**\n * Get unsynced history entries for an issue\n */\nexport function getUnsyncedHistory(issueId: string): ShadowHistoryEntry[] {\n const state = getShadowState(issueId);\n\n if (!state) {\n return [];\n }\n\n return state.history.filter(entry => !entry.syncedToTracker);\n}\n\n/**\n * Get the count of issues that need sync\n */\nexport function getPendingSyncCount(): number {\n return listShadowedIssues().filter(state =>\n state.shadowStatus !== state.trackerStatus\n ).length;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shadow-state-CE3dQfll.js","names":[],"sources":["../src/lib/shadow-state.ts"],"sourcesContent":["/**\n * Shadow State Storage Module\n *\n * Manages shadow state for issues - tracking status locally without updating\n * the issue tracker until explicitly synced.\n *\n * Storage Location: ~/.panopticon/shadow-state/\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { IssueState } from './tracker/interface.js';\n\n// Storage directory for shadow state files\nconst SHADOW_STATE_DIR = join(homedir(), '.panopticon', 'shadow-state');\n\n/**\n * Shadow history entry - tracks state transitions\n */\nexport interface ShadowHistoryEntry {\n /** Previous state */\n from: IssueState;\n /** New state */\n to: IssueState;\n /** When the transition occurred */\n at: string;\n /** Command that triggered the transition (e.g., \"pan work plan\", \"dashboard\") */\n by: string;\n /** Whether this transition was synced to the tracker */\n syncedToTracker: boolean;\n}\n\n/**\n * Canonical state for Kanban column placement\n */\nexport type CanonicalState = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';\n\n/**\n * Shadow state for an issue\n */\nexport interface ShadowState {\n /** Issue ID (e.g., \"MIN-123\") */\n issueId: string;\n /** Panopticon's view of the issue status */\n shadowStatus: IssueState;\n /** Target canonical state for Kanban column placement */\n targetCanonicalState?: CanonicalState;\n /** Last known tracker status (cached) */\n trackerStatus: IssueState;\n /** When tracker status was last fetched */\n trackerStatusUpdatedAt: string;\n /** When shadow mode was enabled for this issue */\n shadowedAt: string;\n /** When shadow state was last synced to tracker */\n syncedAt?: string;\n /** Audit trail of state transitions */\n history: ShadowHistoryEntry[];\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n success: boolean;\n /** The state that was synced */\n syncedState?: IssueState;\n /** Previous tracker state */\n previousState?: IssueState;\n /** Error message if sync failed */\n error?: string;\n /** Number of history entries marked as synced */\n entriesSynced?: number;\n}\n\n/**\n * Ensure the shadow state directory exists\n */\nfunction ensureShadowStateDir(): void {\n if (!existsSync(SHADOW_STATE_DIR)) {\n mkdirSync(SHADOW_STATE_DIR, { recursive: true });\n }\n}\n\n/**\n * Get the file path for a shadow state file\n */\nfunction getShadowStatePath(issueId: string): string {\n // Normalize issue ID for filename (uppercase, replace special chars)\n const normalizedId = issueId.toUpperCase().replace(/[^A-Z0-9-]/g, '');\n return join(SHADOW_STATE_DIR, `${normalizedId}.json`);\n}\n\n/**\n * Get shadow state for an issue\n * @returns ShadowState or null if not shadowed\n */\nexport function getShadowState(issueId: string): ShadowState | null {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as ShadowState;\n } catch (error) {\n console.error(`Error reading shadow state for ${issueId}:`, error);\n return null;\n }\n}\n\n/**\n * Check if an issue is in shadow mode\n */\nexport function isShadowed(issueId: string): boolean {\n return getShadowState(issueId) !== null;\n}\n\n/**\n * Create a new shadow state for an issue\n */\nexport function createShadowState(\n issueId: string,\n initialTrackerStatus: IssueState = 'open',\n triggeredBy: string = 'unknown'\n): ShadowState {\n ensureShadowStateDir();\n\n const now = new Date().toISOString();\n\n const shadowState: ShadowState = {\n issueId: issueId.toUpperCase(),\n shadowStatus: initialTrackerStatus,\n trackerStatus: initialTrackerStatus,\n trackerStatusUpdatedAt: now,\n shadowedAt: now,\n history: [],\n };\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(shadowState, null, 2), 'utf-8');\n\n return shadowState;\n}\n\n/**\n * Update shadow state for an issue\n */\nexport function updateShadowState(\n issueId: string,\n newStatus: IssueState,\n triggeredBy: string,\n targetCanonicalState?: CanonicalState\n): ShadowState {\n ensureShadowStateDir();\n\n let state = getShadowState(issueId);\n\n // Create new shadow state if it doesn't exist\n if (!state) {\n state = {\n issueId: issueId.toUpperCase(),\n shadowStatus: newStatus,\n targetCanonicalState,\n trackerStatus: newStatus,\n trackerStatusUpdatedAt: new Date().toISOString(),\n shadowedAt: new Date().toISOString(),\n history: [],\n };\n }\n\n // Only record transition if status changed\n if (state.shadowStatus !== newStatus) {\n const transition: ShadowHistoryEntry = {\n from: state.shadowStatus,\n to: newStatus,\n at: new Date().toISOString(),\n by: triggeredBy,\n syncedToTracker: false,\n };\n\n state.history.push(transition);\n state.shadowStatus = newStatus;\n }\n\n // Always update target canonical state if provided\n if (targetCanonicalState) {\n state.targetCanonicalState = targetCanonicalState;\n }\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Update tracker status cache (refresh from tracker)\n */\nexport function updateTrackerStatusCache(\n issueId: string,\n trackerStatus: IssueState\n): ShadowState {\n const state = getShadowState(issueId);\n\n if (!state) {\n throw new Error(`Cannot update tracker status: ${issueId} is not in shadow mode`);\n }\n\n state.trackerStatus = trackerStatus;\n state.trackerStatusUpdatedAt = new Date().toISOString();\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return state;\n}\n\n/**\n * Sync shadow state to tracker (mark as synced)\n * This is called after successfully updating the tracker\n */\nexport function markAsSynced(\n issueId: string,\n syncedState: IssueState,\n previousTrackerState?: IssueState\n): SyncResult {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n const now = new Date().toISOString();\n let entriesSynced = 0;\n\n // Mark all unsynced history entries as synced\n for (const entry of state.history) {\n if (!entry.syncedToTracker) {\n entry.syncedToTracker = true;\n entriesSynced++;\n }\n }\n\n // Update sync timestamp and tracker status\n state.syncedAt = now;\n state.trackerStatus = syncedState;\n state.trackerStatusUpdatedAt = now;\n\n const filePath = getShadowStatePath(issueId);\n writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');\n\n return {\n success: true,\n syncedState,\n previousState: previousTrackerState,\n entriesSynced,\n };\n}\n\n/**\n * List all shadowed issues\n */\nexport function listShadowedIssues(): ShadowState[] {\n if (!existsSync(SHADOW_STATE_DIR)) {\n return [];\n }\n\n const files = readdirSync(SHADOW_STATE_DIR);\n const states: ShadowState[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n try {\n const content = readFileSync(join(SHADOW_STATE_DIR, file), 'utf-8');\n const state = JSON.parse(content) as ShadowState;\n states.push(state);\n } catch (error) {\n console.error(`Error reading shadow state file ${file}:`, error);\n }\n }\n\n // Sort by shadowedAt (newest first)\n return states.sort((a, b) =>\n new Date(b.shadowedAt).getTime() - new Date(a.shadowedAt).getTime()\n );\n}\n\n/**\n * Remove shadow state for an issue (unshadow)\n * @param syncFirst - If true, attempts to sync to tracker before removing\n */\nexport function removeShadowState(\n issueId: string,\n syncFirst: boolean = false\n): { success: boolean; error?: string; synced?: boolean } {\n const filePath = getShadowStatePath(issueId);\n\n if (!existsSync(filePath)) {\n return {\n success: false,\n error: `Issue ${issueId} is not in shadow mode`,\n };\n }\n\n try {\n // If syncFirst is true, we should have already synced by this point\n // This parameter is just for API clarity\n\n unlinkSync(filePath);\n return {\n success: true,\n synced: syncFirst,\n };\n } catch (error: any) {\n return {\n success: false,\n error: `Failed to remove shadow state: ${error.message}`,\n };\n }\n}\n\n/**\n * Get the display status for an issue\n * Returns shadow status with tracker status info if in shadow mode\n */\nexport function getDisplayStatus(\n issueId: string,\n trackerStatus: IssueState\n): {\n status: IssueState;\n isShadowed: boolean;\n trackerStatus?: IssueState;\n outOfSync?: boolean;\n} {\n const state = getShadowState(issueId);\n\n if (!state) {\n return {\n status: trackerStatus,\n isShadowed: false,\n };\n }\n\n return {\n status: state.shadowStatus,\n isShadowed: true,\n trackerStatus: state.trackerStatus,\n outOfSync: state.shadowStatus !== state.trackerStatus,\n };\n}\n\n/**\n * Check if an issue needs to be synced to tracker\n * (shadow status differs from tracker status)\n */\nexport function needsSync(issueId: string): boolean {\n const state = getShadowState(issueId);\n\n if (!state) {\n return false;\n }\n\n return state.shadowStatus !== state.trackerStatus;\n}\n\n/**\n * Get unsynced history entries for an issue\n */\nexport function getUnsyncedHistory(issueId: string): ShadowHistoryEntry[] {\n const state = getShadowState(issueId);\n\n if (!state) {\n return [];\n }\n\n return state.history.filter(entry => !entry.syncedToTracker);\n}\n\n/**\n * Get the count of issues that need sync\n */\nexport function getPendingSyncCount(): number {\n return listShadowedIssues().filter(state =>\n state.shadowStatus !== state.trackerStatus\n ).length;\n}\n"],"mappings":";;;;;;;;;;;;AAeA,MAAM,mBAAmB,KAAK,SAAS,EAAE,eAAe,eAAe;;;;AA+DvE,SAAS,uBAA6B;AACpC,KAAI,CAAC,WAAW,iBAAiB,CAC/B,WAAU,kBAAkB,EAAE,WAAW,MAAM,CAAC;;;;;AAOpD,SAAS,mBAAmB,SAAyB;AAGnD,QAAO,KAAK,kBAAkB,GADT,QAAQ,aAAa,CAAC,QAAQ,eAAe,GAAG,CACvB,OAAO;;;;;;AAOvD,SAAgB,eAAe,SAAqC;CAClE,MAAM,WAAW,mBAAmB,QAAQ;AAE5C,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO;AAGT,KAAI;EACF,MAAM,UAAU,aAAa,UAAU,QAAQ;AAC/C,SAAO,KAAK,MAAM,QAAQ;UACnB,OAAO;AACd,UAAQ,MAAM,kCAAkC,QAAQ,IAAI,MAAM;AAClE,SAAO;;;;;;AAOX,SAAgB,WAAW,SAA0B;AACnD,QAAO,eAAe,QAAQ,KAAK;;;;;AAMrC,SAAgB,kBACd,SACA,uBAAmC,QACnC,cAAsB,WACT;AACb,uBAAsB;CAEtB,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;CAEpC,MAAM,cAA2B;EAC/B,SAAS,QAAQ,aAAa;EAC9B,cAAc;EACd,eAAe;EACf,wBAAwB;EACxB,YAAY;EACZ,SAAS,EAAE;EACZ;AAGD,eADiB,mBAAmB,QAAQ,EACpB,KAAK,UAAU,aAAa,MAAM,EAAE,EAAE,QAAQ;AAEtE,QAAO;;;;;AAMT,SAAgB,kBACd,SACA,WACA,aACA,sBACa;AACb,uBAAsB;CAEtB,IAAI,QAAQ,eAAe,QAAQ;AAGnC,KAAI,CAAC,MACH,SAAQ;EACN,SAAS,QAAQ,aAAa;EAC9B,cAAc;EACd;EACA,eAAe;EACf,yCAAwB,IAAI,MAAM,EAAC,aAAa;EAChD,6BAAY,IAAI,MAAM,EAAC,aAAa;EACpC,SAAS,EAAE;EACZ;AAIH,KAAI,MAAM,iBAAiB,WAAW;EACpC,MAAM,aAAiC;GACrC,MAAM,MAAM;GACZ,IAAI;GACJ,qBAAI,IAAI,MAAM,EAAC,aAAa;GAC5B,IAAI;GACJ,iBAAiB;GAClB;AAED,QAAM,QAAQ,KAAK,WAAW;AAC9B,QAAM,eAAe;;AAIvB,KAAI,qBACF,OAAM,uBAAuB;AAI/B,eADiB,mBAAmB,QAAQ,EACpB,KAAK,UAAU,OAAO,MAAM,EAAE,EAAE,QAAQ;AAEhE,QAAO;;;;;AAMT,SAAgB,yBACd,SACA,eACa;CACb,MAAM,QAAQ,eAAe,QAAQ;AAErC,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,iCAAiC,QAAQ,wBAAwB;AAGnF,OAAM,gBAAgB;AACtB,OAAM,0CAAyB,IAAI,MAAM,EAAC,aAAa;AAGvD,eADiB,mBAAmB,QAAQ,EACpB,KAAK,UAAU,OAAO,MAAM,EAAE,EAAE,QAAQ;AAEhE,QAAO;;;;;;AAOT,SAAgB,aACd,SACA,aACA,sBACY;CACZ,MAAM,QAAQ,eAAe,QAAQ;AAErC,KAAI,CAAC,MACH,QAAO;EACL,SAAS;EACT,OAAO,SAAS,QAAQ;EACzB;CAGH,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;CACpC,IAAI,gBAAgB;AAGpB,MAAK,MAAM,SAAS,MAAM,QACxB,KAAI,CAAC,MAAM,iBAAiB;AAC1B,QAAM,kBAAkB;AACxB;;AAKJ,OAAM,WAAW;AACjB,OAAM,gBAAgB;AACtB,OAAM,yBAAyB;AAG/B,eADiB,mBAAmB,QAAQ,EACpB,KAAK,UAAU,OAAO,MAAM,EAAE,EAAE,QAAQ;AAEhE,QAAO;EACL,SAAS;EACT;EACA,eAAe;EACf;EACD;;;;;AAMH,SAAgB,qBAAoC;AAClD,KAAI,CAAC,WAAW,iBAAiB,CAC/B,QAAO,EAAE;CAGX,MAAM,QAAQ,YAAY,iBAAiB;CAC3C,MAAM,SAAwB,EAAE;AAEhC,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAK,SAAS,QAAQ,CAAE;AAE7B,MAAI;GACF,MAAM,UAAU,aAAa,KAAK,kBAAkB,KAAK,EAAE,QAAQ;GACnE,MAAM,QAAQ,KAAK,MAAM,QAAQ;AACjC,UAAO,KAAK,MAAM;WACX,OAAO;AACd,WAAQ,MAAM,mCAAmC,KAAK,IAAI,MAAM;;;AAKpE,QAAO,OAAO,MAAM,GAAG,MACrB,IAAI,KAAK,EAAE,WAAW,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,WAAW,CAAC,SAAS,CACpE;;;;;;AAOH,SAAgB,kBACd,SACA,YAAqB,OACmC;CACxD,MAAM,WAAW,mBAAmB,QAAQ;AAE5C,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO;EACL,SAAS;EACT,OAAO,SAAS,QAAQ;EACzB;AAGH,KAAI;AAIF,aAAW,SAAS;AACpB,SAAO;GACL,SAAS;GACT,QAAQ;GACT;UACM,OAAY;AACnB,SAAO;GACL,SAAS;GACT,OAAO,kCAAkC,MAAM;GAChD;;;;;;;AAsCL,SAAgB,UAAU,SAA0B;CAClD,MAAM,QAAQ,eAAe,QAAQ;AAErC,KAAI,CAAC,MACH,QAAO;AAGT,QAAO,MAAM,iBAAiB,MAAM;;;;;AAMtC,SAAgB,mBAAmB,SAAuC;CACxE,MAAM,QAAQ,eAAe,QAAQ;AAErC,KAAI,CAAC,MACH,QAAO,EAAE;AAGX,QAAO,MAAM,QAAQ,QAAO,UAAS,CAAC,MAAM,gBAAgB;;;;;AAM9D,SAAgB,sBAA8B;AAC5C,QAAO,oBAAoB,CAAC,QAAO,UACjC,MAAM,iBAAiB,MAAM,cAC9B,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __esmMin } from "./chunk-ruWRV7i3.js";
|
|
2
|
-
import {
|
|
3
|
-
import { c as getProject, p as init_projects } from "./projects-
|
|
4
|
-
import { n as init_work_type_router, t as getModelId } from "./work-type-router-
|
|
5
|
-
import { mt as getRecentRunLogs, yt as init_specialist_logs } from "./specialists-
|
|
2
|
+
import { G as init_paths, W as getPanopticonHome } from "./paths-CDJ_HsbN.js";
|
|
3
|
+
import { c as getProject, p as init_projects } from "./projects-Bk-5QhFQ.js";
|
|
4
|
+
import { n as init_work_type_router, t as getModelId } from "./work-type-router-CHjciPyS.js";
|
|
5
|
+
import { mt as getRecentRunLogs, yt as init_specialist_logs } from "./specialists-D7Kj5o6s.js";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { exec } from "child_process";
|
|
@@ -112,7 +112,7 @@ async function generateContextDigest(projectKey, specialistType, options = {}) {
|
|
|
112
112
|
if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
|
|
113
113
|
const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);
|
|
114
114
|
writeFileSync(promptFile, prompt, "utf-8");
|
|
115
|
-
const { getProviderEnvForModel } = await import("./agents-
|
|
115
|
+
const { getProviderEnvForModel } = await import("./agents-D_2oRFVf.js");
|
|
116
116
|
const providerEnv = getProviderEnvForModel(model);
|
|
117
117
|
const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
118
118
|
const { stdout, stderr } = await execAsync(`${envPrefix ? envPrefix + " " : ""}claude --dangerously-skip-permissions --model ${model} "$(cat '${promptFile}')"`, {
|
|
@@ -232,4 +232,4 @@ __esmMin((() => {
|
|
|
232
232
|
}))();
|
|
233
233
|
export { loadContextDigest, scheduleDigestGeneration };
|
|
234
234
|
|
|
235
|
-
//# sourceMappingURL=specialist-context-
|
|
235
|
+
//# sourceMappingURL=specialist-context-BAUWL1Fl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specialist-context-BdNFsfMG.js","names":[],"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProject(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt (include provider env vars for non-Anthropic models)\n const { getProviderEnvForModel } = await import('../agents.js');\n const providerEnv = getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude --dangerously-skip-permissions --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, error.message);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProject(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return generateContextDigest(projectKey, specialistType, { force: true });\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n generateContextDigest(projectKey, specialistType).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"specialist-context-BAUWL1Fl.js","names":[],"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.panopticon/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { getPanopticonHome } from '../paths.js';\nimport { getRecentRunLogs, type RunLogEntry } from './specialist-logs.js';\nimport { getProject } from '../projects.js';\nimport { getModelId } from '../work-type-router.js';\n\nconst execAsync = promisify(exec);\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getPanopticonHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProject(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProject(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n // Fall back to specialist's model\n try {\n const workTypeId = `specialist-${specialistType}` as any;\n return getModelId(workTypeId);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}\n\n/**\n * Generate a context digest from recent runs using AI\n *\n * Creates an AI-generated summary of recent specialist runs to provide\n * context for the next run. This includes patterns, learnings, and common issues.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param options - Generation options\n * @returns Generated digest or null if generation failed\n */\nexport async function generateContextDigest(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[specialist-context] Generating digest for ${projectKey}/${specialistType} using ${model}...`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getPanopticonHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt (include provider env vars for non-Anthropic models)\n const { getProviderEnvForModel } = await import('../agents.js');\n const providerEnv = getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude --dangerously-skip-permissions --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[specialist-context] Claude stderr:`, stderr);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[specialist-context] Empty digest generated`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[specialist-context] Generated digest (${digest.length} chars)`);\n return digest;\n } catch (error: any) {\n console.error(`[specialist-context] Failed to generate digest:`, error.message);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProject(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}\n\n/**\n * Regenerate the context digest\n *\n * Forces regeneration even if a digest already exists.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Generated digest or null if generation failed\n */\nexport async function regenerateContextDigest(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return generateContextDigest(projectKey, specialistType, { force: true });\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n generateContextDigest(projectKey, specialistType).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { St as parseLogMetadata, dt as createRunLog, ft as finalizeRunLog, gt as getRunLogPath, ht as getRunLog, lt as cleanupAllLogs, ut as cleanupOldLogs, xt as listRunLogs, yt as init_specialist_logs } from "./specialists-
|
|
1
|
+
import { St as parseLogMetadata, dt as createRunLog, ft as finalizeRunLog, gt as getRunLogPath, ht as getRunLog, lt as cleanupAllLogs, ut as cleanupOldLogs, xt as listRunLogs, yt as init_specialist_logs } from "./specialists-D7Kj5o6s.js";
|
|
2
2
|
init_specialist_logs();
|
|
3
3
|
export { cleanupAllLogs, cleanupOldLogs, createRunLog, finalizeRunLog, getRunLog, getRunLogPath, listRunLogs, parseLogMetadata };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { I as isRunning, O as getTmuxSessionName, Y as spawnEphemeralSpecialist, Z as submitToSpecialistQueue, f as getAllProjectSpecialistStatuses, j as init_specialists, r as checkSpecialistQueue, x as getProjectSpecialistMetadata } from "./specialists-
|
|
1
|
+
import { I as isRunning, O as getTmuxSessionName, Y as spawnEphemeralSpecialist, Z as submitToSpecialistQueue, f as getAllProjectSpecialistStatuses, j as init_specialists, r as checkSpecialistQueue, x as getProjectSpecialistMetadata } from "./specialists-D7Kj5o6s.js";
|
|
2
2
|
init_specialists();
|
|
3
3
|
export { checkSpecialistQueue, getAllProjectSpecialistStatuses, getProjectSpecialistMetadata, getTmuxSessionName, isRunning, spawnEphemeralSpecialist, submitToSpecialistQueue };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { i as __toCommonJS, n as __exportAll, t as __esmMin } from "./chunk-ruWRV7i3.js";
|
|
2
|
-
import {
|
|
3
|
-
import { a as init_config, i as getDevrootPath } from "./config-
|
|
4
|
-
import { _ as projects_exports, c as getProject, p as init_projects } from "./projects-
|
|
5
|
-
import { f as waitForClaudePrompt, n as capturePaneAsync, o as init_tmux, r as confirmDelivery, u as sendKeysAsync } from "./tmux-
|
|
6
|
-
import { c as popFromHook, l as pushToHook, n as init_work_type_router, r as checkHook, s as init_hooks, t as getModelId } from "./work-type-router-
|
|
7
|
-
import { n as init_config_yaml, r as loadConfig } from "./config-yaml-
|
|
8
|
-
import { a as getProviderForModel, i as getProviderEnv, n as clearCredentialFileAuth, s as init_providers, u as setupCredentialFileAuth } from "./providers-
|
|
9
|
-
import { n as notifyPipeline, t as init_pipeline_notifier } from "./pipeline-notifier-
|
|
2
|
+
import { G as init_paths, W as getPanopticonHome, b as PANOPTICON_HOME, h as COSTS_DIR } from "./paths-CDJ_HsbN.js";
|
|
3
|
+
import { a as init_config, i as getDevrootPath } from "./config-BQNKsi9G.js";
|
|
4
|
+
import { _ as projects_exports, c as getProject, p as init_projects } from "./projects-Bk-5QhFQ.js";
|
|
5
|
+
import { f as waitForClaudePrompt, n as capturePaneAsync, o as init_tmux, r as confirmDelivery, u as sendKeysAsync } from "./tmux-D6Ah4I8z.js";
|
|
6
|
+
import { c as popFromHook, l as pushToHook, n as init_work_type_router, r as checkHook, s as init_hooks, t as getModelId } from "./work-type-router-CHjciPyS.js";
|
|
7
|
+
import { n as init_config_yaml, r as loadConfig } from "./config-yaml-DGbLSMCa.js";
|
|
8
|
+
import { a as getProviderForModel, i as getProviderEnv, n as clearCredentialFileAuth, s as init_providers, u as setupCredentialFileAuth } from "./providers-DSU1vfQF.js";
|
|
9
|
+
import { n as notifyPipeline, t as init_pipeline_notifier } from "./pipeline-notifier-XgDdCdvT.js";
|
|
10
10
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
11
11
|
import { basename, join } from "path";
|
|
12
12
|
import { homedir } from "os";
|
|
@@ -695,6 +695,7 @@ function parseClaudeSession(sessionFile) {
|
|
|
695
695
|
}
|
|
696
696
|
var CLAUDE_PROJECTS_DIR;
|
|
697
697
|
var init_jsonl_parser = __esmMin((() => {
|
|
698
|
+
init_paths();
|
|
698
699
|
init_cost();
|
|
699
700
|
CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
|
|
700
701
|
}));
|
|
@@ -1552,9 +1553,9 @@ function recordWake(name, sessionId) {
|
|
|
1552
1553
|
*/
|
|
1553
1554
|
async function spawnEphemeralSpecialist(projectKey, specialistType, task) {
|
|
1554
1555
|
ensureProjectSpecialistDir(projectKey, specialistType);
|
|
1555
|
-
const { loadContextDigest } = await import("./specialist-context-
|
|
1556
|
+
const { loadContextDigest } = await import("./specialist-context-BAUWL1Fl.js");
|
|
1556
1557
|
const contextDigest = loadContextDigest(projectKey, specialistType);
|
|
1557
|
-
const { createRunLog } = await import("./specialist-logs-
|
|
1558
|
+
const { createRunLog } = await import("./specialist-logs-DQKKQV9B.js");
|
|
1558
1559
|
const { runId, filePath: logFilePath } = createRunLog(projectKey, specialistType, task.issueId, contextDigest || void 0);
|
|
1559
1560
|
setCurrentRun(projectKey, specialistType, runId);
|
|
1560
1561
|
incrementProjectRunCount(projectKey, specialistType);
|
|
@@ -1566,14 +1567,14 @@ ${basePrompt}`;
|
|
|
1566
1567
|
const tmuxSession = getTmuxSessionName(specialistType, projectKey);
|
|
1567
1568
|
const cwd = getProject(projectKey)?.path || getDevrootPath() || homedir();
|
|
1568
1569
|
try {
|
|
1569
|
-
const { preTrustDirectory } = await import("./workspace-manager-
|
|
1570
|
+
const { preTrustDirectory } = await import("./workspace-manager-B9jS4Dsq.js");
|
|
1570
1571
|
preTrustDirectory(cwd);
|
|
1571
1572
|
} catch {}
|
|
1572
1573
|
try {
|
|
1573
1574
|
try {
|
|
1574
1575
|
const { stdout: sessions } = await execAsync("tmux list-sessions -F \"#{session_name}\" 2>/dev/null || echo \"\"", { encoding: "utf-8" });
|
|
1575
1576
|
if (sessions.split("\n").map((s) => s.trim()).includes(tmuxSession)) {
|
|
1576
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
1577
|
+
const { getAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
1577
1578
|
const existingState = getAgentRuntimeState(tmuxSession);
|
|
1578
1579
|
if (existingState?.state === "active") {
|
|
1579
1580
|
if (await isRunning(specialistType, projectKey)) return {
|
|
@@ -1582,7 +1583,7 @@ ${basePrompt}`;
|
|
|
1582
1583
|
error: "specialist_busy"
|
|
1583
1584
|
};
|
|
1584
1585
|
console.log(`[specialist] ${tmuxSession} state=active but not running — clearing stale state`);
|
|
1585
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1586
|
+
const { saveAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
1586
1587
|
saveAgentRuntimeState(tmuxSession, {
|
|
1587
1588
|
state: "idle",
|
|
1588
1589
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1652,7 +1653,7 @@ script -qfec "bash '${innerScript}'" /dev/null 2>&1 | tee -a "${logFilePath}"
|
|
|
1652
1653
|
`, { mode: 493 });
|
|
1653
1654
|
await execAsync(`tmux kill-session -t "${tmuxSession}" 2>/dev/null || true`, { encoding: "utf-8" });
|
|
1654
1655
|
await execAsync(`tmux new-session -d -s "${tmuxSession}" -c "${cwd}"${envFlags} "bash '${launcherScript}'"`, { encoding: "utf-8" });
|
|
1655
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1656
|
+
const { saveAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
1656
1657
|
saveAgentRuntimeState(tmuxSession, {
|
|
1657
1658
|
state: "active",
|
|
1658
1659
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1686,7 +1687,7 @@ async function buildTestAgentPromptContent(task) {
|
|
|
1686
1687
|
const testWorkspace = task.workspace || "unknown";
|
|
1687
1688
|
const testGitInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
1688
1689
|
const testIsPolyrepo = testGitInfo.isPolyrepo;
|
|
1689
|
-
const { extractTeamPrefix, findProjectByTeam } = await import("./projects-
|
|
1690
|
+
const { extractTeamPrefix, findProjectByTeam } = await import("./projects-DhU7rAVN.js");
|
|
1690
1691
|
const testTeamPrefix = extractTeamPrefix(task.issueId);
|
|
1691
1692
|
const testProjectConfig = testTeamPrefix ? findProjectByTeam(testTeamPrefix) : null;
|
|
1692
1693
|
const testConfigs = testProjectConfig?.tests;
|
|
@@ -1809,7 +1810,7 @@ IMPORTANT: Do NOT hand off to merge-agent. Human clicks Merge button when ready.
|
|
|
1809
1810
|
* Build task prompt for a specialist
|
|
1810
1811
|
*/
|
|
1811
1812
|
async function buildTaskPrompt(projectKey, specialistType, task, contextDigest) {
|
|
1812
|
-
const { getSpecialistPromptOverride } = await import("./projects-
|
|
1813
|
+
const { getSpecialistPromptOverride } = await import("./projects-DhU7rAVN.js");
|
|
1813
1814
|
const customPrompt = getSpecialistPromptOverride(projectKey, specialistType);
|
|
1814
1815
|
let prompt = `# ${specialistType} Task - ${task.issueId}\n\n`;
|
|
1815
1816
|
if (contextDigest) prompt += `## Context from Recent Runs\n\n${contextDigest}\n\n`;
|
|
@@ -1975,7 +1976,7 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
1975
1976
|
console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
|
|
1976
1977
|
}
|
|
1977
1978
|
if (metadata.currentRun) {
|
|
1978
|
-
const { finalizeRunLog } = await import("./specialist-logs-
|
|
1979
|
+
const { finalizeRunLog } = await import("./specialist-logs-DQKKQV9B.js");
|
|
1979
1980
|
try {
|
|
1980
1981
|
finalizeRunLog(projectKey, specialistType, metadata.currentRun, {
|
|
1981
1982
|
status: metadata.lastRunStatus || "incomplete",
|
|
@@ -1988,12 +1989,12 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
1988
1989
|
}
|
|
1989
1990
|
const key = `${projectKey}-${specialistType}`;
|
|
1990
1991
|
gracePeriodStates.delete(key);
|
|
1991
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
1992
|
+
const { saveAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
1992
1993
|
saveAgentRuntimeState(tmuxSession, {
|
|
1993
1994
|
state: "suspended",
|
|
1994
1995
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
1995
1996
|
});
|
|
1996
|
-
const { scheduleDigestGeneration } = await import("./specialist-context-
|
|
1997
|
+
const { scheduleDigestGeneration } = await import("./specialist-context-BAUWL1Fl.js");
|
|
1997
1998
|
scheduleDigestGeneration(projectKey, specialistType);
|
|
1998
1999
|
scheduleLogCleanup(projectKey, specialistType);
|
|
1999
2000
|
}
|
|
@@ -2006,8 +2007,8 @@ async function terminateSpecialist(projectKey, specialistType) {
|
|
|
2006
2007
|
function scheduleLogCleanup(projectKey, specialistType) {
|
|
2007
2008
|
Promise.resolve().then(async () => {
|
|
2008
2009
|
try {
|
|
2009
|
-
const { cleanupOldLogs } = await import("./specialist-logs-
|
|
2010
|
-
const { getSpecialistRetention } = await import("./projects-
|
|
2010
|
+
const { cleanupOldLogs } = await import("./specialist-logs-DQKKQV9B.js");
|
|
2011
|
+
const { getSpecialistRetention } = await import("./projects-DhU7rAVN.js");
|
|
2011
2012
|
const retention = getSpecialistRetention(projectKey);
|
|
2012
2013
|
const deleted = cleanupOldLogs(projectKey, specialistType, {
|
|
2013
2014
|
maxDays: retention.max_days,
|
|
@@ -2259,7 +2260,7 @@ async function getSpecialistStatus(name, projectKey) {
|
|
|
2259
2260
|
const sessionId = getSessionId(name, projectKey);
|
|
2260
2261
|
const running = await isRunning(name, projectKey);
|
|
2261
2262
|
const contextTokens = countContextTokens(name);
|
|
2262
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
2263
|
+
const { getAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
2263
2264
|
const runtimeState = getAgentRuntimeState(getTmuxSessionName(name, projectKey));
|
|
2264
2265
|
let state;
|
|
2265
2266
|
if (runtimeState) switch (runtimeState.state) {
|
|
@@ -2349,7 +2350,7 @@ exec claude --dangerously-skip-permissions --session-id "${newSessionId}" --mode
|
|
|
2349
2350
|
`, { mode: 493 });
|
|
2350
2351
|
setSessionId(name, newSessionId);
|
|
2351
2352
|
try {
|
|
2352
|
-
const { preTrustDirectory } = await import("./workspace-manager-
|
|
2353
|
+
const { preTrustDirectory } = await import("./workspace-manager-B9jS4Dsq.js");
|
|
2353
2354
|
preTrustDirectory(cwd);
|
|
2354
2355
|
} catch {}
|
|
2355
2356
|
await execAsync(`tmux kill-session -t "${tmuxSession}" 2>/dev/null || true`, { encoding: "utf-8" });
|
|
@@ -2436,7 +2437,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
2436
2437
|
const sessionId = getSessionId(name);
|
|
2437
2438
|
const wasAlreadyRunning = await isRunning(name);
|
|
2438
2439
|
if (wasAlreadyRunning && !options.skipBusyGuard) {
|
|
2439
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
2440
|
+
const { getAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
2440
2441
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
2441
2442
|
if (runtimeState?.state === "active") {
|
|
2442
2443
|
console.warn(`[specialist] ${name} is busy (working on ${runtimeState.currentIssue}), refusing to interrupt`);
|
|
@@ -2458,7 +2459,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
2458
2459
|
};
|
|
2459
2460
|
const cwd = getDevrootPath() || join(process.env.HOME || "/home/eltmon", "Projects");
|
|
2460
2461
|
try {
|
|
2461
|
-
const { preTrustDirectory } = await import("./workspace-manager-
|
|
2462
|
+
const { preTrustDirectory } = await import("./workspace-manager-B9jS4Dsq.js");
|
|
2462
2463
|
preTrustDirectory(cwd);
|
|
2463
2464
|
} catch {}
|
|
2464
2465
|
try {
|
|
@@ -2531,7 +2532,7 @@ async function wakeSpecialist(name, taskPrompt, options = {}) {
|
|
|
2531
2532
|
};
|
|
2532
2533
|
}
|
|
2533
2534
|
recordWake(name, sessionId || void 0);
|
|
2534
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
2535
|
+
const { saveAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
2535
2536
|
saveAgentRuntimeState(tmuxSession, {
|
|
2536
2537
|
state: "active",
|
|
2537
2538
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2640,14 +2641,14 @@ CRITICAL: Do NOT delete the feature branch.`;
|
|
|
2640
2641
|
}
|
|
2641
2642
|
if (totalChangedFiles === 0) {
|
|
2642
2643
|
console.log(`[specialist] review-agent: stale branch detected for ${task.issueId} — 0 files changed vs main`);
|
|
2643
|
-
const { setReviewStatus } = await import("./review-status-
|
|
2644
|
+
const { setReviewStatus } = await import("./review-status-D6H2WOw8.js");
|
|
2644
2645
|
setReviewStatus(task.issueId.toUpperCase(), {
|
|
2645
2646
|
reviewStatus: "passed",
|
|
2646
2647
|
reviewNotes: "No changes to review — branch identical to main (already merged or stale)"
|
|
2647
2648
|
});
|
|
2648
2649
|
console.log(`[specialist] review-agent: auto-passed ${task.issueId} (stale branch)`);
|
|
2649
2650
|
const tmuxSession = getTmuxSessionName("review-agent");
|
|
2650
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
2651
|
+
const { saveAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
2651
2652
|
saveAgentRuntimeState(tmuxSession, {
|
|
2652
2653
|
state: "idle",
|
|
2653
2654
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2756,7 +2757,7 @@ curl -s -X POST ${apiUrl}/api/specialists/test-agent/queue -H "Content-Type: app
|
|
|
2756
2757
|
const testWorkspace = task.workspace || "unknown";
|
|
2757
2758
|
const testGitInfo = await resolveWorkspaceGitInfo(task.workspace, task.branch);
|
|
2758
2759
|
const testIsPolyrepo = testGitInfo.isPolyrepo;
|
|
2759
|
-
const { extractTeamPrefix, findProjectByTeam } = await import("./projects-
|
|
2760
|
+
const { extractTeamPrefix, findProjectByTeam } = await import("./projects-DhU7rAVN.js");
|
|
2760
2761
|
const testTeamPrefix = extractTeamPrefix(task.issueId);
|
|
2761
2762
|
const testProjectConfig = testTeamPrefix ? findProjectByTeam(testTeamPrefix) : null;
|
|
2762
2763
|
const testConfigs = testProjectConfig?.tests;
|
|
@@ -2941,7 +2942,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
|
2941
2942
|
console.warn(`[specialist] Task readiness check failed for ${vbriefItemId}: ${readinessErr.message}`);
|
|
2942
2943
|
}
|
|
2943
2944
|
const running = await isRunning(name);
|
|
2944
|
-
const { getAgentRuntimeState } = await import("./agents-
|
|
2945
|
+
const { getAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
2945
2946
|
const tmuxSession = getTmuxSessionName(name);
|
|
2946
2947
|
const runtimeState = getAgentRuntimeState(tmuxSession);
|
|
2947
2948
|
const idle = runtimeState?.state === "idle" || runtimeState?.state === "suspended";
|
|
@@ -2970,7 +2971,7 @@ async function wakeSpecialistOrQueue(name, task, options = {}) {
|
|
|
2970
2971
|
error: msg
|
|
2971
2972
|
};
|
|
2972
2973
|
}
|
|
2973
|
-
const { saveAgentRuntimeState } = await import("./agents-
|
|
2974
|
+
const { saveAgentRuntimeState } = await import("./agents-D_2oRFVf.js");
|
|
2974
2975
|
saveAgentRuntimeState(tmuxSession, {
|
|
2975
2976
|
state: "active",
|
|
2976
2977
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3096,7 +3097,7 @@ async function sendFeedbackToAgent(feedback) {
|
|
|
3096
3097
|
}
|
|
3097
3098
|
const agentSession = `agent-${toIssueId.toLowerCase()}`;
|
|
3098
3099
|
const feedbackMessage = formatFeedbackForAgent(fullFeedback);
|
|
3099
|
-
const { writeFeedbackFile } = await import("./feedback-writer-
|
|
3100
|
+
const { writeFeedbackFile } = await import("./feedback-writer-ygXN5F9N.js");
|
|
3100
3101
|
const fileResult = await writeFeedbackFile({
|
|
3101
3102
|
issueId: toIssueId,
|
|
3102
3103
|
specialist: {
|
|
@@ -3113,7 +3114,7 @@ async function sendFeedbackToAgent(feedback) {
|
|
|
3113
3114
|
return false;
|
|
3114
3115
|
}
|
|
3115
3116
|
try {
|
|
3116
|
-
const { messageAgent } = await import("./agents-
|
|
3117
|
+
const { messageAgent } = await import("./agents-D_2oRFVf.js");
|
|
3117
3118
|
await messageAgent(agentSession, `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.\nRead and address: ${fileResult.relativePath}`);
|
|
3118
3119
|
console.log(`[specialist] Sent feedback from ${fromSpecialist} to ${agentSession} (file: ${fileResult.relativePath})`);
|
|
3119
3120
|
return true;
|
|
@@ -3247,4 +3248,4 @@ var init_specialists = __esmMin((() => {
|
|
|
3247
3248
|
//#endregion
|
|
3248
3249
|
export { updateContextTokens as $, initSpecialistsDirectory as A, formatCost as At, loadRegistry as B, init_io as Bt, getSessionGeneration as C, getProjectDirs as Ct, getSpecialistStatus as D, checkBudget as Dt, getSpecialistState as E, parseClaudeSession as Et, isInitialized as F, getWeeklySummary as Ft, sendFeedbackToAgent as G, recordWake as H, updateItemStatus as Ht, isRunning as I, init_cost as It, signalSpecialistCompletion as J, setCurrentRun as K, listProjectsWithSpecialists as L, readIssueCosts as Lt, initializeEnabledSpecialists as M, getAllBudgets as Mt, initializeSpecialist as N, getDailySummary as Nt, getTmuxSessionName as O, createBudget as Ot, isEnabled as P, getMonthlySummary as Pt, terminateSpecialist as Q, listSessionFiles as R, readTodayCosts as Rt, getSessionFilePath as S, parseLogMetadata as St, getSpecialistMetadata as T, init_jsonl_parser as Tt, resumeGracePeriod as U, updateSubItemStatus as Ut, pauseGracePeriod as V, readWorkspacePlan as Vt, saveRegistry as W, startGracePeriod as X, spawnEphemeralSpecialist as Y, submitToSpecialistQueue as Z, getGracePeriodState as _, getRunLogSize as _t, completeSpecialistTask as a, wakeSpecialistWithTask as at, getProjectSpecialistDir as b, isRunLogActive as bt, enableSpecialist as c, checkLogSizeLimit as ct, findSessionFile as d, createRunLog as dt, updateProjectSpecialistMetadata as et, getAllProjectSpecialistStatuses as f, finalizeRunLog as ft, getFeedbackStats as g, getRunLogPath as gt, getEnabledSpecialists as h, getRunLog as ht, clearSessionId as i, wakeSpecialistOrQueue as it, init_specialists as j, generateReport as jt, incrementProjectRunCount as k, deleteBudget as kt, ensureProjectSpecialistDir as l, cleanupAllLogs as lt, getAllSpecialists as m, getRecentRunLogs as mt, bumpSessionGeneration as n, updateSpecialistMetadata as nt, countContextTokens as o, MAX_LOG_SIZE as ot, getAllSpecialistStatus as p, generateRunId as pt, setSessionId as q, checkSpecialistQueue as r, wakeSpecialist as rt, disableSpecialist as s, appendToRunLog as st, buildTestAgentPromptContent as t, updateRunStatus as tt, exitGracePeriod as u, cleanupOldLogs as ut, getNextSpecialistTask as v, getRunsDirectory as vt, getSessionId as w, getSessionFiles as wt, getProjectSpecialistMetadata as x, listRunLogs as xt, getPendingFeedback as y, init_specialist_logs as yt, listSpecialistsForProject as z, summarizeCosts as zt };
|
|
3249
3250
|
|
|
3250
|
-
//# sourceMappingURL=specialists-
|
|
3251
|
+
//# sourceMappingURL=specialists-D7Kj5o6s.js.map
|