@overdeck/core 0.45.10 → 0.45.11
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/dist/{agent-state-KVbJEG5f.js → agent-state-RLhS7ckO.js} +1 -1
- package/dist/{agents-pp8FO7Uj.js → agents-By3u_fQ8.js} +1 -1
- package/dist/{agents-CQHK0CSA.js → agents-CgVr77Qq.js} +12 -12
- package/dist/{agents-CQHK0CSA.js.map → agents-CgVr77Qq.js.map} +1 -1
- package/dist/{backlog-auto-trigger-CyuoS0nl.js → backlog-auto-trigger-B_-1tk4e.js} +5 -5
- package/dist/{backlog-auto-trigger-CyuoS0nl.js.map → backlog-auto-trigger-B_-1tk4e.js.map} +1 -1
- package/dist/cli/index.js +45 -45
- package/dist/{cli-Cewlu3yU.js → cli-DGeSW85B.js} +2 -2
- package/dist/{cli-Cewlu3yU.js.map → cli-DGeSW85B.js.map} +1 -1
- package/dist/cli-MTB9IOoR.js +3 -0
- package/dist/{close-issue-BJGX9_z7.js → close-issue-Djajb6Sv.js} +2 -2
- package/dist/{close-issue-BJGX9_z7.js.map → close-issue-Djajb6Sv.js.map} +1 -1
- package/dist/{concurrency-Ca-fC7tA.js → concurrency-4rzjHAQA.js} +1 -1
- package/dist/{conversation-compaction-B_6s9EvM.js → conversation-compaction-BQvZh09I.js} +4 -4
- package/dist/{conversation-compaction-B_6s9EvM.js.map → conversation-compaction-BQvZh09I.js.map} +1 -1
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js → advancing-selfheal-BezNfM3P.js} +3 -3
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js.map → advancing-selfheal-BezNfM3P.js.map} +1 -1
- package/dist/dashboard/{agent-state-xk_G8t90.js → agent-state-BUANcaA7.js} +1 -1
- package/dist/dashboard/{agent-state-service-C1iUF9Ft.js → agent-state-service-DmFpIMbP.js} +1 -1
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js → agent-state-service-tXUdp6UF.js} +2 -2
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js.map → agent-state-service-tXUdp6UF.js.map} +1 -1
- package/dist/dashboard/{agents-BIggLvi_.js → agents-C-vqmKDi.js} +1 -1
- package/dist/dashboard/{agents-DHvsjsuP.js → agents-C7V4odQ6.js} +11 -11
- package/dist/dashboard/{agents-DHvsjsuP.js.map → agents-C7V4odQ6.js.map} +1 -1
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js → backlog-auto-trigger-Cb8PR-5w.js} +2 -2
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js.map → backlog-auto-trigger-Cb8PR-5w.js.map} +1 -1
- package/dist/dashboard/{cli-CvK9xuSS.js → cli-BgzbB6h3.js} +2 -2
- package/dist/dashboard/{cli-CvK9xuSS.js.map → cli-BgzbB6h3.js.map} +1 -1
- package/dist/dashboard/cli-DCqy_wFa.js +3 -0
- package/dist/dashboard/{close-issue-BnpErnD1.js → close-issue-BWf3pxn4.js} +2 -2
- package/dist/dashboard/{close-issue-BnpErnD1.js.map → close-issue-BWf3pxn4.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js → closed-issue-reaper-D7XPq5pY.js} +4 -4
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js.map → closed-issue-reaper-D7XPq5pY.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-BLCQwTpF.js → closed-issue-reaper-VSTSTROK.js} +1 -1
- package/dist/dashboard/{conflict-gate-JdFuhKuF.js → conflict-gate-DwOslTij.js} +1 -1
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js → conflict-gate-FFEvTDVA.js} +3 -3
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js.map → conflict-gate-FFEvTDVA.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js → conversation-compaction-CCqN4hWk.js} +4 -4
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js.map → conversation-compaction-CCqN4hWk.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-Ba5aZdrb.js → conversation-compaction-CL5Ti4j4.js} +1 -1
- package/dist/dashboard/{deacon-j3qSZ7--.js → deacon-CGHl90eC.js} +107 -50
- package/dist/dashboard/{deacon-j3qSZ7--.js.map → deacon-CGHl90eC.js.map} +1 -1
- package/dist/dashboard/{deacon-Cv2RMfHs.js → deacon-DMPh40Im.js} +2 -2
- package/dist/dashboard/deacon-swarm-CVTiNL0N.js +2 -0
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js → deacon-swarm-aMMJTq6t.js} +4 -4
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js.map → deacon-swarm-aMMJTq6t.js.map} +1 -1
- package/dist/dashboard/deacon.js +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js → feedback-target-dJjFfITP.js} +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js.map → feedback-target-dJjFfITP.js.map} +1 -1
- package/dist/dashboard/{feedback-target-C4DaDSZ8.js → feedback-target-nQSM6ygL.js} +1 -1
- package/dist/dashboard/{flywheel-ClMjGW_u.js → flywheel-BLoVNtJV.js} +6 -6
- package/dist/dashboard/{flywheel-ClMjGW_u.js.map → flywheel-BLoVNtJV.js.map} +1 -1
- package/dist/dashboard/{flywheel-DefHRzY4.js → flywheel-Cqf8vu7N.js} +6 -6
- package/dist/dashboard/{flywheel-DefHRzY4.js.map → flywheel-Cqf8vu7N.js.map} +1 -1
- package/dist/dashboard/{flywheel-BqDdGvrQ.js → flywheel-PJsbrXNY.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js → flywheel-merge-order-B7BH8mZ3.js} +2 -2
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js.map → flywheel-merge-order-B7BH8mZ3.js.map} +1 -1
- package/dist/dashboard/{flywheel-merge-order-B6YDSElh.js → flywheel-merge-order-CCwF60b-.js} +1 -1
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js → harness-resolve-B3f9G1Kp.js} +2 -2
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js.map → harness-resolve-B3f9G1Kp.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js → inspect-agent-CKF5-SJr.js} +3 -3
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js.map → inspect-agent-CKF5-SJr.js.map} +1 -1
- package/dist/dashboard/inspect-agent-CqpEWGWy.js +2 -0
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js → issue-lifecycle-5LbOcATP.js} +7 -7
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js.map → issue-lifecycle-5LbOcATP.js.map} +1 -1
- package/dist/dashboard/{issue-lifecycle-nY4esZn8.js → issue-lifecycle-CjYqQ669.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-Bnqv1uaa.js → issue-service-singleton-B8iu_TKV.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js → issue-service-singleton-CDGt__oQ.js} +2 -2
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js.map → issue-service-singleton-CDGt__oQ.js.map} +1 -1
- package/dist/dashboard/{lifecycle-CGKm7doE.js → lifecycle-CEg4sHZH.js} +4 -4
- package/dist/dashboard/{lifecycle-CGKm7doE.js.map → lifecycle-CEg4sHZH.js.map} +1 -1
- package/dist/dashboard/{merge-agent-Dh8Y9PDO.js → merge-agent-7nBiCytp.js} +1 -1
- package/dist/dashboard/{merge-agent-BcgAYStl.js → merge-agent-C81xPIGd.js} +6 -6
- package/dist/dashboard/{merge-agent-BcgAYStl.js.map → merge-agent-C81xPIGd.js.map} +1 -1
- package/dist/dashboard/merge-ops-DE_6CnmZ.js +2 -0
- package/dist/dashboard/{merge-train-DmxlYbEo.js → merge-train-D3qzRccL.js} +2 -2
- package/dist/dashboard/{merge-train-DmxlYbEo.js.map → merge-train-D3qzRccL.js.map} +1 -1
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js → merge-train-deps-Be5ElC_P.js} +2 -2
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js.map → merge-train-deps-Be5ElC_P.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js → orphan-proposed-reconciler-Boq_XaRe.js} +3 -3
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js.map → orphan-proposed-reconciler-Boq_XaRe.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-mXV--tcB.js → orphan-proposed-reconciler-C0sPnDq_.js} +1 -1
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js → reconstruct-cache-C2pCltOq.js} +3 -3
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js.map → reconstruct-cache-C2pCltOq.js.map} +1 -1
- package/dist/dashboard/{remote-completion-Bklh6loJ.js → remote-completion-BmxD5UMQ.js} +3 -3
- package/dist/dashboard/{remote-completion-Bklh6loJ.js.map → remote-completion-BmxD5UMQ.js.map} +1 -1
- package/dist/dashboard/resume-C-nxu9Y4.js +3 -0
- package/dist/dashboard/{review-agent-phzqNqIs.js → review-agent-D6P6d3pz.js} +26 -17
- package/dist/dashboard/review-agent-D6P6d3pz.js.map +1 -0
- package/dist/dashboard/review-agent-DTINEffB.js +3 -0
- package/dist/dashboard/{review-context-CCq56O_Y.js → review-context-Dyo0tsBa.js} +2 -2
- package/dist/dashboard/{review-context-CCq56O_Y.js.map → review-context-Dyo0tsBa.js.map} +1 -1
- package/dist/dashboard/{review-status-BQjqQXZG.js → review-status-B5ZASOo4.js} +18 -5
- package/dist/dashboard/review-status-B5ZASOo4.js.map +1 -0
- package/dist/dashboard/{review-status-bBkqUj5i.js → review-status-h7BXIA4z.js} +1 -1
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js → review-verdict-feedback-B70VbfRL.js} +4 -4
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js.map → review-verdict-feedback-B70VbfRL.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-cZh9Wi9G.js → review-verdict-feedback-CorgH49f.js} +1 -1
- package/dist/dashboard/{sequence-io-B2GmilSI.js → sequence-io-6YP8fVpH.js} +2 -2
- package/dist/dashboard/{sequence-io-B2GmilSI.js.map → sequence-io-6YP8fVpH.js.map} +1 -1
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js → sequencer-agent-ja-nkEc1.js} +5 -5
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js.map → sequencer-agent-ja-nkEc1.js.map} +1 -1
- package/dist/dashboard/server.js +66 -55
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{service-CILd-uOt.js → service-HKDrIy0h.js} +24 -24
- package/dist/dashboard/{service-CILd-uOt.js.map → service-HKDrIy0h.js.map} +1 -1
- package/dist/dashboard/{smart-compaction-Bgp_gwcA.js → smart-compaction-L0LSnF0_.js} +1 -1
- package/dist/dashboard/{smart-compaction-CrP90OFE.js → smart-compaction-uke1npUz.js} +2 -2
- package/dist/dashboard/{smart-compaction-CrP90OFE.js.map → smart-compaction-uke1npUz.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-D15KbPOy.js → spawn-planning-session-CSMWco8B.js} +1 -1
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js → spawn-planning-session-CXc0NkwV.js} +3 -3
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js.map → spawn-planning-session-CXc0NkwV.js.map} +1 -1
- package/dist/dashboard/{specialist-context-CwKkcXL9.js → specialist-context-BGCJ24LH.js} +3 -3
- package/dist/dashboard/{specialist-context-CwKkcXL9.js.map → specialist-context-BGCJ24LH.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-CvDxquV_.js → specialist-logs--Uz-r8ia.js} +2 -2
- package/dist/dashboard/{specialist-logs-CvDxquV_.js.map → specialist-logs--Uz-r8ia.js.map} +1 -1
- package/dist/dashboard/{specialists-BCjcG4uE.js → specialists-Ca9XSBCS.js} +1 -1
- package/dist/dashboard/{specialists-D07ybnam.js → specialists-DEfrAbWs.js} +36 -36
- package/dist/dashboard/{specialists-D07ybnam.js.map → specialists-DEfrAbWs.js.map} +1 -1
- package/dist/dashboard/{specialists-F2uMO4QS.js → specialists-DeZx9xom.js} +1 -1
- package/dist/dashboard/{specialists-BYi0k8CZ.js → specialists-nZ0mRwpZ.js} +12 -12
- package/dist/dashboard/{specialists-BYi0k8CZ.js.map → specialists-nZ0mRwpZ.js.map} +1 -1
- package/dist/dashboard/{summary-fork-DedqguZb.js → summary-fork-2a1L2DIU.js} +2 -2
- package/dist/dashboard/{summary-fork-EjvOkEPl.js → summary-fork-Xn0QUNmb.js} +3 -3
- package/dist/dashboard/{summary-fork-EjvOkEPl.js.map → summary-fork-Xn0QUNmb.js.map} +1 -1
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js → teardown-workspace-DhA6oAIK.js} +2 -2
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js.map → teardown-workspace-DhA6oAIK.js.map} +1 -1
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js → test-agent-queue-B_ukPyIK.js} +3 -3
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js.map → test-agent-queue-B_ukPyIK.js.map} +1 -1
- package/dist/dashboard/{uat-train-CtNGgXxj.js → uat-train-BLBAbJ8Y.js} +10 -10
- package/dist/dashboard/{uat-train-CtNGgXxj.js.map → uat-train-BLBAbJ8Y.js.map} +1 -1
- package/dist/dashboard/{uat-train-C3hWXtip.js → uat-train-DPfGR9_I.js} +1 -1
- package/dist/dashboard/{verification-runner-DuZRHjPU.js → verification-runner-KTGTNugM.js} +1 -1
- package/dist/dashboard/{verification-runner-BPAhPWR7.js → verification-runner-R2-4XgMO.js} +4 -4
- package/dist/dashboard/{verification-runner-BPAhPWR7.js.map → verification-runner-R2-4XgMO.js.map} +1 -1
- package/dist/dashboard/{workflows-H-COJ84X.js → workflows-CSKX5MhC.js} +7 -7
- package/dist/dashboard/{workflows-H-COJ84X.js.map → workflows-CSKX5MhC.js.map} +1 -1
- package/dist/dashboard/workflows-DVwXauef.js +2 -0
- package/dist/dashboard/{workspaces-CRU9_vRn.js → workspaces-DXLDgXo2.js} +32 -32
- package/dist/dashboard/{workspaces-CRU9_vRn.js.map → workspaces-DXLDgXo2.js.map} +1 -1
- package/dist/docs-index.sqlite +0 -0
- package/dist/{feedback-target-DTGAhFkM.js → feedback-target-BsKN4Tr_.js} +4 -4
- package/dist/{feedback-target-DTGAhFkM.js.map → feedback-target-BsKN4Tr_.js.map} +1 -1
- package/dist/{feedback-target-DutF088V.js → feedback-target-DM1WzOKy.js} +1 -1
- package/dist/{harness-resolve-Bz1prk6B.js → harness-resolve-B-YDNwNW.js} +2 -2
- package/dist/{harness-resolve-Bz1prk6B.js.map → harness-resolve-B-YDNwNW.js.map} +1 -1
- package/dist/{issue-lifecycle-fVqvCdKM.js → issue-lifecycle-Bc1l3U9f.js} +7 -7
- package/dist/{issue-lifecycle-fVqvCdKM.js.map → issue-lifecycle-Bc1l3U9f.js.map} +1 -1
- package/dist/{issue-service-singleton-CZm8p6Rj.js → issue-service-singleton-D-8Q6tdT.js} +1 -1
- package/dist/{issue-service-singleton-Cr73JAaR.js → issue-service-singleton-D1433I7n.js} +2 -2
- package/dist/{issue-service-singleton-Cr73JAaR.js.map → issue-service-singleton-D1433I7n.js.map} +1 -1
- package/dist/{lifecycle-CR6tZfl1.js → lifecycle-SmrqysDf.js} +7 -7
- package/dist/{lifecycle-CR6tZfl1.js.map → lifecycle-SmrqysDf.js.map} +1 -1
- package/dist/{lifecycle-D_EWiUjk.js → lifecycle-mRMyiIXs.js} +2 -2
- package/dist/{merge-agent-LM4avOtA.js → merge-agent-B9hIZ6Z0.js} +6 -6
- package/dist/{merge-agent-LM4avOtA.js.map → merge-agent-B9hIZ6Z0.js.map} +1 -1
- package/dist/{merge-agent-Bj046HE1.js → merge-agent-DUj22cXL.js} +1 -1
- package/dist/{merge-fulfillment-DrnC1Kmg.js → merge-fulfillment-__P4WE-T.js} +3 -3
- package/dist/{merge-fulfillment-DrnC1Kmg.js.map → merge-fulfillment-__P4WE-T.js.map} +1 -1
- package/dist/{reconstruct-cache-NPqoAUg-.js → reconstruct-cache-9SWp82SQ.js} +3 -3
- package/dist/{reconstruct-cache-NPqoAUg-.js.map → reconstruct-cache-9SWp82SQ.js.map} +1 -1
- package/dist/{records-BIkN1LQ4.js → records-CTmvuRzz.js} +14 -1
- package/dist/{records-BIkN1LQ4.js.map → records-CTmvuRzz.js.map} +1 -1
- package/dist/records-backfill-BQcmTmwF.js +2 -0
- package/dist/{records-backfill-HM0Pln6N.js → records-backfill-BrNsv_zT.js} +2 -2
- package/dist/{records-backfill-HM0Pln6N.js.map → records-backfill-BrNsv_zT.js.map} +1 -1
- package/dist/resume-Bn4kDk9x.js +3 -0
- package/dist/{review-agent-DFosQzCG.js → review-agent-PhqOhpx3.js} +24 -16
- package/dist/{review-agent-DFosQzCG.js.map → review-agent-PhqOhpx3.js.map} +1 -1
- package/dist/review-agent-hLbRRxZF.js +3 -0
- package/dist/{review-status-iJwfEoNX.js → review-status-B_XXomV0.js} +6 -6
- package/dist/{review-status-iJwfEoNX.js.map → review-status-B_XXomV0.js.map} +1 -1
- package/dist/{review-status-BbNVlXh1.js → review-status-Bo-EFVKh.js} +1 -1
- package/dist/{review-verdict-feedback-BGPw3z1X.js → review-verdict-feedback-CXHkoiKZ.js} +1 -1
- package/dist/{review-verdict-feedback-CCZ05atV.js → review-verdict-feedback-DGV13LDb.js} +4 -4
- package/dist/{review-verdict-feedback-CCZ05atV.js.map → review-verdict-feedback-DGV13LDb.js.map} +1 -1
- package/dist/{sequence-io-CcxZNHcD.js → sequence-io-Cg4NHAco.js} +1 -1
- package/dist/{sequence-io-Cx1usxG3.js → sequence-io-CyxvNHfA.js} +2 -2
- package/dist/{sequence-io-Cx1usxG3.js.map → sequence-io-CyxvNHfA.js.map} +1 -1
- package/dist/{smart-compaction-ByUEgQkF.js → smart-compaction-ByKXTQNy.js} +2 -2
- package/dist/{smart-compaction-ByUEgQkF.js.map → smart-compaction-ByKXTQNy.js.map} +1 -1
- package/dist/{smart-compaction-DfLe_CtH.js → smart-compaction-U6e_uB1E.js} +1 -1
- package/dist/{specialist-context-McuGkaeR.js → specialist-context-BUTadO-U.js} +3 -3
- package/dist/{specialist-context-McuGkaeR.js.map → specialist-context-BUTadO-U.js.map} +1 -1
- package/dist/{specialist-logs-CUUJ4eiB.js → specialist-logs-pEMCTtGZ.js} +2 -2
- package/dist/{specialist-logs-CUUJ4eiB.js.map → specialist-logs-pEMCTtGZ.js.map} +1 -1
- package/dist/{specialists-BSrylD1S.js → specialists-BGFnrQ8n.js} +1 -1
- package/dist/{specialists-B0elgInc.js → specialists-DeTkkp8b.js} +12 -12
- package/dist/{specialists-B0elgInc.js.map → specialists-DeTkkp8b.js.map} +1 -1
- package/dist/{summary-fork-DskMRnyb.js → summary-fork-CgzThE2-.js} +2 -2
- package/dist/{summary-fork-CPUTKcAT.js → summary-fork-Dx1RMuvU.js} +3 -3
- package/dist/{summary-fork-CPUTKcAT.js.map → summary-fork-Dx1RMuvU.js.map} +1 -1
- package/dist/{sync-BiNVQHBf.js → sync-Bsww9tuj.js} +2 -2
- package/dist/{sync-BiNVQHBf.js.map → sync-Bsww9tuj.js.map} +1 -1
- package/dist/sync-CylE_SjB.js +2 -0
- package/dist/{verdict-restore-DQiV7_fe.js → verdict-restore-DGpX0ZOJ.js} +5 -5
- package/dist/{verdict-restore-DQiV7_fe.js.map → verdict-restore-DGpX0ZOJ.js.map} +1 -1
- package/dist/{webhook-handlers-DHBSEDEm.js → webhook-handlers-BjsfIL7u.js} +3 -3
- package/dist/{webhook-handlers-DHBSEDEm.js.map → webhook-handlers-BjsfIL7u.js.map} +1 -1
- package/dist/{workspace-migrate-duUsWNpd.js → workspace-migrate-BBQ8nAnP.js} +2 -2
- package/dist/{workspace-migrate-duUsWNpd.js.map → workspace-migrate-BBQ8nAnP.js.map} +1 -1
- package/dist/{workspace-reap-DLCIoJmn.js → workspace-reap-12O9354k.js} +2 -2
- package/dist/{workspace-reap-DLCIoJmn.js.map → workspace-reap-12O9354k.js.map} +1 -1
- package/package.json +1 -1
- package/scripts/file-size-baseline.txt +1 -1
- package/dist/cli-DBWG12XK.js +0 -3
- package/dist/dashboard/cli-CJDWXE_Y.js +0 -3
- package/dist/dashboard/deacon-swarm-D1iAUX-P.js +0 -2
- package/dist/dashboard/inspect-agent-vm88mLYr.js +0 -2
- package/dist/dashboard/merge-ops-QdBiQ8XB.js +0 -2
- package/dist/dashboard/resume-DnIsmIgC.js +0 -3
- package/dist/dashboard/review-agent-DgySDb-G.js +0 -3
- package/dist/dashboard/review-agent-phzqNqIs.js.map +0 -1
- package/dist/dashboard/review-status-BQjqQXZG.js.map +0 -1
- package/dist/dashboard/workflows-Cz8OtiB4.js +0 -2
- package/dist/records-backfill-DEgtRyKL.js +0 -2
- package/dist/resume-DGymuXZM.js +0 -3
- package/dist/review-agent-DjVadURa.js +0 -3
- package/dist/sync-4jqukkxz.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue-service-singleton-_AquSBTl.js","names":[],"sources":["../../src/dashboard/server/services/cache-service.ts","../../src/dashboard/server/services/issue-data-service.ts","../../src/dashboard/server/services/issue-service-singleton.ts"],"sourcesContent":["/**\n * CacheService — Two-layer cache for dashboard API responses\n *\n * L1: In-memory Map (hot, 10s TTL, 50 entries max)\n * L2: SQLite (persistent, survives restarts)\n *\n * Stores API responses per tracker with ETag support (GitHub REST 304s are FREE).\n * Tracks rate limits per tracker for adaptive backoff.\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { openDatabase, type SqliteDatabase } from '../../../lib/database/driver.js';\n\nconst OVERDECK_HOME = process.env.OVERDECK_HOME || join(homedir(), '.overdeck');\nconst CACHE_DB_PATH = join(OVERDECK_HOME, 'cache.db');\n\n// Default TTLs per tracker (seconds)\nexport const DEFAULT_TTLS: Record<string, number> = {\n github: 60,\n linear: 30,\n rally: 120,\n};\n\n// L1 in-memory cache entry\ninterface L1Entry {\n data: any;\n etag?: string;\n lastModified?: string;\n lastFetchedAt: string;\n lastUpdatedAt: string;\n ttlSeconds: number;\n insertedAt: number; // Date.now()\n}\n\n// Rate limit info\nexport interface RateLimitInfo {\n remaining: number;\n total: number;\n resetAt: string;\n}\n\n/**\n * Parse an integer value from a response header.\n * Returns null when the header is missing or not a finite integer.\n */\nexport function parseIntegerHeader(headers: Headers, name: string): number | null {\n const raw = headers.get(name);\n if (!raw) return null;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\n// Cache entry returned from get()\nexport interface CacheEntry {\n data: any;\n etag?: string;\n lastModified?: string;\n lastFetchedAt: string;\n lastUpdatedAt: string;\n ttlSeconds: number;\n}\n\nexport class CacheService {\n private db: SqliteDatabase;\n private l1: Map<string, L1Entry> = new Map();\n private readonly l1MaxEntries = 50;\n private readonly l1TtlMs = 10_000; // 10 seconds\n\n constructor() {\n const t0 = performance.now();\n this.db = openDatabase(CACHE_DB_PATH);\n this.db.pragma('journal_mode = WAL');\n console.log(`[boot-timing] cache.db opened (WAL replay) at +${Math.round(performance.now() - t0)}ms`);\n this.createSchema();\n }\n\n private createSchema(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS api_cache (\n tracker TEXT NOT NULL,\n cache_key TEXT NOT NULL,\n data TEXT NOT NULL,\n etag TEXT,\n last_modified TEXT,\n last_fetched_at TEXT NOT NULL,\n last_updated_at TEXT NOT NULL,\n ttl_seconds INTEGER NOT NULL,\n PRIMARY KEY (tracker, cache_key)\n );\n\n CREATE TABLE IF NOT EXISTS rate_limits (\n tracker TEXT PRIMARY KEY,\n remaining INTEGER,\n total INTEGER,\n reset_at TEXT,\n updated_at TEXT NOT NULL\n );\n `);\n }\n\n /**\n * Get a cached entry. Checks L1 first, then L2 (SQLite).\n * Returns null if no entry or if expired.\n */\n get(tracker: string, cacheKey: string): CacheEntry | null {\n const compositeKey = `${tracker}:${cacheKey}`;\n\n // L1 check\n const l1Entry = this.l1.get(compositeKey);\n if (l1Entry && Date.now() - l1Entry.insertedAt < this.l1TtlMs) {\n return {\n data: l1Entry.data,\n etag: l1Entry.etag,\n lastModified: l1Entry.lastModified,\n lastFetchedAt: l1Entry.lastFetchedAt,\n lastUpdatedAt: l1Entry.lastUpdatedAt,\n ttlSeconds: l1Entry.ttlSeconds,\n };\n }\n\n // L1 miss or expired — check L2\n const stmt = this.db.prepare(`\n SELECT data, etag, last_modified, last_fetched_at, last_updated_at, ttl_seconds\n FROM api_cache\n WHERE tracker = ? AND cache_key = ?\n `);\n\n const row = stmt.get(tracker, cacheKey) as any;\n if (!row) return null;\n\n const entry: CacheEntry = {\n data: JSON.parse(row.data),\n etag: row.etag || undefined,\n lastModified: row.last_modified || undefined,\n lastFetchedAt: row.last_fetched_at,\n lastUpdatedAt: row.last_updated_at,\n ttlSeconds: row.ttl_seconds,\n };\n\n // Promote to L1\n this.setL1(compositeKey, {\n ...entry,\n insertedAt: Date.now(),\n });\n\n return entry;\n }\n\n /**\n * Get cached entry even if stale (for serving while re-fetching).\n */\n getStale(tracker: string, cacheKey: string): CacheEntry | null {\n const compositeKey = `${tracker}:${cacheKey}`;\n\n // Check L1 (even if expired)\n const l1Entry = this.l1.get(compositeKey);\n if (l1Entry) {\n return {\n data: l1Entry.data,\n etag: l1Entry.etag,\n lastModified: l1Entry.lastModified,\n lastFetchedAt: l1Entry.lastFetchedAt,\n lastUpdatedAt: l1Entry.lastUpdatedAt,\n ttlSeconds: l1Entry.ttlSeconds,\n };\n }\n\n // L2\n const stmt = this.db.prepare(`\n SELECT data, etag, last_modified, last_fetched_at, last_updated_at, ttl_seconds\n FROM api_cache\n WHERE tracker = ? AND cache_key = ?\n `);\n\n const row = stmt.get(tracker, cacheKey) as any;\n if (!row) return null;\n\n return {\n data: JSON.parse(row.data),\n etag: row.etag || undefined,\n lastModified: row.last_modified || undefined,\n lastFetchedAt: row.last_fetched_at,\n lastUpdatedAt: row.last_updated_at,\n ttlSeconds: row.ttl_seconds,\n };\n }\n\n /**\n * Store data in both L1 and L2 cache.\n */\n set(\n tracker: string,\n cacheKey: string,\n data: any,\n options?: {\n etag?: string;\n lastModified?: string;\n lastUpdatedAt?: string;\n ttlSeconds?: number;\n }\n ): void {\n const now = new Date().toISOString();\n const ttl = options?.ttlSeconds ?? DEFAULT_TTLS[tracker] ?? 60;\n const lastUpdatedAt = options?.lastUpdatedAt ?? now;\n const compositeKey = `${tracker}:${cacheKey}`;\n\n // L2 (SQLite) — upsert\n const stmt = this.db.prepare(`\n INSERT INTO api_cache (tracker, cache_key, data, etag, last_modified, last_fetched_at, last_updated_at, ttl_seconds)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(tracker, cache_key) DO UPDATE SET\n data = excluded.data,\n etag = excluded.etag,\n last_modified = excluded.last_modified,\n last_fetched_at = excluded.last_fetched_at,\n last_updated_at = excluded.last_updated_at,\n ttl_seconds = excluded.ttl_seconds\n `);\n\n stmt.run(\n tracker,\n cacheKey,\n JSON.stringify(data),\n options?.etag ?? null,\n options?.lastModified ?? null,\n now,\n lastUpdatedAt,\n ttl\n );\n\n // L1\n this.setL1(compositeKey, {\n data,\n etag: options?.etag,\n lastModified: options?.lastModified,\n lastFetchedAt: now,\n lastUpdatedAt,\n ttlSeconds: ttl,\n insertedAt: Date.now(),\n });\n }\n\n /**\n * Check if a cache entry is stale (past its TTL).\n */\n isStale(tracker: string, cacheKey: string): boolean {\n const entry = this.get(tracker, cacheKey);\n if (!entry) return true;\n\n const fetchedAt = new Date(entry.lastFetchedAt).getTime();\n const age = (Date.now() - fetchedAt) / 1000;\n return age > entry.ttlSeconds;\n }\n\n /**\n * Get the stored ETag for a tracker/key combo (for conditional requests).\n */\n getEtag(tracker: string, cacheKey: string): string | undefined {\n // Check L1 first\n const compositeKey = `${tracker}:${cacheKey}`;\n const l1Entry = this.l1.get(compositeKey);\n if (l1Entry?.etag) return l1Entry.etag;\n\n // Check L2\n const stmt = this.db.prepare(`\n SELECT etag FROM api_cache WHERE tracker = ? AND cache_key = ?\n `);\n const row = stmt.get(tracker, cacheKey) as any;\n return row?.etag || undefined;\n }\n\n /**\n * Invalidate cache for a specific tracker (all keys).\n */\n invalidate(tracker: string): void {\n // L1 — remove all entries for this tracker\n for (const key of this.l1.keys()) {\n if (key.startsWith(`${tracker}:`)) {\n this.l1.delete(key);\n }\n }\n\n // L2\n this.db.prepare('DELETE FROM api_cache WHERE tracker = ?').run(tracker);\n }\n\n /**\n * Invalidate a specific cache key.\n */\n invalidateKey(tracker: string, cacheKey: string): void {\n this.l1.delete(`${tracker}:${cacheKey}`);\n this.db.prepare('DELETE FROM api_cache WHERE tracker = ? AND cache_key = ?').run(tracker, cacheKey);\n }\n\n // --- Rate limit tracking ---\n\n /**\n * Update rate limit info for a tracker.\n */\n updateRateLimit(tracker: string, info: RateLimitInfo): void {\n const stmt = this.db.prepare(`\n INSERT INTO rate_limits (tracker, remaining, total, reset_at, updated_at)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(tracker) DO UPDATE SET\n remaining = excluded.remaining,\n total = excluded.total,\n reset_at = excluded.reset_at,\n updated_at = excluded.updated_at\n `);\n stmt.run(tracker, info.remaining, info.total, info.resetAt, new Date().toISOString());\n }\n\n /**\n * Get rate limit info for a tracker.\n */\n getRateLimit(tracker: string): RateLimitInfo | null {\n const stmt = this.db.prepare(`\n SELECT remaining, total, reset_at FROM rate_limits WHERE tracker = ?\n `);\n const row = stmt.get(tracker) as any;\n if (!row) return null;\n\n return {\n remaining: row.remaining,\n total: row.total,\n resetAt: row.reset_at,\n };\n }\n\n /**\n * Check if we should back off requests for a tracker.\n * Returns true if remaining < 10% of total.\n */\n shouldBackoff(tracker: string): boolean {\n const limit = this.getRateLimit(tracker);\n if (!limit) return false;\n\n // If reset time has passed, no need to back off\n if (new Date(limit.resetAt).getTime() < Date.now()) return false;\n\n return limit.remaining < limit.total * 0.1;\n }\n\n /**\n * Calculate adaptive backoff delay in ms.\n * Returns 0 if no backoff needed.\n */\n getBackoffMs(tracker: string, baseIntervalMs: number): number {\n const limit = this.getRateLimit(tracker);\n if (!limit) return 0;\n\n // If reset time has passed, no backoff\n if (new Date(limit.resetAt).getTime() < Date.now()) return 0;\n\n const ratioRemaining = limit.remaining / limit.total;\n\n if (ratioRemaining > 0.5) return 0; // >50% remaining: no backoff\n if (ratioRemaining > 0.25) return baseIntervalMs; // 25-50%: 2x interval\n if (ratioRemaining > 0.1) return baseIntervalMs * 4; // 10-25%: 5x interval\n return baseIntervalMs * 9; // <10%: 10x interval\n }\n\n /**\n * Calculate suspension delay in ms for an exhausted tracker.\n * Returns 0 when there is no rate limit row, remaining > 0, resetAt has\n * passed, or resetAt is unparseable. Callers clamp to their own ceiling.\n */\n getSuspensionMs(tracker: string, now: number = Date.now()): number {\n const limit = this.getRateLimit(tracker);\n if (!limit) return 0;\n if (limit.remaining > 0) return 0;\n\n const resetMs = new Date(limit.resetAt).getTime();\n if (!Number.isFinite(resetMs)) return 0;\n if (resetMs <= now) return 0;\n\n return resetMs - now;\n }\n\n /**\n * Get cache status for all trackers (for diagnostics endpoint).\n */\n getStatus(): Record<string, {\n remaining: number | null;\n total: number | null;\n lastFetched: string | null;\n cacheKeys: number;\n }> {\n const result: Record<string, any> = {};\n\n for (const tracker of ['github', 'linear', 'rally']) {\n const limit = this.getRateLimit(tracker);\n const countStmt = this.db.prepare(\n 'SELECT COUNT(*) as cnt, MAX(last_fetched_at) as latest FROM api_cache WHERE tracker = ?'\n );\n const row = countStmt.get(tracker) as any;\n\n result[tracker] = {\n remaining: limit?.remaining ?? null,\n total: limit?.total ?? null,\n lastFetched: row?.latest ?? null,\n cacheKeys: row?.cnt ?? 0,\n };\n }\n\n return result;\n }\n\n /**\n * Close the database connection.\n */\n close(): void {\n this.l1.clear();\n this.db.close();\n }\n\n // --- L1 helpers ---\n\n private setL1(compositeKey: string, entry: L1Entry): void {\n // Evict oldest if at capacity\n if (this.l1.size >= this.l1MaxEntries && !this.l1.has(compositeKey)) {\n const oldestKey = this.l1.keys().next().value;\n if (oldestKey) this.l1.delete(oldestKey);\n }\n this.l1.set(compositeKey, entry);\n }\n}\n","/**\n * IssueDataService — Central orchestrator for issue data\n *\n * Replaces the inline fetching in /api/issues with:\n * - Background polling per tracker on independent timers\n * - GitHub REST + ETags (304s are FREE, don't count against rate limit)\n * - Linear incremental fetching via updatedAt filter\n * - Rally TTL-based caching\n * - Change detection + event store push (via onIssuesChanged callback)\n * - Adaptive backoff on rate limit pressure\n * - Instant serve from cache (sub-100ms dashboard loads)\n */\n\nimport { Octokit } from '@octokit/rest';\nimport { Effect } from 'effect';\nimport { existsSync } from 'fs';\nimport { stat } from 'fs/promises';\nimport { join } from 'path';\nimport { mapGitHubStateToCanonical } from '../../../core/state-mapping.js';\nimport { CacheService, DEFAULT_TTLS, parseIntegerHeader } from './cache-service.js';\nimport { getGitHubConfig, getLinearApiKey, getRallyConfig, validateRallyConfig } from './tracker-config.js';\nimport { loadReviewStatusesForIssues, type ReviewStatus } from '../../../lib/review-status.js';\nimport { resolveProjectFromIssueSync } from '../../../lib/projects.js';\nimport { findPlan, readWorkspacePlan } from '../../../lib/vbrief/io.js';\nimport type { VBriefDocument } from '../../../lib/vbrief/types.js';\nimport { loadConfigSync } from '../../../lib/config-yaml.js';\n\n/**\n * Compute bead progress counts from a cached plan document.\n * Exported for testing.\n */\nexport function computeBeadCounts(doc: VBriefDocument | null): { completed: number; total: number } | null {\n const items = doc?.plan?.items ?? [];\n if (items.length === 0) return null;\n return { completed: items.filter((i) => i.status === 'completed').length, total: items.length };\n}\n\n/**\n * Map a raw status string to its canonical state.\n * Exported for testing.\n */\nexport function getCanonicalStatus(status: string | undefined, stateType?: string): string {\n if (!status) return 'backlog';\n const normalized = status.toLowerCase();\n // Direct backlog mappings\n if (normalized === 'backlog' || normalized === 'triage' || normalized === 'unknown') {\n return 'backlog';\n }\n // Other canonical states\n if (normalized === 'todo' || normalized === 'to do' || normalized === 'ready' || normalized === 'unstarted') {\n return 'todo';\n }\n if (normalized === 'in progress' || normalized === 'in_progress' || normalized === 'started' || normalized === 'active' || normalized === 'in planning') {\n return 'in_progress';\n }\n if (normalized === 'in review' || normalized === 'in_review' || normalized === 'review' || normalized === 'qa' || normalized === 'testing') {\n return 'in_review';\n }\n if (normalized === 'verifying' || normalized === 'verifying on main' || normalized === 'verifying_on_main') {\n return 'verifying_on_main';\n }\n if (normalized === 'done' || normalized === 'completed' || normalized === 'closed') {\n return 'done';\n }\n if (normalized === 'canceled' || normalized === 'cancelled' || normalized === 'duplicate' || normalized === \"won't do\" || normalized === 'wontfix') {\n return 'canceled';\n }\n // Fallback: use Linear stateType if available (handles custom status names)\n if (stateType) {\n const typeMap: Record<string, string> = {\n backlog: 'backlog',\n unstarted: 'todo',\n started: 'in_progress',\n completed: 'done',\n canceled: 'canceled',\n cancelled: 'canceled',\n };\n if (typeMap[stateType]) return typeMap[stateType];\n }\n return 'backlog'; // Default fallback\n}\n\nexport function shouldRefreshPlanningStateForIssue(issue: any): boolean {\n const canonical = getCanonicalStatus(issue?.status, issue?.stateType);\n return canonical !== 'done' && canonical !== 'canceled';\n}\n\n// Poll intervals (ms)\nconst POLL_INTERVALS = {\n github: { default: 30_000, min: 15_000, max: 300_000 },\n linear: { default: 30_000, min: 15_000, max: 300_000 },\n rally: { default: 120_000, min: 60_000, max: 600_000 },\n};\n\n// Linear full refresh interval (safety net)\nconst LINEAR_FULL_REFRESH_MS = 5 * 60 * 1000; // 5 minutes\n\ninterface TrackerState {\n timer: ReturnType<typeof setTimeout> | null;\n currentInterval: number;\n lastFetchedIssues: any[];\n lastError: string | null;\n lastFetchedAt: string | null;\n}\n\ninterface GetIssuesCacheEntry {\n issues: any[];\n json: string | null;\n}\n\n/**\n * Cheap change detection for issue arrays.\n * Compares length + the most recent updatedAt timestamp instead of\n * serializing the entire array with JSON.stringify (which caused\n * 600-900ms event loop stalls on large issue sets).\n */\nfunction issuesChanged(newIssues: any[], oldIssues: any[]): boolean {\n if (newIssues.length !== oldIssues.length) return true;\n if (newIssues.length === 0) return false;\n\n // Find the most recent updatedAt in each set\n let newMax = '';\n let oldMax = '';\n for (const issue of newIssues) {\n if (issue.updatedAt && issue.updatedAt > newMax) newMax = issue.updatedAt;\n }\n for (const issue of oldIssues) {\n if (issue.updatedAt && issue.updatedAt > oldMax) oldMax = issue.updatedAt;\n }\n return newMax !== oldMax;\n}\n\n/**\n * Map normalized IssueState (open/in_progress/closed) to canonical dashboard status.\n * The Rally tracker already normalizes raw Rally states to IssueState in rally.ts.\n */\nfunction mapRallyStateToCanonical(issueState: string): string {\n if (!issueState) return 'todo';\n const stateLower = issueState.toLowerCase();\n if (stateLower === 'in_progress') return 'in_progress';\n if (stateLower === 'closed') return 'done';\n // 'open' and anything unrecognized → 'todo'\n return 'todo';\n}\n\n/**\n * Compute planning-state for an issue via cheap filesystem checks.\n *\n * `isPlanningComplete()` reads and JSON-parses the workspace's plan.vbrief.json\n * file. Calling it for every issue on every getSnapshot — which happens once\n * per WS-RPC bootstrap and then every emitted snapshot — would do 870+ sync\n * disk reads per call and starve the dashboard event loop until WS clients\n * timeout. Cache by plan-file mtime so the read happens at most once per file\n * change.\n */\ninterface PlanningStateCacheEntry {\n result: {\n hasPlan: boolean;\n hasBeads: boolean;\n planningComplete: boolean;\n workspacePath: string;\n beadCounts: { completed: number; total: number } | null;\n };\n planMtimeMs: number; // -1 when no plan file existed at compute time\n continueMtimeMs: number; // -1 when no continue.json existed at compute time\n}\nconst planningStateCache = new Map<string, PlanningStateCacheEntry>();\n\nconst planningStateRefreshInFlight = new Set<string>();\nconst PLANNING_REFRESH_CONCURRENCY = 4;\nconst PLANNING_FINISHED_STATUSES = new Set(['proposed', 'approved', 'pending', 'running', 'completed', 'blocked']);\nconst DAY_MS = 86_400_000;\n\nfunction getCachedPlanningState(identifier: string): {\n hasPlan: boolean;\n hasBeads: boolean;\n planningComplete: boolean;\n workspacePath: string;\n beadCounts: { completed: number; total: number } | null;\n} {\n try {\n const resolved = resolveProjectFromIssueSync(identifier);\n const projectPath = resolved?.projectPath ?? '';\n if (!projectPath) {\n return { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null };\n }\n const issueLower = identifier.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n return planningStateCache.get(identifier)?.result\n ?? { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath, beadCounts: null };\n } catch {\n return { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null };\n }\n}\n\nasync function refreshPlanningState(identifier: string): Promise<boolean> {\n if (planningStateRefreshInFlight.has(identifier)) return false;\n planningStateRefreshInFlight.add(identifier);\n try {\n const resolved = resolveProjectFromIssueSync(identifier);\n const projectPath = resolved?.projectPath ?? '';\n if (!projectPath) return updatePlanningStateCache(identifier, {\n result: { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null },\n planMtimeMs: -1,\n continueMtimeMs: -1,\n });\n\n const issueLower = identifier.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (!existsSync(workspacePath)) {\n return updatePlanningStateCache(identifier, {\n result: { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath, beadCounts: null },\n planMtimeMs: -1,\n continueMtimeMs: -1,\n });\n }\n\n const planPath = await Effect.runPromise(findPlan(workspacePath));\n let planMtimeMs = -1;\n if (planPath) {\n try {\n planMtimeMs = (await stat(planPath)).mtimeMs;\n } catch {\n planMtimeMs = -1;\n }\n }\n\n const continuePath = join(workspacePath, '.pan', 'continue.json');\n let continueMtimeMs = -1;\n try {\n continueMtimeMs = (await stat(continuePath)).mtimeMs;\n } catch {\n continueMtimeMs = -1;\n }\n\n const cached = planningStateCache.get(identifier);\n if (cached && cached.planMtimeMs === planMtimeMs && cached.continueMtimeMs === continueMtimeMs && cached.result.workspacePath === workspacePath) {\n return false;\n }\n\n const doc = planPath ? await Effect.runPromise(readWorkspacePlan(workspacePath)) : null;\n const planningComplete = doc?.plan?.status ? PLANNING_FINISHED_STATUSES.has(doc.plan.status) : false;\n const beadCounts = computeBeadCounts(doc);\n return updatePlanningStateCache(identifier, {\n result: { hasPlan: planPath !== null, hasBeads: planningComplete, planningComplete, workspacePath, beadCounts },\n planMtimeMs,\n continueMtimeMs,\n });\n } catch {\n return updatePlanningStateCache(identifier, {\n result: { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null },\n planMtimeMs: -1,\n continueMtimeMs: -1,\n });\n } finally {\n planningStateRefreshInFlight.delete(identifier);\n }\n}\n\nfunction updatePlanningStateCache(identifier: string, entry: PlanningStateCacheEntry): boolean {\n const prev = planningStateCache.get(identifier);\n const changed = !prev\n || prev.planMtimeMs !== entry.planMtimeMs\n || prev.continueMtimeMs !== entry.continueMtimeMs\n || JSON.stringify(prev.result) !== JSON.stringify(entry.result);\n planningStateCache.set(identifier, entry);\n return changed;\n}\n\nexport class IssueDataService {\n private cache: CacheService;\n private loadConfig: typeof loadConfigSync;\n private trackers: Record<string, TrackerState> = {};\n private linearLastFullRefresh = 0;\n private started = false;\n private shadowStateModule: any = null;\n /** In-memory snapshot of shadow states, refreshed asynchronously. The hot\n * path (`getIssues`) reads from this map — no disk I/O on every request. */\n private shadowStatesCache: Map<string, any> = new Map();\n private reviewStatusesCache: Record<string, ReviewStatus> = {};\n private _onIssuesChanged: ((issues: unknown[]) => void) | null = null;\n private planningSnapshotQueued = false;\n private planningRefreshQueue: string[] = [];\n private planningRefreshQueued = new Set<string>();\n private planningRefreshActive = 0;\n private reviewStatusRefreshQueued = false;\n private reviewStatusRefreshIssueIds = new Set<string>();\n private getIssuesCache = new Map<string, GetIssuesCacheEntry>();\n\n /** Register a callback invoked whenever issue data changes (PAN-433). */\n onIssuesChanged(fn: (issues: unknown[]) => void): void {\n this._onIssuesChanged = fn;\n }\n\n constructor(cache: CacheService, options?: { loadConfig?: typeof loadConfigSync }) {\n this.cache = cache;\n this.loadConfig = options?.loadConfig ?? loadConfigSync;\n\n for (const tracker of ['github', 'linear', 'rally'] as const) {\n this.trackers[tracker] = {\n timer: null,\n currentInterval: POLL_INTERVALS[tracker].default,\n lastFetchedIssues: [],\n lastError: null,\n lastFetchedAt: null,\n };\n }\n }\n\n /**\n * Start background polling. Returns immediately after loading cached data.\n * API fetches run in the background and push incremental updates.\n *\n * `skipPolling` (PAN-1817): load the SQLite cache and serve read-only, but\n * start NO recurring tracker fetches. Peer dashboards running inside workspace\n * containers (OVERDECK_DISABLE_DEACON=1) MUST use this — otherwise every\n * workspace container becomes an independent Linear/GitHub poller hammering\n * the single shared API key, which exhausted Linear's 2500/hr quota when\n * ~17 container pollers ran at once.\n */\n async start(options?: { skipPolling?: boolean }): Promise<void> {\n if (this.started) return;\n this.started = true;\n\n // Pre-load shadow state module\n await this.ensureShadowStateLoaded();\n\n // Load any cached data from SQLite so getIssues() works instantly\n this.loadCachedData();\n\n // Push snapshot immediately with stale cached data so read model has\n // something to work with before the background fetches complete.\n this.pushSnapshot();\n\n if (options?.skipPolling) {\n console.log('[IssueDataService] Tracker polling DISABLED (peer dashboard / OVERDECK_DISABLE_DEACON=1) — serving cached issues only, zero tracker API calls (PAN-1817)');\n return;\n }\n\n // Kick off all tracker fetches in the background — do NOT await.\n // Each poll calls pushUpdated() when done → incremental client updates.\n void Promise.allSettled([\n this.pollGitHub(),\n this.pollLinear(),\n this.pollRally(),\n ]).then(() => {\n // Final snapshot push after all initial fetches complete\n this.pushSnapshot();\n // Start recurring timers (after first fetch completes)\n this.scheduleNext('github');\n this.scheduleNext('linear');\n this.scheduleNext('rally');\n });\n }\n\n /**\n * Stop all polling timers.\n */\n stop(): void {\n this.started = false;\n for (const state of Object.values(this.trackers)) {\n if (state.timer) {\n clearTimeout(state.timer);\n state.timer = null;\n }\n }\n }\n\n /**\n * Clear all cached issue data and trigger a fresh re-fetch from all trackers.\n */\n async clearCacheAndRefresh(): Promise<void> {\n // Clear SQLite + L1 cache for all trackers\n for (const tracker of ['github', 'linear', 'rally']) {\n this.cache.invalidate(tracker);\n this.trackers[tracker].lastFetchedIssues = [];\n this.trackers[tracker].lastFetchedAt = null;\n this.trackers[tracker].lastError = null;\n }\n console.log('[IssueDataService] Cache cleared — re-fetching all trackers');\n // Re-fetch all trackers\n await Promise.allSettled([\n this.pollGitHub(),\n this.pollLinear(),\n this.pollRally(),\n ]);\n this.pushSnapshot();\n }\n\n /**\n * Look up which tracker an issue belongs to by its identifier.\n * Returns 'github' | 'linear' | 'rally' | null.\n */\n getIssueSource(identifier: string): 'github' | 'linear' | 'rally' | null {\n const id = identifier.toLowerCase();\n for (const [trackerName, state] of Object.entries(this.trackers)) {\n for (const issue of state.lastFetchedIssues) {\n if ((issue.identifier || '').toLowerCase() === id) {\n return trackerName as 'github' | 'linear' | 'rally';\n }\n }\n }\n return null;\n }\n\n /**\n * Get all issues from cache. Applies shadow state and filtering.\n * This is the hot path — must be fast.\n */\n getIssues(options?: { cycle?: string; includeCompleted?: boolean }): any[] {\n return this.getIssuesCacheEntry(options).issues;\n }\n\n getIssuesJson(options?: { cycle?: string; includeCompleted?: boolean }): string {\n const entry = this.getIssuesCacheEntry(options);\n if (entry.json === null) {\n entry.json = JSON.stringify(entry.issues);\n }\n return entry.json;\n }\n\n private getIssuesCacheEntry(options?: { cycle?: string; includeCompleted?: boolean }): GetIssuesCacheEntry {\n const key = this.getIssuesCacheKey(options);\n const cached = this.getIssuesCache.get(key);\n if (cached) return cached;\n\n const issues = this.computeIssues(options);\n if (process.env.NODE_ENV !== 'production') {\n Object.freeze(issues);\n }\n const entry: GetIssuesCacheEntry = { issues, json: null };\n this.getIssuesCache.set(key, entry);\n return entry;\n }\n\n private getIssuesCacheKey(options?: { cycle?: string; includeCompleted?: boolean }): string {\n return `${options?.cycle ?? 'current'}|${options?.includeCompleted === true}`;\n }\n\n private invalidateGetIssuesCache(): void {\n this.getIssuesCache.clear();\n }\n\n private computeIssues(options?: { cycle?: string; includeCompleted?: boolean }): any[] {\n let allIssues = [\n ...this.trackers.github.lastFetchedIssues,\n ...this.trackers.linear.lastFetchedIssues,\n ...this.trackers.rally.lastFetchedIssues,\n ];\n\n // Merge shadow state from the in-memory cache. The cache is refreshed\n // asynchronously by `refreshShadowStatesCache()` — we never hit disk here,\n // keeping `getIssues()` (a hot path) off the event-loop-blocking path.\n try {\n allIssues = allIssues.map(issue => {\n const shadowState = this.shadowStatesCache.get(issue.identifier.toLowerCase());\n if (shadowState) {\n return {\n ...issue,\n shadowStatus: shadowState.shadowStatus,\n targetCanonicalState: shadowState.targetCanonicalState,\n shadowedAt: shadowState.shadowedAt,\n shadowTrackerStatus: shadowState.trackerStatus,\n };\n }\n return { ...issue, shadowStatus: null, targetCanonicalState: null };\n });\n } catch (e) {\n allIssues = allIssues.map(issue => ({ ...issue, shadowStatus: null }));\n }\n\n // Show all completed issues (label-based dismissal will be added later)\n\n // Apply cycle filter using canonical status mapping\n const cycle = options?.cycle ?? 'current';\n if (cycle === 'current') {\n // Current cycle: exclude Backlog and Canceled items, only show active cycle work\n allIssues = allIssues.filter(issue => {\n const canonical = getCanonicalStatus(issue.status);\n return canonical !== 'backlog' && canonical !== 'canceled';\n });\n } else if (cycle === 'backlog') {\n // Backlog view: only show Backlog items (including Triage, Unknown)\n allIssues = allIssues.filter(issue => {\n const canonical = getCanonicalStatus(issue.status, issue.stateType);\n return canonical === 'backlog';\n });\n } else if (cycle === 'canceled') {\n // Canceled view: only show Canceled items (Canceled, Duplicate, Won't Do)\n allIssues = allIssues.filter(issue => {\n const canonical = getCanonicalStatus(issue.status);\n return canonical === 'canceled';\n });\n }\n // cycle === 'all': no additional filtering, show everything\n\n // Augment with mergeStatus from the asynchronous review-status cache.\n allIssues = allIssues.map(issue => {\n const canonical = getCanonicalStatus(issue.state ?? issue.canonicalStatus ?? issue.status, issue.stateType);\n const key = issue.identifier?.toUpperCase();\n const rs = key ? this.reviewStatusesCache[key] : null;\n if (rs?.mergeStatus) {\n const issueWithMerge = { ...issue, mergeStatus: rs.mergeStatus };\n if (canonical === 'done' && issueWithMerge.mergeStatus !== 'merged') {\n return { ...issueWithMerge, mergeStatus: 'merged' };\n }\n if (rs.mergeStatus === 'merged' && canonical !== 'done' && canonical !== 'canceled') {\n return { ...issueWithMerge, status: 'Verifying', canonicalStatus: 'verifying_on_main', state: 'verifying_on_main' };\n }\n return issueWithMerge;\n }\n if (canonical === 'done' && issue.mergeStatus && issue.mergeStatus !== 'merged') {\n return { ...issue, mergeStatus: 'merged' };\n }\n return issue;\n });\n\n // Enrich with cached planning-state only. Refresh work is scheduled by tracker updates.\n allIssues = allIssues.map(issue => {\n const ps = getCachedPlanningState(issue.identifier);\n return {\n ...issue,\n hasPlan: ps.hasPlan,\n hasBeads: ps.hasBeads,\n planningComplete: ps.planningComplete,\n workspacePath: ps.workspacePath || undefined,\n beadCounts: ps.beadCounts,\n };\n });\n\n const updatedAtMs = new Map<any, number>();\n for (const issue of allIssues) {\n updatedAtMs.set(issue, Date.parse(issue.updatedAt) || 0);\n }\n allIssues.sort((a, b) => (updatedAtMs.get(b) ?? 0) - (updatedAtMs.get(a) ?? 0));\n\n return allIssues;\n }\n\n /**\n * Invalidate a tracker's cache and trigger immediate re-poll.\n * Called after mutations (move-status, label changes, etc.)\n */\n /**\n * Immediately patch a cached issue and push the update to all clients.\n * Use this after any state mutation so the dashboard reflects the change\n * instantly without waiting for the next poll cycle.\n *\n * @param identifier - Issue identifier (e.g. \"MIN-734\", \"PAN-302\")\n * @param patch - Fields to merge into the cached issue object\n */\n patchIssue(identifier: string, patch: Record<string, any>): void {\n const id = identifier.toLowerCase();\n for (const state of Object.values(this.trackers)) {\n const idx = state.lastFetchedIssues.findIndex(\n (i: any) => (i.identifier || '').toLowerCase() === id\n );\n if (idx !== -1) {\n state.lastFetchedIssues[idx] = { ...state.lastFetchedIssues[idx], ...patch };\n this.pushUpdated();\n return;\n }\n }\n // Issue not in cache yet (e.g. just created) — trigger a full refresh instead\n const source = patch.source || 'linear';\n this.invalidateTracker(source).catch(() => {});\n }\n\n async invalidateTracker(tracker: string): Promise<void> {\n this.cache.invalidate(tracker);\n\n // Force full refresh on next poll (not incremental) so new issues\n // added to the cycle externally are discovered immediately\n if (tracker === 'linear') {\n this.linearLastFullRefresh = 0;\n }\n\n // Cancel current timer and fetch immediately\n const state = this.trackers[tracker];\n if (state?.timer) {\n clearTimeout(state.timer);\n state.timer = null;\n }\n\n switch (tracker) {\n case 'github': await this.pollGitHub(); break;\n case 'linear': await this.pollLinear(); break;\n case 'rally': await this.pollRally(); break;\n }\n\n this.pushSnapshot();\n this.scheduleNext(tracker);\n }\n\n /**\n * Get diagnostics for /api/cache-status endpoint.\n */\n getDiagnostics(): Record<string, any> {\n const result: Record<string, any> = {};\n for (const [tracker, state] of Object.entries(this.trackers)) {\n const limit = this.cache.getRateLimit(tracker);\n const intervals = POLL_INTERVALS[tracker as keyof typeof POLL_INTERVALS];\n const backoffMs = intervals ? this.cache.getBackoffMs(tracker, intervals.default) : 0;\n const suspendMs = this.cache.getSuspensionMs(tracker);\n result[tracker] = {\n remaining: limit?.remaining ?? null,\n total: limit?.total ?? null,\n backoffMs,\n suspendedUntil: suspendMs > 0 ? new Date(Date.now() + suspendMs).toISOString() : null,\n rateLimited: suspendMs > 0,\n pollInterval: state.currentInterval,\n lastFetched: state.lastFetchedAt,\n lastError: state.lastError,\n issueCount: state.lastFetchedIssues.length,\n };\n }\n return result;\n }\n\n // ---------------------------------------------------------------\n // Private: polling methods\n // ---------------------------------------------------------------\n\n private scheduleNext(tracker: string): void {\n const state = this.trackers[tracker];\n if (!this.started || !state) return;\n\n // Compute effective interval with backoff\n const intervals = POLL_INTERVALS[tracker as keyof typeof POLL_INTERVALS];\n if (!intervals) return;\n\n let delayMs: number;\n try {\n const backoffMs = this.cache.getBackoffMs(tracker, intervals.default);\n const effectiveInterval = Math.min(\n Math.max(intervals.default + backoffMs, intervals.min),\n intervals.max\n );\n\n // If the tracker is exhausted, suspend polling until the rate-limit window resets.\n // This bypasses intervals.max so we don't wake up before the quota resets.\n const suspendMs = this.cache.getSuspensionMs(tracker);\n delayMs = effectiveInterval;\n if (suspendMs > 0) {\n delayMs = Math.min(suspendMs, 3_600_000);\n console.warn(`[IssueDataService] ${tracker} tracker suspended until rate-limit reset; next poll in ${delayMs}ms`);\n }\n } catch (err: any) {\n console.error(`[IssueDataService] Cache read failed for ${tracker}; falling back to default interval:`, err.message);\n delayMs = intervals.default;\n }\n state.currentInterval = delayMs;\n\n state.timer = setTimeout(async () => {\n try {\n switch (tracker) {\n case 'github': await this.pollGitHub(); break;\n case 'linear': await this.pollLinear(); break;\n case 'rally': await this.pollRally(); break;\n }\n } catch (err: any) {\n console.error(`[IssueDataService] Error polling ${tracker}:`, err.message);\n state.lastError = err.message;\n }\n this.scheduleNext(tracker);\n }, delayMs);\n }\n\n private async ensureShadowStateLoaded(): Promise<void> {\n if (this.shadowStateModule) return;\n try {\n this.shadowStateModule = await import('../../../lib/shadow-state.js');\n await this.refreshShadowStatesCache();\n } catch {\n // Shadow state not available — issues will work without it\n }\n }\n\n /**\n * Populate the in-memory shadow-state cache by reading all shadow files\n * off the event loop (via fs/promises). Call after any write that may\n * have changed shadow state for any issue.\n */\n async refreshShadowStatesCache(): Promise<void> {\n if (!this.shadowStateModule) return;\n try {\n const states: Array<{ issueId: string; [k: string]: any }> =\n await this.shadowStateModule.listShadowedIssues();\n const next = new Map<string, any>();\n for (const state of states) {\n next.set(state.issueId.toLowerCase(), state);\n }\n this.shadowStatesCache = next;\n } catch {\n // Non-fatal — keep the previous cache\n }\n }\n\n private loadCachedData(): void {\n // Build a lookup of repo → prefix from current config for re-stamping stale identifiers\n const repoPrefixMap = new Map<string, string>();\n try {\n const ghConfig = getGitHubConfig();\n if (ghConfig) {\n for (const { owner, repo, prefix } of ghConfig.repos) {\n repoPrefixMap.set(`${owner}/${repo}`, prefix || repo.toUpperCase());\n }\n }\n } catch { /* ignore */ }\n\n for (const tracker of ['github', 'linear', 'rally']) {\n const cached = this.cache.getStale(tracker, 'issues');\n if (cached?.data) {\n // Sanitize stale Rally cache: rawTrackerState may be an object from pre-PAN-201 data\n if (tracker === 'rally') {\n let sanitizedCount = 0;\n cached.data = cached.data.map((issue: any) => {\n if (typeof issue.rawTrackerState === 'object' && issue.rawTrackerState !== null) {\n sanitizedCount++;\n return {\n ...issue,\n rawTrackerState: issue.rawTrackerState.Name || issue.rawTrackerState._refObjectName || 'Defined',\n };\n }\n return issue;\n });\n if (sanitizedCount > 0) {\n console.warn(`[IssueDataService] Rally cache: sanitized ${sanitizedCount} issues with object rawTrackerState (PAN-201)`);\n }\n }\n // Re-stamp GitHub identifiers in case prefix config changed since cache was written\n if (tracker === 'github') {\n cached.data = cached.data.map((issue: any) => {\n const repoKey = issue.sourceRepo;\n const prefix = repoKey ? repoPrefixMap.get(repoKey) : undefined;\n if (prefix) {\n // Extract issue number from id (github-owner-repo-NUMBER) or identifier (PREFIX-NUMBER)\n const issueNum = issue.id?.match(/-(\\d+)$/)?.[1] || issue.identifier?.match(/-(\\d+)$/)?.[1];\n if (issueNum) {\n const expectedId = `${prefix}-${issueNum}`;\n if (issue.identifier !== expectedId) {\n return { ...issue, identifier: expectedId };\n }\n }\n }\n return issue;\n });\n }\n this.trackers[tracker].lastFetchedIssues = cached.data;\n this.trackers[tracker].lastFetchedAt = cached.lastFetchedAt;\n }\n }\n const cachedIssues = this.getCachedTrackerIssues();\n this.schedulePlanningRefreshForIssues(cachedIssues);\n this.scheduleReviewStatusRefreshForIssues(cachedIssues);\n }\n\n private pushSnapshot(): void {\n this.invalidateGetIssuesCache();\n this._onIssuesChanged?.(this.getIssues());\n }\n\n private pushUpdated(): void {\n this.invalidateGetIssuesCache();\n const cachedIssues = this.getCachedTrackerIssues();\n this.schedulePlanningRefreshForIssues(cachedIssues);\n this.scheduleReviewStatusRefreshForIssues(cachedIssues);\n this._onIssuesChanged?.(this.getIssues());\n }\n\n private getCachedTrackerIssues(): any[] {\n return [\n ...this.trackers.github.lastFetchedIssues,\n ...this.trackers.linear.lastFetchedIssues,\n ...this.trackers.rally.lastFetchedIssues,\n ];\n }\n\n private schedulePlanningRefreshForIssues(issues: any[]): void {\n for (const issue of issues) {\n const identifier = typeof issue?.identifier === 'string' ? issue.identifier : '';\n if (!shouldRefreshPlanningStateForIssue(issue)) continue;\n if (!identifier || this.planningRefreshQueued.has(identifier) || planningStateRefreshInFlight.has(identifier)) continue;\n this.planningRefreshQueued.add(identifier);\n this.planningRefreshQueue.push(identifier);\n }\n this.drainPlanningRefreshQueue();\n }\n\n private drainPlanningRefreshQueue(): void {\n while (this.planningRefreshActive < PLANNING_REFRESH_CONCURRENCY && this.planningRefreshQueue.length > 0) {\n const identifier = this.planningRefreshQueue.shift()!;\n this.planningRefreshQueued.delete(identifier);\n this.planningRefreshActive++;\n void refreshPlanningState(identifier).then((changed) => {\n if (changed) this.queuePlanningSnapshot();\n }).finally(() => {\n this.planningRefreshActive--;\n this.drainPlanningRefreshQueue();\n });\n }\n }\n\n private queuePlanningSnapshot(): void {\n if (this.planningSnapshotQueued) return;\n this.planningSnapshotQueued = true;\n setTimeout(() => {\n this.planningSnapshotQueued = false;\n if (this.started) this.pushSnapshot();\n }, 50);\n }\n\n private scheduleReviewStatusRefreshForIssues(issues: any[]): void {\n for (const issue of issues) {\n const identifier = typeof issue?.identifier === 'string' ? issue.identifier : '';\n if (identifier) this.reviewStatusRefreshIssueIds.add(identifier);\n }\n if (this.reviewStatusRefreshQueued || this.reviewStatusRefreshIssueIds.size === 0) return;\n this.reviewStatusRefreshQueued = true;\n setImmediate(() => {\n this.reviewStatusRefreshQueued = false;\n const issueIds = [...this.reviewStatusRefreshIssueIds];\n this.reviewStatusRefreshIssueIds.clear();\n try {\n this.reviewStatusesCache = {\n ...this.reviewStatusesCache,\n ...loadReviewStatusesForIssues(issueIds),\n };\n if (this.started) this.pushSnapshot();\n } catch {\n // review-status store may not exist yet\n }\n });\n }\n\n private pushMeta(): void {\n // Diagnostics are served via GET /api/issues/diagnostics — no push needed\n }\n\n // ---------------------------------------------------------------\n // GitHub polling — uses Octokit REST + ETags (304 = FREE)\n // ---------------------------------------------------------------\n\n private async pollGitHub(): Promise<void> {\n const config = getGitHubConfig();\n if (!config) {\n this.trackers.github.lastFetchedIssues = [];\n return;\n }\n\n const allIssues: any[] = [];\n const octokit = new Octokit({ auth: config.token });\n\n for (const { owner, repo, prefix } of config.repos) {\n try {\n // Fetch open issues with ETag support\n const openIssues = await this.fetchGitHubRepoIssues(\n octokit, owner, repo, 'open', prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, ''),\n `github:open:${owner}/${repo}`\n );\n\n // Fetch recently closed issues\n const closedIssues = await this.fetchGitHubRepoIssues(\n octokit, owner, repo, 'closed', prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, ''),\n `github:closed:${owner}/${repo}`\n );\n\n allIssues.push(...openIssues, ...closedIssues);\n } catch (error: any) {\n console.error(`[IssueDataService] Error fetching GitHub issues for ${owner}/${repo}:`, error.message);\n this.trackers.github.lastError = error.message;\n }\n }\n\n // Check if data actually changed (cheap length + updatedAt check)\n const oldData = this.trackers.github.lastFetchedIssues;\n const changed = issuesChanged(allIssues, oldData);\n\n this.trackers.github.lastFetchedIssues = allIssues;\n this.trackers.github.lastFetchedAt = new Date().toISOString();\n this.trackers.github.lastError = null;\n\n // Persist to cache\n this.cache.set('github', 'issues', allIssues, { ttlSeconds: DEFAULT_TTLS.github });\n\n if (changed) {\n console.log(`[IssueDataService] GitHub: ${allIssues.length} issues (changed)`);\n this.pushUpdated();\n this.pushMeta();\n }\n }\n\n private async fetchGitHubRepoIssues(\n octokit: Octokit,\n owner: string,\n repo: string,\n state: 'open' | 'closed',\n issuePrefix: string,\n cacheKey: string,\n ): Promise<any[]> {\n // Get stored ETag for conditional request\n const cachedEtag = this.cache.getEtag('github', cacheKey);\n\n const requestParams: any = {\n owner,\n repo,\n state,\n per_page: 100,\n sort: 'updated' as const,\n direction: 'desc' as const,\n };\n\n // Only send If-None-Match when we have a cached ETag\n if (cachedEtag) {\n requestParams.headers = { 'If-None-Match': cachedEtag };\n }\n\n // Fetch recently updated closed issues; SQLite L2 cache keeps older closed\n // issues visible within TTL across restarts while the windowed fetch runs.\n if (state === 'closed') {\n requestParams.per_page = 100;\n const windowDays = this.loadConfig().config.issues.closedWindowDays;\n requestParams.since = new Date(Date.now() - windowDays * DAY_MS).toISOString();\n }\n\n try {\n // Use paginate to fetch ALL pages (not just the first 100)\n let newEtag: string | undefined;\n const allData = await octokit.paginate(octokit.issues.listForRepo, requestParams, (response) => {\n // Extract rate limit from each response\n const remaining = parseInt(response.headers['x-ratelimit-remaining'] as string);\n const total = parseInt(response.headers['x-ratelimit-limit'] as string);\n const resetAt = new Date(parseInt(response.headers['x-ratelimit-reset'] as string) * 1000).toISOString();\n\n if (!isNaN(remaining) && !isNaN(total)) {\n this.cache.updateRateLimit('github', { remaining, total, resetAt });\n }\n\n // Store ETag from first page (used for conditional requests on next poll)\n if (!newEtag && response.headers.etag) {\n newEtag = response.headers.etag as string;\n }\n\n return response.data;\n });\n\n // Filter out PRs (they have pull_request key)\n const issues = allData.filter((issue: any) => !issue.pull_request);\n\n // Format issues to match dashboard schema\n const formatted = issues.map((issue: any) => {\n const labelNames = issue.labels?.map((l: any) => typeof l === 'string' ? l : l.name) || [];\n const canonicalStatus = mapGitHubStateToCanonical(issue.state || '', labelNames);\n const identifier = `${issuePrefix}-${issue.number}`;\n\n const firstAssignee = issue.assignees?.[0] || issue.assignee;\n\n return {\n id: `github-${owner}-${repo}-${issue.number}`,\n identifier,\n title: issue.title,\n description: issue.body || '',\n author: issue.user?.login || '',\n status: canonicalStatus === 'todo' ? 'Todo' :\n canonicalStatus === 'in_progress' ? 'In Progress' :\n canonicalStatus === 'in_review' ? 'In Review' :\n canonicalStatus === 'verifying_on_main' ? 'Verifying' :\n canonicalStatus === 'done' ? 'Done' :\n canonicalStatus === 'backlog' ? 'Backlog' : 'Todo',\n canonicalStatus,\n state: canonicalStatus,\n priority: labelNames.some((l: string) => l.includes('priority') && l.includes('high')) ? 2 :\n labelNames.some((l: string) => l.includes('priority') && l.includes('urgent')) ? 1 :\n labelNames.some((l: string) => l.includes('priority') && l.includes('low')) ? 4 : 3,\n assignee: firstAssignee ? {\n name: firstAssignee.login,\n email: `${firstAssignee.login}@github`,\n } : undefined,\n labels: labelNames,\n url: issue.html_url,\n createdAt: issue.created_at,\n updatedAt: issue.updated_at,\n completedAt: issue.closed_at,\n project: {\n id: `github-${owner}-${repo}`,\n name: `${owner}/${repo}`,\n color: '#333',\n icon: 'github',\n },\n source: 'github',\n sourceRepo: `${owner}/${repo}`,\n };\n });\n\n // Cache with ETag\n this.cache.set('github', cacheKey, formatted, {\n etag: newEtag,\n ttlSeconds: DEFAULT_TTLS.github,\n });\n\n return formatted;\n } catch (err: any) {\n // 304 Not Modified — return cached data (this is FREE, no rate limit cost)\n if (err.status === 304) {\n const cached = this.cache.getStale('github', cacheKey);\n if (!cached?.data) return [];\n // Re-stamp identifiers in case the prefix changed since the cache was written\n return cached.data.map((issue: any) => {\n const issueNum = issue.id?.match(/-(\\d+)$/)?.[1] || issue.identifier?.match(/-(\\d+)$/)?.[1];\n if (issueNum) {\n const expectedId = `${issuePrefix}-${issueNum}`;\n return issue.identifier === expectedId ? issue : { ...issue, identifier: expectedId };\n }\n return issue;\n });\n }\n throw err;\n }\n }\n\n // ---------------------------------------------------------------\n // Linear polling — TTL + incremental updatedAt\n // ---------------------------------------------------------------\n\n private async pollLinear(): Promise<void> {\n const apiKey = getLinearApiKey();\n if (!apiKey) {\n this.trackers.linear.lastFetchedIssues = [];\n return;\n }\n\n const now = Date.now();\n const needsFullRefresh = now - this.linearLastFullRefresh > LINEAR_FULL_REFRESH_MS;\n\n // Get the most recent updatedAt from cached issues for incremental fetch\n let sinceUpdatedAt: string | null = null;\n if (!needsFullRefresh && this.trackers.linear.lastFetchedIssues.length > 0) {\n const maxUpdated = this.trackers.linear.lastFetchedIssues.reduce((max: string, issue: any) => {\n return issue.updatedAt > max ? issue.updatedAt : max;\n }, '');\n if (maxUpdated) sinceUpdatedAt = maxUpdated;\n }\n\n try {\n const fetchedIssues = await this.fetchLinearIssues(apiKey, sinceUpdatedAt);\n\n let allIssues: any[];\n if (sinceUpdatedAt && fetchedIssues.length > 0) {\n // Incremental: merge new/updated issues into existing list\n const existingMap = new Map(\n this.trackers.linear.lastFetchedIssues.map((i: any) => [i.identifier, i])\n );\n for (const issue of fetchedIssues) {\n existingMap.set(issue.identifier, issue);\n }\n allIssues = Array.from(existingMap.values());\n } else if (needsFullRefresh || sinceUpdatedAt === null) {\n // Full refresh\n allIssues = fetchedIssues;\n this.linearLastFullRefresh = now;\n } else {\n // No new data from incremental fetch\n allIssues = this.trackers.linear.lastFetchedIssues;\n }\n\n const oldData = this.trackers.linear.lastFetchedIssues;\n const changed = issuesChanged(allIssues, oldData);\n\n this.trackers.linear.lastFetchedIssues = allIssues;\n this.trackers.linear.lastFetchedAt = new Date().toISOString();\n this.trackers.linear.lastError = null;\n\n this.cache.set('linear', 'issues', allIssues, { ttlSeconds: DEFAULT_TTLS.linear });\n\n if (changed) {\n console.log(`[IssueDataService] Linear: ${allIssues.length} issues (changed)`);\n this.pushUpdated();\n this.pushMeta();\n }\n } catch (err: any) {\n console.error('[IssueDataService] Linear poll error:', err.message);\n this.trackers.linear.lastError = err.message;\n }\n }\n\n private normalizeLinearResetAt(resetValue: number | null): string | null {\n if (resetValue === null) return null;\n // Linear sends reset as Unix milliseconds, but other trackers use Unix seconds.\n // Defensive normalization: values below 1e12 are seconds, otherwise milliseconds.\n const epochMs = resetValue < 1_000_000_000_000 ? resetValue * 1000 : resetValue;\n return new Date(epochMs).toISOString();\n }\n\n private recordLinearExhaustion(response: Response, nowMs: number): void {\n const existing = this.cache.getRateLimit('linear');\n const total =\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-limit') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-limit') ??\n existing?.total ??\n 2500;\n\n let resetAt = this.normalizeLinearResetAt(\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-reset') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-reset')\n );\n if (!resetAt) {\n const retryAfter = parseIntegerHeader(response.headers, 'retry-after');\n if (retryAfter !== null) {\n resetAt = new Date(nowMs + retryAfter * 1000).toISOString();\n } else {\n resetAt = new Date(nowMs + 3_600_000).toISOString();\n }\n }\n\n this.cache.updateRateLimit('linear', { remaining: 0, total, resetAt });\n }\n\n private async fetchLinearIssues(apiKey: string, sinceUpdatedAt: string | null): Promise<any[]> {\n const allIssues: any[] = [];\n let hasMore = true;\n let cursor: string | null | undefined;\n\n // Build filter conditions\n const filterConditions: string[] = [];\n // Scope to active cycle only — completed/canceled filtering is handled\n // by getIssues() post-filter, NOT here. The GraphQL query must fetch\n // completed issues so that: (1) the \"Include completed\" toggle works,\n // (2) incremental updates correctly reflect state transitions, and\n // (3) internal getIssues() callers can look up recently-completed issues.\n filterConditions.push('cycle: { isActive: { eq: true } }');\n\n // Incremental: only issues updated after sinceUpdatedAt\n if (sinceUpdatedAt) {\n filterConditions.push(`updatedAt: { gt: \"${sinceUpdatedAt}\" }`);\n }\n\n let filterClause = '';\n if (filterConditions.length === 1) {\n filterClause = `filter: { ${filterConditions[0]} }`;\n } else if (filterConditions.length > 1) {\n filterClause = `filter: { and: [${filterConditions.map(c => `{ ${c} }`).join(', ')}] }`;\n }\n\n while (hasMore) {\n const query = `\n query GetIssues($after: String) {\n issues(first: 100, after: $after, ${filterClause ? filterClause + ', ' : ''}orderBy: updatedAt) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n identifier\n title\n description\n priority\n url\n createdAt\n updatedAt\n completedAt\n state {\n name\n type\n }\n assignee {\n name\n email\n }\n labels {\n nodes {\n name\n }\n }\n project {\n id\n name\n color\n icon\n }\n team {\n id\n name\n color\n icon\n }\n cycle {\n id\n name\n number\n }\n }\n }\n }\n `;\n\n const response = await fetch('https://api.linear.app/graphql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': apiKey,\n },\n body: JSON.stringify({ query, variables: { after: cursor } }),\n });\n\n // Parse Linear rate-limit headers before reading the body or throwing on errors.\n // Linear uses x-ratelimit-requests-*; fall back to the bare x-ratelimit-* names.\n const rlRemaining =\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-remaining') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-remaining');\n const rlTotal =\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-limit') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-limit');\n const rlReset =\n this.normalizeLinearResetAt(\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-reset') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-reset')\n );\n if (rlRemaining !== null && rlTotal !== null && rlReset !== null) {\n this.cache.updateRateLimit('linear', { remaining: rlRemaining, total: rlTotal, resetAt: rlReset });\n }\n\n // Detect quota exhaustion on HTTP 429 before attempting to parse the body.\n if (response.status === 429) {\n this.recordLinearExhaustion(response, Date.now());\n let bodyText = '';\n try { bodyText = await response.text(); } catch { /* ignore unreadable body */ }\n let message = `Linear API error: HTTP 429`;\n try {\n const body = JSON.parse(bodyText);\n if (body?.errors?.[0]?.message) message = body.errors[0].message;\n } catch { /* ignore non-JSON body */ }\n throw new Error(message);\n }\n\n const json = await response.json() as {\n errors?: Array<{ message?: string }>;\n data?: {\n issues?: {\n nodes: any[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n };\n };\n\n if (json.errors) {\n const message = json.errors[0]?.message || 'Linear GraphQL error';\n if (/rate limit|ratelimited/i.test(message)) {\n this.recordLinearExhaustion(response, Date.now());\n }\n throw new Error(message);\n }\n\n const issues = json.data?.issues;\n if (!issues) break;\n\n allIssues.push(...issues.nodes);\n hasMore = issues.pageInfo.hasNextPage;\n cursor = issues.pageInfo.endCursor;\n\n if (allIssues.length > 1000) break;\n }\n\n // Build project lookup for deduplication\n const projectByName = new Map<string, { id: string; name: string; color?: string; icon?: string }>();\n for (const issue of allIssues) {\n if (issue.project && !projectByName.has(issue.project.name)) {\n projectByName.set(issue.project.name, {\n id: issue.project.id,\n name: issue.project.name,\n color: issue.project.color,\n icon: issue.project.icon,\n });\n }\n }\n\n // Format to dashboard schema\n return allIssues.map((issue: any) => {\n let project;\n if (issue.project) {\n project = {\n id: issue.project.id,\n name: issue.project.name,\n color: issue.project.color,\n icon: issue.project.icon,\n };\n } else if (issue.team) {\n const existing = projectByName.get(issue.team.name);\n project = existing || {\n id: issue.team.id,\n name: issue.team.name,\n color: issue.team.color,\n icon: issue.team.icon,\n };\n }\n\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description,\n status: issue.state?.name || 'Backlog',\n stateType: issue.state?.type,\n priority: issue.priority,\n assignee: issue.assignee ? { name: issue.assignee.name, email: issue.assignee.email } : undefined,\n labels: issue.labels?.nodes?.map((l: any) => l.name) || [],\n url: issue.url,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n completedAt: issue.completedAt,\n project,\n cycle: issue.cycle ? {\n id: issue.cycle.id,\n name: issue.cycle.name,\n number: issue.cycle.number,\n } : undefined,\n source: 'linear',\n };\n });\n }\n\n // ---------------------------------------------------------------\n // Rally polling — TTL-based caching, per-project config support\n // ---------------------------------------------------------------\n\n /**\n * Format a raw Rally issue into the dashboard schema.\n */\n private formatRallyIssue(issue: any, projectInfo: { id: string; name: string; color: string; icon: string }): any {\n const canonicalStatus = mapRallyStateToCanonical(issue.state);\n const identifier = issue.ref || issue.id || 'unknown';\n if (typeof issue.rawState === 'object' && issue.rawState !== null) {\n console.warn(`[IssueDataService] Rally ${identifier}: rawState is object, normalizing (PAN-201)`);\n }\n return {\n id: `rally-${issue.id || identifier}`,\n identifier,\n title: issue.title || '',\n description: issue.description || '',\n status: canonicalStatus === 'todo' ? 'Todo' :\n canonicalStatus === 'in_progress' ? 'In Progress' :\n canonicalStatus === 'done' ? 'Done' : 'Todo',\n priority: issue.priority ?? 3,\n assignee: issue.assignee ? {\n name: issue.assignee,\n email: `${issue.assignee.replace(/\\s+/g, '.').toLowerCase()}@rally`,\n } : undefined,\n labels: Array.isArray(issue.labels) ? issue.labels.filter((l: any) => typeof l === 'string') : [],\n url: issue.url || '',\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n parentRef: issue.parentRef,\n artifactType: issue.artifactType,\n rawTrackerState: typeof issue.rawState === 'object' && issue.rawState !== null\n ? (issue.rawState.Name || issue.rawState._refObjectName || 'Defined')\n : issue.rawState,\n project: projectInfo,\n source: 'rally',\n };\n }\n\n /**\n * Compute derived feature status from child stories.\n * If ANY child is in progress, the feature is derived as 'in_progress'.\n * If ALL children are done, the feature is derived as 'closed'.\n * Attaches child counts for progress display.\n */\n private computeDerivedFeatureStatus(issues: any[]): any[] {\n // Build children-by-parent map (key: parent identifier, value: child issues)\n const childrenByParent = new Map<string, any[]>();\n for (const issue of issues) {\n if (issue.parentRef) {\n const existing = childrenByParent.get(issue.parentRef) || [];\n existing.push(issue);\n childrenByParent.set(issue.parentRef, existing);\n }\n }\n\n // For each Feature, compute derived status\n return issues.map(issue => {\n const isFeature = issue.artifactType?.includes('PortfolioItem');\n if (!isFeature) return issue;\n\n const children = childrenByParent.get(issue.identifier) || [];\n if (children.length === 0) return issue;\n\n const completedChildCount = children.filter(\n (c: any) => c.status === 'Done'\n ).length;\n const inProgressChildCount = children.filter(\n (c: any) => c.status === 'In Progress'\n ).length;\n const totalChildCount = children.length;\n\n let derivedStatus: string | undefined;\n if (completedChildCount === totalChildCount) {\n derivedStatus = 'closed';\n } else if (inProgressChildCount > 0) {\n derivedStatus = 'in_progress';\n }\n\n return {\n ...issue,\n derivedStatus,\n totalChildCount,\n completedChildCount,\n inProgressChildCount,\n };\n });\n }\n\n private async pollRally(): Promise<void> {\n const globalConfig = getRallyConfig();\n if (!globalConfig) {\n this.trackers.rally.lastFetchedIssues = [];\n return;\n }\n\n // Validate config on first poll and log warnings\n if (!this.trackers.rally.lastFetchedAt) {\n const validation = validateRallyConfig(globalConfig);\n if (validation.warnings.length > 0) {\n console.warn('[Rally] Configuration warnings:', validation.warnings.join('; '));\n }\n }\n\n // Only fetch if cache is stale\n if (!this.cache.isStale('rally', 'issues') && this.trackers.rally.lastFetchedIssues.length > 0) {\n return;\n }\n\n try {\n const { RallyTracker } = await import('../../../lib/tracker/rally.js');\n const { findProjectsByRallyProject } = await import('../../../lib/projects.js');\n\n const rallyProjects = findProjectsByRallyProject();\n let allFormatted: any[] = [];\n\n if (rallyProjects.length > 0) {\n // Per-project mode: create separate tracker per Rally project OID\n const projectQueries = rallyProjects.map(async ({ key, config: projConfig }) => {\n try {\n const tracker = new RallyTracker({\n apiKey: globalConfig.apiKey,\n server: globalConfig.server,\n workspace: globalConfig.workspace,\n project: projConfig.rally_project,\n });\n\n const issues = await Effect.runPromise(tracker.listIssues({\n includeClosed: false,\n limit: 100,\n }));\n\n const projectInfo = {\n id: `rally-${key}`,\n name: projConfig.name,\n color: '#00C7B1',\n icon: 'rally',\n };\n\n return issues.map((issue: any) => this.formatRallyIssue(issue, projectInfo));\n } catch (err: any) {\n console.error(`[IssueDataService] Rally poll error for project ${key}:`, err.message);\n return [];\n }\n });\n\n const results = await Promise.all(projectQueries);\n allFormatted = results.flat();\n } else {\n // Fallback: use global RALLY_PROJECT env (backward compat)\n const tracker = new RallyTracker({\n apiKey: globalConfig.apiKey,\n server: globalConfig.server,\n workspace: globalConfig.workspace,\n project: globalConfig.project,\n });\n\n const issues = await Effect.runPromise(tracker.listIssues({\n includeClosed: false,\n limit: 100,\n }));\n\n const projectInfo = {\n id: 'rally-project',\n name: 'Rally',\n color: '#00C7B1',\n icon: 'rally',\n };\n\n allFormatted = issues.map((issue: any) => this.formatRallyIssue(issue, projectInfo));\n }\n\n // Compute derived feature status from child stories\n allFormatted = this.computeDerivedFeatureStatus(allFormatted);\n\n const oldData = this.trackers.rally.lastFetchedIssues;\n const changed = issuesChanged(allFormatted, oldData);\n\n this.trackers.rally.lastFetchedIssues = allFormatted;\n this.trackers.rally.lastFetchedAt = new Date().toISOString();\n this.trackers.rally.lastError = null;\n\n this.cache.set('rally', 'issues', allFormatted, { ttlSeconds: DEFAULT_TTLS.rally });\n\n if (changed) {\n console.log(`[IssueDataService] Rally: ${allFormatted.length} issues (changed)`);\n this.pushUpdated();\n this.pushMeta();\n }\n } catch (err: any) {\n const errorMsg = err.message?.includes('Could not parse')\n ? `${err.message} - Check Rally workspace/project configuration. Enable DEBUG=rally for query details.`\n : err.message;\n console.error('[IssueDataService] Rally poll error:', errorMsg);\n this.trackers.rally.lastError = errorMsg;\n }\n }\n}\n","/**\n * Shared IssueDataService singleton — used by read model (bootstrap),\n * route handlers (force-refresh, diagnostics), and event emission.\n */\nimport { IssueDataService } from './issue-data-service.js';\nimport { CacheService } from './cache-service.js';\n\nlet _service: IssueDataService | null = null;\nlet _startPromise: Promise<void> | null = null;\n\nexport function getSharedIssueService(): IssueDataService {\n if (!_service) {\n _service = new IssueDataService(new CacheService());\n }\n return _service;\n}\n\nexport function startSharedIssueService(options?: { skipPolling?: boolean }): Promise<void> {\n if (_startPromise) return _startPromise;\n _startPromise = getSharedIssueService().start(options).catch((err: unknown) => {\n console.error('[issue-service-singleton] start failed:', err);\n });\n return _startPromise;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"issue-service-singleton-CDGt__oQ.js","names":[],"sources":["../../src/dashboard/server/services/cache-service.ts","../../src/dashboard/server/services/issue-data-service.ts","../../src/dashboard/server/services/issue-service-singleton.ts"],"sourcesContent":["/**\n * CacheService — Two-layer cache for dashboard API responses\n *\n * L1: In-memory Map (hot, 10s TTL, 50 entries max)\n * L2: SQLite (persistent, survives restarts)\n *\n * Stores API responses per tracker with ETag support (GitHub REST 304s are FREE).\n * Tracks rate limits per tracker for adaptive backoff.\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { openDatabase, type SqliteDatabase } from '../../../lib/database/driver.js';\n\nconst OVERDECK_HOME = process.env.OVERDECK_HOME || join(homedir(), '.overdeck');\nconst CACHE_DB_PATH = join(OVERDECK_HOME, 'cache.db');\n\n// Default TTLs per tracker (seconds)\nexport const DEFAULT_TTLS: Record<string, number> = {\n github: 60,\n linear: 30,\n rally: 120,\n};\n\n// L1 in-memory cache entry\ninterface L1Entry {\n data: any;\n etag?: string;\n lastModified?: string;\n lastFetchedAt: string;\n lastUpdatedAt: string;\n ttlSeconds: number;\n insertedAt: number; // Date.now()\n}\n\n// Rate limit info\nexport interface RateLimitInfo {\n remaining: number;\n total: number;\n resetAt: string;\n}\n\n/**\n * Parse an integer value from a response header.\n * Returns null when the header is missing or not a finite integer.\n */\nexport function parseIntegerHeader(headers: Headers, name: string): number | null {\n const raw = headers.get(name);\n if (!raw) return null;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\n// Cache entry returned from get()\nexport interface CacheEntry {\n data: any;\n etag?: string;\n lastModified?: string;\n lastFetchedAt: string;\n lastUpdatedAt: string;\n ttlSeconds: number;\n}\n\nexport class CacheService {\n private db: SqliteDatabase;\n private l1: Map<string, L1Entry> = new Map();\n private readonly l1MaxEntries = 50;\n private readonly l1TtlMs = 10_000; // 10 seconds\n\n constructor() {\n const t0 = performance.now();\n this.db = openDatabase(CACHE_DB_PATH);\n this.db.pragma('journal_mode = WAL');\n console.log(`[boot-timing] cache.db opened (WAL replay) at +${Math.round(performance.now() - t0)}ms`);\n this.createSchema();\n }\n\n private createSchema(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS api_cache (\n tracker TEXT NOT NULL,\n cache_key TEXT NOT NULL,\n data TEXT NOT NULL,\n etag TEXT,\n last_modified TEXT,\n last_fetched_at TEXT NOT NULL,\n last_updated_at TEXT NOT NULL,\n ttl_seconds INTEGER NOT NULL,\n PRIMARY KEY (tracker, cache_key)\n );\n\n CREATE TABLE IF NOT EXISTS rate_limits (\n tracker TEXT PRIMARY KEY,\n remaining INTEGER,\n total INTEGER,\n reset_at TEXT,\n updated_at TEXT NOT NULL\n );\n `);\n }\n\n /**\n * Get a cached entry. Checks L1 first, then L2 (SQLite).\n * Returns null if no entry or if expired.\n */\n get(tracker: string, cacheKey: string): CacheEntry | null {\n const compositeKey = `${tracker}:${cacheKey}`;\n\n // L1 check\n const l1Entry = this.l1.get(compositeKey);\n if (l1Entry && Date.now() - l1Entry.insertedAt < this.l1TtlMs) {\n return {\n data: l1Entry.data,\n etag: l1Entry.etag,\n lastModified: l1Entry.lastModified,\n lastFetchedAt: l1Entry.lastFetchedAt,\n lastUpdatedAt: l1Entry.lastUpdatedAt,\n ttlSeconds: l1Entry.ttlSeconds,\n };\n }\n\n // L1 miss or expired — check L2\n const stmt = this.db.prepare(`\n SELECT data, etag, last_modified, last_fetched_at, last_updated_at, ttl_seconds\n FROM api_cache\n WHERE tracker = ? AND cache_key = ?\n `);\n\n const row = stmt.get(tracker, cacheKey) as any;\n if (!row) return null;\n\n const entry: CacheEntry = {\n data: JSON.parse(row.data),\n etag: row.etag || undefined,\n lastModified: row.last_modified || undefined,\n lastFetchedAt: row.last_fetched_at,\n lastUpdatedAt: row.last_updated_at,\n ttlSeconds: row.ttl_seconds,\n };\n\n // Promote to L1\n this.setL1(compositeKey, {\n ...entry,\n insertedAt: Date.now(),\n });\n\n return entry;\n }\n\n /**\n * Get cached entry even if stale (for serving while re-fetching).\n */\n getStale(tracker: string, cacheKey: string): CacheEntry | null {\n const compositeKey = `${tracker}:${cacheKey}`;\n\n // Check L1 (even if expired)\n const l1Entry = this.l1.get(compositeKey);\n if (l1Entry) {\n return {\n data: l1Entry.data,\n etag: l1Entry.etag,\n lastModified: l1Entry.lastModified,\n lastFetchedAt: l1Entry.lastFetchedAt,\n lastUpdatedAt: l1Entry.lastUpdatedAt,\n ttlSeconds: l1Entry.ttlSeconds,\n };\n }\n\n // L2\n const stmt = this.db.prepare(`\n SELECT data, etag, last_modified, last_fetched_at, last_updated_at, ttl_seconds\n FROM api_cache\n WHERE tracker = ? AND cache_key = ?\n `);\n\n const row = stmt.get(tracker, cacheKey) as any;\n if (!row) return null;\n\n return {\n data: JSON.parse(row.data),\n etag: row.etag || undefined,\n lastModified: row.last_modified || undefined,\n lastFetchedAt: row.last_fetched_at,\n lastUpdatedAt: row.last_updated_at,\n ttlSeconds: row.ttl_seconds,\n };\n }\n\n /**\n * Store data in both L1 and L2 cache.\n */\n set(\n tracker: string,\n cacheKey: string,\n data: any,\n options?: {\n etag?: string;\n lastModified?: string;\n lastUpdatedAt?: string;\n ttlSeconds?: number;\n }\n ): void {\n const now = new Date().toISOString();\n const ttl = options?.ttlSeconds ?? DEFAULT_TTLS[tracker] ?? 60;\n const lastUpdatedAt = options?.lastUpdatedAt ?? now;\n const compositeKey = `${tracker}:${cacheKey}`;\n\n // L2 (SQLite) — upsert\n const stmt = this.db.prepare(`\n INSERT INTO api_cache (tracker, cache_key, data, etag, last_modified, last_fetched_at, last_updated_at, ttl_seconds)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(tracker, cache_key) DO UPDATE SET\n data = excluded.data,\n etag = excluded.etag,\n last_modified = excluded.last_modified,\n last_fetched_at = excluded.last_fetched_at,\n last_updated_at = excluded.last_updated_at,\n ttl_seconds = excluded.ttl_seconds\n `);\n\n stmt.run(\n tracker,\n cacheKey,\n JSON.stringify(data),\n options?.etag ?? null,\n options?.lastModified ?? null,\n now,\n lastUpdatedAt,\n ttl\n );\n\n // L1\n this.setL1(compositeKey, {\n data,\n etag: options?.etag,\n lastModified: options?.lastModified,\n lastFetchedAt: now,\n lastUpdatedAt,\n ttlSeconds: ttl,\n insertedAt: Date.now(),\n });\n }\n\n /**\n * Check if a cache entry is stale (past its TTL).\n */\n isStale(tracker: string, cacheKey: string): boolean {\n const entry = this.get(tracker, cacheKey);\n if (!entry) return true;\n\n const fetchedAt = new Date(entry.lastFetchedAt).getTime();\n const age = (Date.now() - fetchedAt) / 1000;\n return age > entry.ttlSeconds;\n }\n\n /**\n * Get the stored ETag for a tracker/key combo (for conditional requests).\n */\n getEtag(tracker: string, cacheKey: string): string | undefined {\n // Check L1 first\n const compositeKey = `${tracker}:${cacheKey}`;\n const l1Entry = this.l1.get(compositeKey);\n if (l1Entry?.etag) return l1Entry.etag;\n\n // Check L2\n const stmt = this.db.prepare(`\n SELECT etag FROM api_cache WHERE tracker = ? AND cache_key = ?\n `);\n const row = stmt.get(tracker, cacheKey) as any;\n return row?.etag || undefined;\n }\n\n /**\n * Invalidate cache for a specific tracker (all keys).\n */\n invalidate(tracker: string): void {\n // L1 — remove all entries for this tracker\n for (const key of this.l1.keys()) {\n if (key.startsWith(`${tracker}:`)) {\n this.l1.delete(key);\n }\n }\n\n // L2\n this.db.prepare('DELETE FROM api_cache WHERE tracker = ?').run(tracker);\n }\n\n /**\n * Invalidate a specific cache key.\n */\n invalidateKey(tracker: string, cacheKey: string): void {\n this.l1.delete(`${tracker}:${cacheKey}`);\n this.db.prepare('DELETE FROM api_cache WHERE tracker = ? AND cache_key = ?').run(tracker, cacheKey);\n }\n\n // --- Rate limit tracking ---\n\n /**\n * Update rate limit info for a tracker.\n */\n updateRateLimit(tracker: string, info: RateLimitInfo): void {\n const stmt = this.db.prepare(`\n INSERT INTO rate_limits (tracker, remaining, total, reset_at, updated_at)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(tracker) DO UPDATE SET\n remaining = excluded.remaining,\n total = excluded.total,\n reset_at = excluded.reset_at,\n updated_at = excluded.updated_at\n `);\n stmt.run(tracker, info.remaining, info.total, info.resetAt, new Date().toISOString());\n }\n\n /**\n * Get rate limit info for a tracker.\n */\n getRateLimit(tracker: string): RateLimitInfo | null {\n const stmt = this.db.prepare(`\n SELECT remaining, total, reset_at FROM rate_limits WHERE tracker = ?\n `);\n const row = stmt.get(tracker) as any;\n if (!row) return null;\n\n return {\n remaining: row.remaining,\n total: row.total,\n resetAt: row.reset_at,\n };\n }\n\n /**\n * Check if we should back off requests for a tracker.\n * Returns true if remaining < 10% of total.\n */\n shouldBackoff(tracker: string): boolean {\n const limit = this.getRateLimit(tracker);\n if (!limit) return false;\n\n // If reset time has passed, no need to back off\n if (new Date(limit.resetAt).getTime() < Date.now()) return false;\n\n return limit.remaining < limit.total * 0.1;\n }\n\n /**\n * Calculate adaptive backoff delay in ms.\n * Returns 0 if no backoff needed.\n */\n getBackoffMs(tracker: string, baseIntervalMs: number): number {\n const limit = this.getRateLimit(tracker);\n if (!limit) return 0;\n\n // If reset time has passed, no backoff\n if (new Date(limit.resetAt).getTime() < Date.now()) return 0;\n\n const ratioRemaining = limit.remaining / limit.total;\n\n if (ratioRemaining > 0.5) return 0; // >50% remaining: no backoff\n if (ratioRemaining > 0.25) return baseIntervalMs; // 25-50%: 2x interval\n if (ratioRemaining > 0.1) return baseIntervalMs * 4; // 10-25%: 5x interval\n return baseIntervalMs * 9; // <10%: 10x interval\n }\n\n /**\n * Calculate suspension delay in ms for an exhausted tracker.\n * Returns 0 when there is no rate limit row, remaining > 0, resetAt has\n * passed, or resetAt is unparseable. Callers clamp to their own ceiling.\n */\n getSuspensionMs(tracker: string, now: number = Date.now()): number {\n const limit = this.getRateLimit(tracker);\n if (!limit) return 0;\n if (limit.remaining > 0) return 0;\n\n const resetMs = new Date(limit.resetAt).getTime();\n if (!Number.isFinite(resetMs)) return 0;\n if (resetMs <= now) return 0;\n\n return resetMs - now;\n }\n\n /**\n * Get cache status for all trackers (for diagnostics endpoint).\n */\n getStatus(): Record<string, {\n remaining: number | null;\n total: number | null;\n lastFetched: string | null;\n cacheKeys: number;\n }> {\n const result: Record<string, any> = {};\n\n for (const tracker of ['github', 'linear', 'rally']) {\n const limit = this.getRateLimit(tracker);\n const countStmt = this.db.prepare(\n 'SELECT COUNT(*) as cnt, MAX(last_fetched_at) as latest FROM api_cache WHERE tracker = ?'\n );\n const row = countStmt.get(tracker) as any;\n\n result[tracker] = {\n remaining: limit?.remaining ?? null,\n total: limit?.total ?? null,\n lastFetched: row?.latest ?? null,\n cacheKeys: row?.cnt ?? 0,\n };\n }\n\n return result;\n }\n\n /**\n * Close the database connection.\n */\n close(): void {\n this.l1.clear();\n this.db.close();\n }\n\n // --- L1 helpers ---\n\n private setL1(compositeKey: string, entry: L1Entry): void {\n // Evict oldest if at capacity\n if (this.l1.size >= this.l1MaxEntries && !this.l1.has(compositeKey)) {\n const oldestKey = this.l1.keys().next().value;\n if (oldestKey) this.l1.delete(oldestKey);\n }\n this.l1.set(compositeKey, entry);\n }\n}\n","/**\n * IssueDataService — Central orchestrator for issue data\n *\n * Replaces the inline fetching in /api/issues with:\n * - Background polling per tracker on independent timers\n * - GitHub REST + ETags (304s are FREE, don't count against rate limit)\n * - Linear incremental fetching via updatedAt filter\n * - Rally TTL-based caching\n * - Change detection + event store push (via onIssuesChanged callback)\n * - Adaptive backoff on rate limit pressure\n * - Instant serve from cache (sub-100ms dashboard loads)\n */\n\nimport { Octokit } from '@octokit/rest';\nimport { Effect } from 'effect';\nimport { existsSync } from 'fs';\nimport { stat } from 'fs/promises';\nimport { join } from 'path';\nimport { mapGitHubStateToCanonical } from '../../../core/state-mapping.js';\nimport { CacheService, DEFAULT_TTLS, parseIntegerHeader } from './cache-service.js';\nimport { getGitHubConfig, getLinearApiKey, getRallyConfig, validateRallyConfig } from './tracker-config.js';\nimport { loadReviewStatusesForIssues, type ReviewStatus } from '../../../lib/review-status.js';\nimport { resolveProjectFromIssueSync } from '../../../lib/projects.js';\nimport { findPlan, readWorkspacePlan } from '../../../lib/vbrief/io.js';\nimport type { VBriefDocument } from '../../../lib/vbrief/types.js';\nimport { loadConfigSync } from '../../../lib/config-yaml.js';\n\n/**\n * Compute bead progress counts from a cached plan document.\n * Exported for testing.\n */\nexport function computeBeadCounts(doc: VBriefDocument | null): { completed: number; total: number } | null {\n const items = doc?.plan?.items ?? [];\n if (items.length === 0) return null;\n return { completed: items.filter((i) => i.status === 'completed').length, total: items.length };\n}\n\n/**\n * Map a raw status string to its canonical state.\n * Exported for testing.\n */\nexport function getCanonicalStatus(status: string | undefined, stateType?: string): string {\n if (!status) return 'backlog';\n const normalized = status.toLowerCase();\n // Direct backlog mappings\n if (normalized === 'backlog' || normalized === 'triage' || normalized === 'unknown') {\n return 'backlog';\n }\n // Other canonical states\n if (normalized === 'todo' || normalized === 'to do' || normalized === 'ready' || normalized === 'unstarted') {\n return 'todo';\n }\n if (normalized === 'in progress' || normalized === 'in_progress' || normalized === 'started' || normalized === 'active' || normalized === 'in planning') {\n return 'in_progress';\n }\n if (normalized === 'in review' || normalized === 'in_review' || normalized === 'review' || normalized === 'qa' || normalized === 'testing') {\n return 'in_review';\n }\n if (normalized === 'verifying' || normalized === 'verifying on main' || normalized === 'verifying_on_main') {\n return 'verifying_on_main';\n }\n if (normalized === 'done' || normalized === 'completed' || normalized === 'closed') {\n return 'done';\n }\n if (normalized === 'canceled' || normalized === 'cancelled' || normalized === 'duplicate' || normalized === \"won't do\" || normalized === 'wontfix') {\n return 'canceled';\n }\n // Fallback: use Linear stateType if available (handles custom status names)\n if (stateType) {\n const typeMap: Record<string, string> = {\n backlog: 'backlog',\n unstarted: 'todo',\n started: 'in_progress',\n completed: 'done',\n canceled: 'canceled',\n cancelled: 'canceled',\n };\n if (typeMap[stateType]) return typeMap[stateType];\n }\n return 'backlog'; // Default fallback\n}\n\nexport function shouldRefreshPlanningStateForIssue(issue: any): boolean {\n const canonical = getCanonicalStatus(issue?.status, issue?.stateType);\n return canonical !== 'done' && canonical !== 'canceled';\n}\n\n// Poll intervals (ms)\nconst POLL_INTERVALS = {\n github: { default: 30_000, min: 15_000, max: 300_000 },\n linear: { default: 30_000, min: 15_000, max: 300_000 },\n rally: { default: 120_000, min: 60_000, max: 600_000 },\n};\n\n// Linear full refresh interval (safety net)\nconst LINEAR_FULL_REFRESH_MS = 5 * 60 * 1000; // 5 minutes\n\ninterface TrackerState {\n timer: ReturnType<typeof setTimeout> | null;\n currentInterval: number;\n lastFetchedIssues: any[];\n lastError: string | null;\n lastFetchedAt: string | null;\n}\n\ninterface GetIssuesCacheEntry {\n issues: any[];\n json: string | null;\n}\n\n/**\n * Cheap change detection for issue arrays.\n * Compares length + the most recent updatedAt timestamp instead of\n * serializing the entire array with JSON.stringify (which caused\n * 600-900ms event loop stalls on large issue sets).\n */\nfunction issuesChanged(newIssues: any[], oldIssues: any[]): boolean {\n if (newIssues.length !== oldIssues.length) return true;\n if (newIssues.length === 0) return false;\n\n // Find the most recent updatedAt in each set\n let newMax = '';\n let oldMax = '';\n for (const issue of newIssues) {\n if (issue.updatedAt && issue.updatedAt > newMax) newMax = issue.updatedAt;\n }\n for (const issue of oldIssues) {\n if (issue.updatedAt && issue.updatedAt > oldMax) oldMax = issue.updatedAt;\n }\n return newMax !== oldMax;\n}\n\n/**\n * Map normalized IssueState (open/in_progress/closed) to canonical dashboard status.\n * The Rally tracker already normalizes raw Rally states to IssueState in rally.ts.\n */\nfunction mapRallyStateToCanonical(issueState: string): string {\n if (!issueState) return 'todo';\n const stateLower = issueState.toLowerCase();\n if (stateLower === 'in_progress') return 'in_progress';\n if (stateLower === 'closed') return 'done';\n // 'open' and anything unrecognized → 'todo'\n return 'todo';\n}\n\n/**\n * Compute planning-state for an issue via cheap filesystem checks.\n *\n * `isPlanningComplete()` reads and JSON-parses the workspace's plan.vbrief.json\n * file. Calling it for every issue on every getSnapshot — which happens once\n * per WS-RPC bootstrap and then every emitted snapshot — would do 870+ sync\n * disk reads per call and starve the dashboard event loop until WS clients\n * timeout. Cache by plan-file mtime so the read happens at most once per file\n * change.\n */\ninterface PlanningStateCacheEntry {\n result: {\n hasPlan: boolean;\n hasBeads: boolean;\n planningComplete: boolean;\n workspacePath: string;\n beadCounts: { completed: number; total: number } | null;\n };\n planMtimeMs: number; // -1 when no plan file existed at compute time\n continueMtimeMs: number; // -1 when no continue.json existed at compute time\n}\nconst planningStateCache = new Map<string, PlanningStateCacheEntry>();\n\nconst planningStateRefreshInFlight = new Set<string>();\nconst PLANNING_REFRESH_CONCURRENCY = 4;\nconst PLANNING_FINISHED_STATUSES = new Set(['proposed', 'approved', 'pending', 'running', 'completed', 'blocked']);\nconst DAY_MS = 86_400_000;\n\nfunction getCachedPlanningState(identifier: string): {\n hasPlan: boolean;\n hasBeads: boolean;\n planningComplete: boolean;\n workspacePath: string;\n beadCounts: { completed: number; total: number } | null;\n} {\n try {\n const resolved = resolveProjectFromIssueSync(identifier);\n const projectPath = resolved?.projectPath ?? '';\n if (!projectPath) {\n return { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null };\n }\n const issueLower = identifier.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n return planningStateCache.get(identifier)?.result\n ?? { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath, beadCounts: null };\n } catch {\n return { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null };\n }\n}\n\nasync function refreshPlanningState(identifier: string): Promise<boolean> {\n if (planningStateRefreshInFlight.has(identifier)) return false;\n planningStateRefreshInFlight.add(identifier);\n try {\n const resolved = resolveProjectFromIssueSync(identifier);\n const projectPath = resolved?.projectPath ?? '';\n if (!projectPath) return updatePlanningStateCache(identifier, {\n result: { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null },\n planMtimeMs: -1,\n continueMtimeMs: -1,\n });\n\n const issueLower = identifier.toLowerCase();\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (!existsSync(workspacePath)) {\n return updatePlanningStateCache(identifier, {\n result: { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath, beadCounts: null },\n planMtimeMs: -1,\n continueMtimeMs: -1,\n });\n }\n\n const planPath = await Effect.runPromise(findPlan(workspacePath));\n let planMtimeMs = -1;\n if (planPath) {\n try {\n planMtimeMs = (await stat(planPath)).mtimeMs;\n } catch {\n planMtimeMs = -1;\n }\n }\n\n const continuePath = join(workspacePath, '.pan', 'continue.json');\n let continueMtimeMs = -1;\n try {\n continueMtimeMs = (await stat(continuePath)).mtimeMs;\n } catch {\n continueMtimeMs = -1;\n }\n\n const cached = planningStateCache.get(identifier);\n if (cached && cached.planMtimeMs === planMtimeMs && cached.continueMtimeMs === continueMtimeMs && cached.result.workspacePath === workspacePath) {\n return false;\n }\n\n const doc = planPath ? await Effect.runPromise(readWorkspacePlan(workspacePath)) : null;\n const planningComplete = doc?.plan?.status ? PLANNING_FINISHED_STATUSES.has(doc.plan.status) : false;\n const beadCounts = computeBeadCounts(doc);\n return updatePlanningStateCache(identifier, {\n result: { hasPlan: planPath !== null, hasBeads: planningComplete, planningComplete, workspacePath, beadCounts },\n planMtimeMs,\n continueMtimeMs,\n });\n } catch {\n return updatePlanningStateCache(identifier, {\n result: { hasPlan: false, hasBeads: false, planningComplete: false, workspacePath: '', beadCounts: null },\n planMtimeMs: -1,\n continueMtimeMs: -1,\n });\n } finally {\n planningStateRefreshInFlight.delete(identifier);\n }\n}\n\nfunction updatePlanningStateCache(identifier: string, entry: PlanningStateCacheEntry): boolean {\n const prev = planningStateCache.get(identifier);\n const changed = !prev\n || prev.planMtimeMs !== entry.planMtimeMs\n || prev.continueMtimeMs !== entry.continueMtimeMs\n || JSON.stringify(prev.result) !== JSON.stringify(entry.result);\n planningStateCache.set(identifier, entry);\n return changed;\n}\n\nexport class IssueDataService {\n private cache: CacheService;\n private loadConfig: typeof loadConfigSync;\n private trackers: Record<string, TrackerState> = {};\n private linearLastFullRefresh = 0;\n private started = false;\n private shadowStateModule: any = null;\n /** In-memory snapshot of shadow states, refreshed asynchronously. The hot\n * path (`getIssues`) reads from this map — no disk I/O on every request. */\n private shadowStatesCache: Map<string, any> = new Map();\n private reviewStatusesCache: Record<string, ReviewStatus> = {};\n private _onIssuesChanged: ((issues: unknown[]) => void) | null = null;\n private planningSnapshotQueued = false;\n private planningRefreshQueue: string[] = [];\n private planningRefreshQueued = new Set<string>();\n private planningRefreshActive = 0;\n private reviewStatusRefreshQueued = false;\n private reviewStatusRefreshIssueIds = new Set<string>();\n private getIssuesCache = new Map<string, GetIssuesCacheEntry>();\n\n /** Register a callback invoked whenever issue data changes (PAN-433). */\n onIssuesChanged(fn: (issues: unknown[]) => void): void {\n this._onIssuesChanged = fn;\n }\n\n constructor(cache: CacheService, options?: { loadConfig?: typeof loadConfigSync }) {\n this.cache = cache;\n this.loadConfig = options?.loadConfig ?? loadConfigSync;\n\n for (const tracker of ['github', 'linear', 'rally'] as const) {\n this.trackers[tracker] = {\n timer: null,\n currentInterval: POLL_INTERVALS[tracker].default,\n lastFetchedIssues: [],\n lastError: null,\n lastFetchedAt: null,\n };\n }\n }\n\n /**\n * Start background polling. Returns immediately after loading cached data.\n * API fetches run in the background and push incremental updates.\n *\n * `skipPolling` (PAN-1817): load the SQLite cache and serve read-only, but\n * start NO recurring tracker fetches. Peer dashboards running inside workspace\n * containers (OVERDECK_DISABLE_DEACON=1) MUST use this — otherwise every\n * workspace container becomes an independent Linear/GitHub poller hammering\n * the single shared API key, which exhausted Linear's 2500/hr quota when\n * ~17 container pollers ran at once.\n */\n async start(options?: { skipPolling?: boolean }): Promise<void> {\n if (this.started) return;\n this.started = true;\n\n // Pre-load shadow state module\n await this.ensureShadowStateLoaded();\n\n // Load any cached data from SQLite so getIssues() works instantly\n this.loadCachedData();\n\n // Push snapshot immediately with stale cached data so read model has\n // something to work with before the background fetches complete.\n this.pushSnapshot();\n\n if (options?.skipPolling) {\n console.log('[IssueDataService] Tracker polling DISABLED (peer dashboard / OVERDECK_DISABLE_DEACON=1) — serving cached issues only, zero tracker API calls (PAN-1817)');\n return;\n }\n\n // Kick off all tracker fetches in the background — do NOT await.\n // Each poll calls pushUpdated() when done → incremental client updates.\n void Promise.allSettled([\n this.pollGitHub(),\n this.pollLinear(),\n this.pollRally(),\n ]).then(() => {\n // Final snapshot push after all initial fetches complete\n this.pushSnapshot();\n // Start recurring timers (after first fetch completes)\n this.scheduleNext('github');\n this.scheduleNext('linear');\n this.scheduleNext('rally');\n });\n }\n\n /**\n * Stop all polling timers.\n */\n stop(): void {\n this.started = false;\n for (const state of Object.values(this.trackers)) {\n if (state.timer) {\n clearTimeout(state.timer);\n state.timer = null;\n }\n }\n }\n\n /**\n * Clear all cached issue data and trigger a fresh re-fetch from all trackers.\n */\n async clearCacheAndRefresh(): Promise<void> {\n // Clear SQLite + L1 cache for all trackers\n for (const tracker of ['github', 'linear', 'rally']) {\n this.cache.invalidate(tracker);\n this.trackers[tracker].lastFetchedIssues = [];\n this.trackers[tracker].lastFetchedAt = null;\n this.trackers[tracker].lastError = null;\n }\n console.log('[IssueDataService] Cache cleared — re-fetching all trackers');\n // Re-fetch all trackers\n await Promise.allSettled([\n this.pollGitHub(),\n this.pollLinear(),\n this.pollRally(),\n ]);\n this.pushSnapshot();\n }\n\n /**\n * Look up which tracker an issue belongs to by its identifier.\n * Returns 'github' | 'linear' | 'rally' | null.\n */\n getIssueSource(identifier: string): 'github' | 'linear' | 'rally' | null {\n const id = identifier.toLowerCase();\n for (const [trackerName, state] of Object.entries(this.trackers)) {\n for (const issue of state.lastFetchedIssues) {\n if ((issue.identifier || '').toLowerCase() === id) {\n return trackerName as 'github' | 'linear' | 'rally';\n }\n }\n }\n return null;\n }\n\n /**\n * Get all issues from cache. Applies shadow state and filtering.\n * This is the hot path — must be fast.\n */\n getIssues(options?: { cycle?: string; includeCompleted?: boolean }): any[] {\n return this.getIssuesCacheEntry(options).issues;\n }\n\n getIssuesJson(options?: { cycle?: string; includeCompleted?: boolean }): string {\n const entry = this.getIssuesCacheEntry(options);\n if (entry.json === null) {\n entry.json = JSON.stringify(entry.issues);\n }\n return entry.json;\n }\n\n private getIssuesCacheEntry(options?: { cycle?: string; includeCompleted?: boolean }): GetIssuesCacheEntry {\n const key = this.getIssuesCacheKey(options);\n const cached = this.getIssuesCache.get(key);\n if (cached) return cached;\n\n const issues = this.computeIssues(options);\n if (process.env.NODE_ENV !== 'production') {\n Object.freeze(issues);\n }\n const entry: GetIssuesCacheEntry = { issues, json: null };\n this.getIssuesCache.set(key, entry);\n return entry;\n }\n\n private getIssuesCacheKey(options?: { cycle?: string; includeCompleted?: boolean }): string {\n return `${options?.cycle ?? 'current'}|${options?.includeCompleted === true}`;\n }\n\n private invalidateGetIssuesCache(): void {\n this.getIssuesCache.clear();\n }\n\n private computeIssues(options?: { cycle?: string; includeCompleted?: boolean }): any[] {\n let allIssues = [\n ...this.trackers.github.lastFetchedIssues,\n ...this.trackers.linear.lastFetchedIssues,\n ...this.trackers.rally.lastFetchedIssues,\n ];\n\n // Merge shadow state from the in-memory cache. The cache is refreshed\n // asynchronously by `refreshShadowStatesCache()` — we never hit disk here,\n // keeping `getIssues()` (a hot path) off the event-loop-blocking path.\n try {\n allIssues = allIssues.map(issue => {\n const shadowState = this.shadowStatesCache.get(issue.identifier.toLowerCase());\n if (shadowState) {\n return {\n ...issue,\n shadowStatus: shadowState.shadowStatus,\n targetCanonicalState: shadowState.targetCanonicalState,\n shadowedAt: shadowState.shadowedAt,\n shadowTrackerStatus: shadowState.trackerStatus,\n };\n }\n return { ...issue, shadowStatus: null, targetCanonicalState: null };\n });\n } catch (e) {\n allIssues = allIssues.map(issue => ({ ...issue, shadowStatus: null }));\n }\n\n // Show all completed issues (label-based dismissal will be added later)\n\n // Apply cycle filter using canonical status mapping\n const cycle = options?.cycle ?? 'current';\n if (cycle === 'current') {\n // Current cycle: exclude Backlog and Canceled items, only show active cycle work\n allIssues = allIssues.filter(issue => {\n const canonical = getCanonicalStatus(issue.status);\n return canonical !== 'backlog' && canonical !== 'canceled';\n });\n } else if (cycle === 'backlog') {\n // Backlog view: only show Backlog items (including Triage, Unknown)\n allIssues = allIssues.filter(issue => {\n const canonical = getCanonicalStatus(issue.status, issue.stateType);\n return canonical === 'backlog';\n });\n } else if (cycle === 'canceled') {\n // Canceled view: only show Canceled items (Canceled, Duplicate, Won't Do)\n allIssues = allIssues.filter(issue => {\n const canonical = getCanonicalStatus(issue.status);\n return canonical === 'canceled';\n });\n }\n // cycle === 'all': no additional filtering, show everything\n\n // Augment with mergeStatus from the asynchronous review-status cache.\n allIssues = allIssues.map(issue => {\n const canonical = getCanonicalStatus(issue.state ?? issue.canonicalStatus ?? issue.status, issue.stateType);\n const key = issue.identifier?.toUpperCase();\n const rs = key ? this.reviewStatusesCache[key] : null;\n if (rs?.mergeStatus) {\n const issueWithMerge = { ...issue, mergeStatus: rs.mergeStatus };\n if (canonical === 'done' && issueWithMerge.mergeStatus !== 'merged') {\n return { ...issueWithMerge, mergeStatus: 'merged' };\n }\n if (rs.mergeStatus === 'merged' && canonical !== 'done' && canonical !== 'canceled') {\n return { ...issueWithMerge, status: 'Verifying', canonicalStatus: 'verifying_on_main', state: 'verifying_on_main' };\n }\n return issueWithMerge;\n }\n if (canonical === 'done' && issue.mergeStatus && issue.mergeStatus !== 'merged') {\n return { ...issue, mergeStatus: 'merged' };\n }\n return issue;\n });\n\n // Enrich with cached planning-state only. Refresh work is scheduled by tracker updates.\n allIssues = allIssues.map(issue => {\n const ps = getCachedPlanningState(issue.identifier);\n return {\n ...issue,\n hasPlan: ps.hasPlan,\n hasBeads: ps.hasBeads,\n planningComplete: ps.planningComplete,\n workspacePath: ps.workspacePath || undefined,\n beadCounts: ps.beadCounts,\n };\n });\n\n const updatedAtMs = new Map<any, number>();\n for (const issue of allIssues) {\n updatedAtMs.set(issue, Date.parse(issue.updatedAt) || 0);\n }\n allIssues.sort((a, b) => (updatedAtMs.get(b) ?? 0) - (updatedAtMs.get(a) ?? 0));\n\n return allIssues;\n }\n\n /**\n * Invalidate a tracker's cache and trigger immediate re-poll.\n * Called after mutations (move-status, label changes, etc.)\n */\n /**\n * Immediately patch a cached issue and push the update to all clients.\n * Use this after any state mutation so the dashboard reflects the change\n * instantly without waiting for the next poll cycle.\n *\n * @param identifier - Issue identifier (e.g. \"MIN-734\", \"PAN-302\")\n * @param patch - Fields to merge into the cached issue object\n */\n patchIssue(identifier: string, patch: Record<string, any>): void {\n const id = identifier.toLowerCase();\n for (const state of Object.values(this.trackers)) {\n const idx = state.lastFetchedIssues.findIndex(\n (i: any) => (i.identifier || '').toLowerCase() === id\n );\n if (idx !== -1) {\n state.lastFetchedIssues[idx] = { ...state.lastFetchedIssues[idx], ...patch };\n this.pushUpdated();\n return;\n }\n }\n // Issue not in cache yet (e.g. just created) — trigger a full refresh instead\n const source = patch.source || 'linear';\n this.invalidateTracker(source).catch(() => {});\n }\n\n async invalidateTracker(tracker: string): Promise<void> {\n this.cache.invalidate(tracker);\n\n // Force full refresh on next poll (not incremental) so new issues\n // added to the cycle externally are discovered immediately\n if (tracker === 'linear') {\n this.linearLastFullRefresh = 0;\n }\n\n // Cancel current timer and fetch immediately\n const state = this.trackers[tracker];\n if (state?.timer) {\n clearTimeout(state.timer);\n state.timer = null;\n }\n\n switch (tracker) {\n case 'github': await this.pollGitHub(); break;\n case 'linear': await this.pollLinear(); break;\n case 'rally': await this.pollRally(); break;\n }\n\n this.pushSnapshot();\n this.scheduleNext(tracker);\n }\n\n /**\n * Get diagnostics for /api/cache-status endpoint.\n */\n getDiagnostics(): Record<string, any> {\n const result: Record<string, any> = {};\n for (const [tracker, state] of Object.entries(this.trackers)) {\n const limit = this.cache.getRateLimit(tracker);\n const intervals = POLL_INTERVALS[tracker as keyof typeof POLL_INTERVALS];\n const backoffMs = intervals ? this.cache.getBackoffMs(tracker, intervals.default) : 0;\n const suspendMs = this.cache.getSuspensionMs(tracker);\n result[tracker] = {\n remaining: limit?.remaining ?? null,\n total: limit?.total ?? null,\n backoffMs,\n suspendedUntil: suspendMs > 0 ? new Date(Date.now() + suspendMs).toISOString() : null,\n rateLimited: suspendMs > 0,\n pollInterval: state.currentInterval,\n lastFetched: state.lastFetchedAt,\n lastError: state.lastError,\n issueCount: state.lastFetchedIssues.length,\n };\n }\n return result;\n }\n\n // ---------------------------------------------------------------\n // Private: polling methods\n // ---------------------------------------------------------------\n\n private scheduleNext(tracker: string): void {\n const state = this.trackers[tracker];\n if (!this.started || !state) return;\n\n // Compute effective interval with backoff\n const intervals = POLL_INTERVALS[tracker as keyof typeof POLL_INTERVALS];\n if (!intervals) return;\n\n let delayMs: number;\n try {\n const backoffMs = this.cache.getBackoffMs(tracker, intervals.default);\n const effectiveInterval = Math.min(\n Math.max(intervals.default + backoffMs, intervals.min),\n intervals.max\n );\n\n // If the tracker is exhausted, suspend polling until the rate-limit window resets.\n // This bypasses intervals.max so we don't wake up before the quota resets.\n const suspendMs = this.cache.getSuspensionMs(tracker);\n delayMs = effectiveInterval;\n if (suspendMs > 0) {\n delayMs = Math.min(suspendMs, 3_600_000);\n console.warn(`[IssueDataService] ${tracker} tracker suspended until rate-limit reset; next poll in ${delayMs}ms`);\n }\n } catch (err: any) {\n console.error(`[IssueDataService] Cache read failed for ${tracker}; falling back to default interval:`, err.message);\n delayMs = intervals.default;\n }\n state.currentInterval = delayMs;\n\n state.timer = setTimeout(async () => {\n try {\n switch (tracker) {\n case 'github': await this.pollGitHub(); break;\n case 'linear': await this.pollLinear(); break;\n case 'rally': await this.pollRally(); break;\n }\n } catch (err: any) {\n console.error(`[IssueDataService] Error polling ${tracker}:`, err.message);\n state.lastError = err.message;\n }\n this.scheduleNext(tracker);\n }, delayMs);\n }\n\n private async ensureShadowStateLoaded(): Promise<void> {\n if (this.shadowStateModule) return;\n try {\n this.shadowStateModule = await import('../../../lib/shadow-state.js');\n await this.refreshShadowStatesCache();\n } catch {\n // Shadow state not available — issues will work without it\n }\n }\n\n /**\n * Populate the in-memory shadow-state cache by reading all shadow files\n * off the event loop (via fs/promises). Call after any write that may\n * have changed shadow state for any issue.\n */\n async refreshShadowStatesCache(): Promise<void> {\n if (!this.shadowStateModule) return;\n try {\n const states: Array<{ issueId: string; [k: string]: any }> =\n await this.shadowStateModule.listShadowedIssues();\n const next = new Map<string, any>();\n for (const state of states) {\n next.set(state.issueId.toLowerCase(), state);\n }\n this.shadowStatesCache = next;\n } catch {\n // Non-fatal — keep the previous cache\n }\n }\n\n private loadCachedData(): void {\n // Build a lookup of repo → prefix from current config for re-stamping stale identifiers\n const repoPrefixMap = new Map<string, string>();\n try {\n const ghConfig = getGitHubConfig();\n if (ghConfig) {\n for (const { owner, repo, prefix } of ghConfig.repos) {\n repoPrefixMap.set(`${owner}/${repo}`, prefix || repo.toUpperCase());\n }\n }\n } catch { /* ignore */ }\n\n for (const tracker of ['github', 'linear', 'rally']) {\n const cached = this.cache.getStale(tracker, 'issues');\n if (cached?.data) {\n // Sanitize stale Rally cache: rawTrackerState may be an object from pre-PAN-201 data\n if (tracker === 'rally') {\n let sanitizedCount = 0;\n cached.data = cached.data.map((issue: any) => {\n if (typeof issue.rawTrackerState === 'object' && issue.rawTrackerState !== null) {\n sanitizedCount++;\n return {\n ...issue,\n rawTrackerState: issue.rawTrackerState.Name || issue.rawTrackerState._refObjectName || 'Defined',\n };\n }\n return issue;\n });\n if (sanitizedCount > 0) {\n console.warn(`[IssueDataService] Rally cache: sanitized ${sanitizedCount} issues with object rawTrackerState (PAN-201)`);\n }\n }\n // Re-stamp GitHub identifiers in case prefix config changed since cache was written\n if (tracker === 'github') {\n cached.data = cached.data.map((issue: any) => {\n const repoKey = issue.sourceRepo;\n const prefix = repoKey ? repoPrefixMap.get(repoKey) : undefined;\n if (prefix) {\n // Extract issue number from id (github-owner-repo-NUMBER) or identifier (PREFIX-NUMBER)\n const issueNum = issue.id?.match(/-(\\d+)$/)?.[1] || issue.identifier?.match(/-(\\d+)$/)?.[1];\n if (issueNum) {\n const expectedId = `${prefix}-${issueNum}`;\n if (issue.identifier !== expectedId) {\n return { ...issue, identifier: expectedId };\n }\n }\n }\n return issue;\n });\n }\n this.trackers[tracker].lastFetchedIssues = cached.data;\n this.trackers[tracker].lastFetchedAt = cached.lastFetchedAt;\n }\n }\n const cachedIssues = this.getCachedTrackerIssues();\n this.schedulePlanningRefreshForIssues(cachedIssues);\n this.scheduleReviewStatusRefreshForIssues(cachedIssues);\n }\n\n private pushSnapshot(): void {\n this.invalidateGetIssuesCache();\n this._onIssuesChanged?.(this.getIssues());\n }\n\n private pushUpdated(): void {\n this.invalidateGetIssuesCache();\n const cachedIssues = this.getCachedTrackerIssues();\n this.schedulePlanningRefreshForIssues(cachedIssues);\n this.scheduleReviewStatusRefreshForIssues(cachedIssues);\n this._onIssuesChanged?.(this.getIssues());\n }\n\n private getCachedTrackerIssues(): any[] {\n return [\n ...this.trackers.github.lastFetchedIssues,\n ...this.trackers.linear.lastFetchedIssues,\n ...this.trackers.rally.lastFetchedIssues,\n ];\n }\n\n private schedulePlanningRefreshForIssues(issues: any[]): void {\n for (const issue of issues) {\n const identifier = typeof issue?.identifier === 'string' ? issue.identifier : '';\n if (!shouldRefreshPlanningStateForIssue(issue)) continue;\n if (!identifier || this.planningRefreshQueued.has(identifier) || planningStateRefreshInFlight.has(identifier)) continue;\n this.planningRefreshQueued.add(identifier);\n this.planningRefreshQueue.push(identifier);\n }\n this.drainPlanningRefreshQueue();\n }\n\n private drainPlanningRefreshQueue(): void {\n while (this.planningRefreshActive < PLANNING_REFRESH_CONCURRENCY && this.planningRefreshQueue.length > 0) {\n const identifier = this.planningRefreshQueue.shift()!;\n this.planningRefreshQueued.delete(identifier);\n this.planningRefreshActive++;\n void refreshPlanningState(identifier).then((changed) => {\n if (changed) this.queuePlanningSnapshot();\n }).finally(() => {\n this.planningRefreshActive--;\n this.drainPlanningRefreshQueue();\n });\n }\n }\n\n private queuePlanningSnapshot(): void {\n if (this.planningSnapshotQueued) return;\n this.planningSnapshotQueued = true;\n setTimeout(() => {\n this.planningSnapshotQueued = false;\n if (this.started) this.pushSnapshot();\n }, 50);\n }\n\n private scheduleReviewStatusRefreshForIssues(issues: any[]): void {\n for (const issue of issues) {\n const identifier = typeof issue?.identifier === 'string' ? issue.identifier : '';\n if (identifier) this.reviewStatusRefreshIssueIds.add(identifier);\n }\n if (this.reviewStatusRefreshQueued || this.reviewStatusRefreshIssueIds.size === 0) return;\n this.reviewStatusRefreshQueued = true;\n setImmediate(() => {\n this.reviewStatusRefreshQueued = false;\n const issueIds = [...this.reviewStatusRefreshIssueIds];\n this.reviewStatusRefreshIssueIds.clear();\n try {\n this.reviewStatusesCache = {\n ...this.reviewStatusesCache,\n ...loadReviewStatusesForIssues(issueIds),\n };\n if (this.started) this.pushSnapshot();\n } catch {\n // review-status store may not exist yet\n }\n });\n }\n\n private pushMeta(): void {\n // Diagnostics are served via GET /api/issues/diagnostics — no push needed\n }\n\n // ---------------------------------------------------------------\n // GitHub polling — uses Octokit REST + ETags (304 = FREE)\n // ---------------------------------------------------------------\n\n private async pollGitHub(): Promise<void> {\n const config = getGitHubConfig();\n if (!config) {\n this.trackers.github.lastFetchedIssues = [];\n return;\n }\n\n const allIssues: any[] = [];\n const octokit = new Octokit({ auth: config.token });\n\n for (const { owner, repo, prefix } of config.repos) {\n try {\n // Fetch open issues with ETag support\n const openIssues = await this.fetchGitHubRepoIssues(\n octokit, owner, repo, 'open', prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, ''),\n `github:open:${owner}/${repo}`\n );\n\n // Fetch recently closed issues\n const closedIssues = await this.fetchGitHubRepoIssues(\n octokit, owner, repo, 'closed', prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, ''),\n `github:closed:${owner}/${repo}`\n );\n\n allIssues.push(...openIssues, ...closedIssues);\n } catch (error: any) {\n console.error(`[IssueDataService] Error fetching GitHub issues for ${owner}/${repo}:`, error.message);\n this.trackers.github.lastError = error.message;\n }\n }\n\n // Check if data actually changed (cheap length + updatedAt check)\n const oldData = this.trackers.github.lastFetchedIssues;\n const changed = issuesChanged(allIssues, oldData);\n\n this.trackers.github.lastFetchedIssues = allIssues;\n this.trackers.github.lastFetchedAt = new Date().toISOString();\n this.trackers.github.lastError = null;\n\n // Persist to cache\n this.cache.set('github', 'issues', allIssues, { ttlSeconds: DEFAULT_TTLS.github });\n\n if (changed) {\n console.log(`[IssueDataService] GitHub: ${allIssues.length} issues (changed)`);\n this.pushUpdated();\n this.pushMeta();\n }\n }\n\n private async fetchGitHubRepoIssues(\n octokit: Octokit,\n owner: string,\n repo: string,\n state: 'open' | 'closed',\n issuePrefix: string,\n cacheKey: string,\n ): Promise<any[]> {\n // Get stored ETag for conditional request\n const cachedEtag = this.cache.getEtag('github', cacheKey);\n\n const requestParams: any = {\n owner,\n repo,\n state,\n per_page: 100,\n sort: 'updated' as const,\n direction: 'desc' as const,\n };\n\n // Only send If-None-Match when we have a cached ETag\n if (cachedEtag) {\n requestParams.headers = { 'If-None-Match': cachedEtag };\n }\n\n // Fetch recently updated closed issues; SQLite L2 cache keeps older closed\n // issues visible within TTL across restarts while the windowed fetch runs.\n if (state === 'closed') {\n requestParams.per_page = 100;\n const windowDays = this.loadConfig().config.issues.closedWindowDays;\n requestParams.since = new Date(Date.now() - windowDays * DAY_MS).toISOString();\n }\n\n try {\n // Use paginate to fetch ALL pages (not just the first 100)\n let newEtag: string | undefined;\n const allData = await octokit.paginate(octokit.issues.listForRepo, requestParams, (response) => {\n // Extract rate limit from each response\n const remaining = parseInt(response.headers['x-ratelimit-remaining'] as string);\n const total = parseInt(response.headers['x-ratelimit-limit'] as string);\n const resetAt = new Date(parseInt(response.headers['x-ratelimit-reset'] as string) * 1000).toISOString();\n\n if (!isNaN(remaining) && !isNaN(total)) {\n this.cache.updateRateLimit('github', { remaining, total, resetAt });\n }\n\n // Store ETag from first page (used for conditional requests on next poll)\n if (!newEtag && response.headers.etag) {\n newEtag = response.headers.etag as string;\n }\n\n return response.data;\n });\n\n // Filter out PRs (they have pull_request key)\n const issues = allData.filter((issue: any) => !issue.pull_request);\n\n // Format issues to match dashboard schema\n const formatted = issues.map((issue: any) => {\n const labelNames = issue.labels?.map((l: any) => typeof l === 'string' ? l : l.name) || [];\n const canonicalStatus = mapGitHubStateToCanonical(issue.state || '', labelNames);\n const identifier = `${issuePrefix}-${issue.number}`;\n\n const firstAssignee = issue.assignees?.[0] || issue.assignee;\n\n return {\n id: `github-${owner}-${repo}-${issue.number}`,\n identifier,\n title: issue.title,\n description: issue.body || '',\n author: issue.user?.login || '',\n status: canonicalStatus === 'todo' ? 'Todo' :\n canonicalStatus === 'in_progress' ? 'In Progress' :\n canonicalStatus === 'in_review' ? 'In Review' :\n canonicalStatus === 'verifying_on_main' ? 'Verifying' :\n canonicalStatus === 'done' ? 'Done' :\n canonicalStatus === 'backlog' ? 'Backlog' : 'Todo',\n canonicalStatus,\n state: canonicalStatus,\n priority: labelNames.some((l: string) => l.includes('priority') && l.includes('high')) ? 2 :\n labelNames.some((l: string) => l.includes('priority') && l.includes('urgent')) ? 1 :\n labelNames.some((l: string) => l.includes('priority') && l.includes('low')) ? 4 : 3,\n assignee: firstAssignee ? {\n name: firstAssignee.login,\n email: `${firstAssignee.login}@github`,\n } : undefined,\n labels: labelNames,\n url: issue.html_url,\n createdAt: issue.created_at,\n updatedAt: issue.updated_at,\n completedAt: issue.closed_at,\n project: {\n id: `github-${owner}-${repo}`,\n name: `${owner}/${repo}`,\n color: '#333',\n icon: 'github',\n },\n source: 'github',\n sourceRepo: `${owner}/${repo}`,\n };\n });\n\n // Cache with ETag\n this.cache.set('github', cacheKey, formatted, {\n etag: newEtag,\n ttlSeconds: DEFAULT_TTLS.github,\n });\n\n return formatted;\n } catch (err: any) {\n // 304 Not Modified — return cached data (this is FREE, no rate limit cost)\n if (err.status === 304) {\n const cached = this.cache.getStale('github', cacheKey);\n if (!cached?.data) return [];\n // Re-stamp identifiers in case the prefix changed since the cache was written\n return cached.data.map((issue: any) => {\n const issueNum = issue.id?.match(/-(\\d+)$/)?.[1] || issue.identifier?.match(/-(\\d+)$/)?.[1];\n if (issueNum) {\n const expectedId = `${issuePrefix}-${issueNum}`;\n return issue.identifier === expectedId ? issue : { ...issue, identifier: expectedId };\n }\n return issue;\n });\n }\n throw err;\n }\n }\n\n // ---------------------------------------------------------------\n // Linear polling — TTL + incremental updatedAt\n // ---------------------------------------------------------------\n\n private async pollLinear(): Promise<void> {\n const apiKey = getLinearApiKey();\n if (!apiKey) {\n this.trackers.linear.lastFetchedIssues = [];\n return;\n }\n\n const now = Date.now();\n const needsFullRefresh = now - this.linearLastFullRefresh > LINEAR_FULL_REFRESH_MS;\n\n // Get the most recent updatedAt from cached issues for incremental fetch\n let sinceUpdatedAt: string | null = null;\n if (!needsFullRefresh && this.trackers.linear.lastFetchedIssues.length > 0) {\n const maxUpdated = this.trackers.linear.lastFetchedIssues.reduce((max: string, issue: any) => {\n return issue.updatedAt > max ? issue.updatedAt : max;\n }, '');\n if (maxUpdated) sinceUpdatedAt = maxUpdated;\n }\n\n try {\n const fetchedIssues = await this.fetchLinearIssues(apiKey, sinceUpdatedAt);\n\n let allIssues: any[];\n if (sinceUpdatedAt && fetchedIssues.length > 0) {\n // Incremental: merge new/updated issues into existing list\n const existingMap = new Map(\n this.trackers.linear.lastFetchedIssues.map((i: any) => [i.identifier, i])\n );\n for (const issue of fetchedIssues) {\n existingMap.set(issue.identifier, issue);\n }\n allIssues = Array.from(existingMap.values());\n } else if (needsFullRefresh || sinceUpdatedAt === null) {\n // Full refresh\n allIssues = fetchedIssues;\n this.linearLastFullRefresh = now;\n } else {\n // No new data from incremental fetch\n allIssues = this.trackers.linear.lastFetchedIssues;\n }\n\n const oldData = this.trackers.linear.lastFetchedIssues;\n const changed = issuesChanged(allIssues, oldData);\n\n this.trackers.linear.lastFetchedIssues = allIssues;\n this.trackers.linear.lastFetchedAt = new Date().toISOString();\n this.trackers.linear.lastError = null;\n\n this.cache.set('linear', 'issues', allIssues, { ttlSeconds: DEFAULT_TTLS.linear });\n\n if (changed) {\n console.log(`[IssueDataService] Linear: ${allIssues.length} issues (changed)`);\n this.pushUpdated();\n this.pushMeta();\n }\n } catch (err: any) {\n console.error('[IssueDataService] Linear poll error:', err.message);\n this.trackers.linear.lastError = err.message;\n }\n }\n\n private normalizeLinearResetAt(resetValue: number | null): string | null {\n if (resetValue === null) return null;\n // Linear sends reset as Unix milliseconds, but other trackers use Unix seconds.\n // Defensive normalization: values below 1e12 are seconds, otherwise milliseconds.\n const epochMs = resetValue < 1_000_000_000_000 ? resetValue * 1000 : resetValue;\n return new Date(epochMs).toISOString();\n }\n\n private recordLinearExhaustion(response: Response, nowMs: number): void {\n const existing = this.cache.getRateLimit('linear');\n const total =\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-limit') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-limit') ??\n existing?.total ??\n 2500;\n\n let resetAt = this.normalizeLinearResetAt(\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-reset') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-reset')\n );\n if (!resetAt) {\n const retryAfter = parseIntegerHeader(response.headers, 'retry-after');\n if (retryAfter !== null) {\n resetAt = new Date(nowMs + retryAfter * 1000).toISOString();\n } else {\n resetAt = new Date(nowMs + 3_600_000).toISOString();\n }\n }\n\n this.cache.updateRateLimit('linear', { remaining: 0, total, resetAt });\n }\n\n private async fetchLinearIssues(apiKey: string, sinceUpdatedAt: string | null): Promise<any[]> {\n const allIssues: any[] = [];\n let hasMore = true;\n let cursor: string | null | undefined;\n\n // Build filter conditions\n const filterConditions: string[] = [];\n // Scope to active cycle only — completed/canceled filtering is handled\n // by getIssues() post-filter, NOT here. The GraphQL query must fetch\n // completed issues so that: (1) the \"Include completed\" toggle works,\n // (2) incremental updates correctly reflect state transitions, and\n // (3) internal getIssues() callers can look up recently-completed issues.\n filterConditions.push('cycle: { isActive: { eq: true } }');\n\n // Incremental: only issues updated after sinceUpdatedAt\n if (sinceUpdatedAt) {\n filterConditions.push(`updatedAt: { gt: \"${sinceUpdatedAt}\" }`);\n }\n\n let filterClause = '';\n if (filterConditions.length === 1) {\n filterClause = `filter: { ${filterConditions[0]} }`;\n } else if (filterConditions.length > 1) {\n filterClause = `filter: { and: [${filterConditions.map(c => `{ ${c} }`).join(', ')}] }`;\n }\n\n while (hasMore) {\n const query = `\n query GetIssues($after: String) {\n issues(first: 100, after: $after, ${filterClause ? filterClause + ', ' : ''}orderBy: updatedAt) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n identifier\n title\n description\n priority\n url\n createdAt\n updatedAt\n completedAt\n state {\n name\n type\n }\n assignee {\n name\n email\n }\n labels {\n nodes {\n name\n }\n }\n project {\n id\n name\n color\n icon\n }\n team {\n id\n name\n color\n icon\n }\n cycle {\n id\n name\n number\n }\n }\n }\n }\n `;\n\n const response = await fetch('https://api.linear.app/graphql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': apiKey,\n },\n body: JSON.stringify({ query, variables: { after: cursor } }),\n });\n\n // Parse Linear rate-limit headers before reading the body or throwing on errors.\n // Linear uses x-ratelimit-requests-*; fall back to the bare x-ratelimit-* names.\n const rlRemaining =\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-remaining') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-remaining');\n const rlTotal =\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-limit') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-limit');\n const rlReset =\n this.normalizeLinearResetAt(\n parseIntegerHeader(response.headers, 'x-ratelimit-requests-reset') ??\n parseIntegerHeader(response.headers, 'x-ratelimit-reset')\n );\n if (rlRemaining !== null && rlTotal !== null && rlReset !== null) {\n this.cache.updateRateLimit('linear', { remaining: rlRemaining, total: rlTotal, resetAt: rlReset });\n }\n\n // Detect quota exhaustion on HTTP 429 before attempting to parse the body.\n if (response.status === 429) {\n this.recordLinearExhaustion(response, Date.now());\n let bodyText = '';\n try { bodyText = await response.text(); } catch { /* ignore unreadable body */ }\n let message = `Linear API error: HTTP 429`;\n try {\n const body = JSON.parse(bodyText);\n if (body?.errors?.[0]?.message) message = body.errors[0].message;\n } catch { /* ignore non-JSON body */ }\n throw new Error(message);\n }\n\n const json = await response.json() as {\n errors?: Array<{ message?: string }>;\n data?: {\n issues?: {\n nodes: any[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n };\n };\n\n if (json.errors) {\n const message = json.errors[0]?.message || 'Linear GraphQL error';\n if (/rate limit|ratelimited/i.test(message)) {\n this.recordLinearExhaustion(response, Date.now());\n }\n throw new Error(message);\n }\n\n const issues = json.data?.issues;\n if (!issues) break;\n\n allIssues.push(...issues.nodes);\n hasMore = issues.pageInfo.hasNextPage;\n cursor = issues.pageInfo.endCursor;\n\n if (allIssues.length > 1000) break;\n }\n\n // Build project lookup for deduplication\n const projectByName = new Map<string, { id: string; name: string; color?: string; icon?: string }>();\n for (const issue of allIssues) {\n if (issue.project && !projectByName.has(issue.project.name)) {\n projectByName.set(issue.project.name, {\n id: issue.project.id,\n name: issue.project.name,\n color: issue.project.color,\n icon: issue.project.icon,\n });\n }\n }\n\n // Format to dashboard schema\n return allIssues.map((issue: any) => {\n let project;\n if (issue.project) {\n project = {\n id: issue.project.id,\n name: issue.project.name,\n color: issue.project.color,\n icon: issue.project.icon,\n };\n } else if (issue.team) {\n const existing = projectByName.get(issue.team.name);\n project = existing || {\n id: issue.team.id,\n name: issue.team.name,\n color: issue.team.color,\n icon: issue.team.icon,\n };\n }\n\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description,\n status: issue.state?.name || 'Backlog',\n stateType: issue.state?.type,\n priority: issue.priority,\n assignee: issue.assignee ? { name: issue.assignee.name, email: issue.assignee.email } : undefined,\n labels: issue.labels?.nodes?.map((l: any) => l.name) || [],\n url: issue.url,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n completedAt: issue.completedAt,\n project,\n cycle: issue.cycle ? {\n id: issue.cycle.id,\n name: issue.cycle.name,\n number: issue.cycle.number,\n } : undefined,\n source: 'linear',\n };\n });\n }\n\n // ---------------------------------------------------------------\n // Rally polling — TTL-based caching, per-project config support\n // ---------------------------------------------------------------\n\n /**\n * Format a raw Rally issue into the dashboard schema.\n */\n private formatRallyIssue(issue: any, projectInfo: { id: string; name: string; color: string; icon: string }): any {\n const canonicalStatus = mapRallyStateToCanonical(issue.state);\n const identifier = issue.ref || issue.id || 'unknown';\n if (typeof issue.rawState === 'object' && issue.rawState !== null) {\n console.warn(`[IssueDataService] Rally ${identifier}: rawState is object, normalizing (PAN-201)`);\n }\n return {\n id: `rally-${issue.id || identifier}`,\n identifier,\n title: issue.title || '',\n description: issue.description || '',\n status: canonicalStatus === 'todo' ? 'Todo' :\n canonicalStatus === 'in_progress' ? 'In Progress' :\n canonicalStatus === 'done' ? 'Done' : 'Todo',\n priority: issue.priority ?? 3,\n assignee: issue.assignee ? {\n name: issue.assignee,\n email: `${issue.assignee.replace(/\\s+/g, '.').toLowerCase()}@rally`,\n } : undefined,\n labels: Array.isArray(issue.labels) ? issue.labels.filter((l: any) => typeof l === 'string') : [],\n url: issue.url || '',\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n parentRef: issue.parentRef,\n artifactType: issue.artifactType,\n rawTrackerState: typeof issue.rawState === 'object' && issue.rawState !== null\n ? (issue.rawState.Name || issue.rawState._refObjectName || 'Defined')\n : issue.rawState,\n project: projectInfo,\n source: 'rally',\n };\n }\n\n /**\n * Compute derived feature status from child stories.\n * If ANY child is in progress, the feature is derived as 'in_progress'.\n * If ALL children are done, the feature is derived as 'closed'.\n * Attaches child counts for progress display.\n */\n private computeDerivedFeatureStatus(issues: any[]): any[] {\n // Build children-by-parent map (key: parent identifier, value: child issues)\n const childrenByParent = new Map<string, any[]>();\n for (const issue of issues) {\n if (issue.parentRef) {\n const existing = childrenByParent.get(issue.parentRef) || [];\n existing.push(issue);\n childrenByParent.set(issue.parentRef, existing);\n }\n }\n\n // For each Feature, compute derived status\n return issues.map(issue => {\n const isFeature = issue.artifactType?.includes('PortfolioItem');\n if (!isFeature) return issue;\n\n const children = childrenByParent.get(issue.identifier) || [];\n if (children.length === 0) return issue;\n\n const completedChildCount = children.filter(\n (c: any) => c.status === 'Done'\n ).length;\n const inProgressChildCount = children.filter(\n (c: any) => c.status === 'In Progress'\n ).length;\n const totalChildCount = children.length;\n\n let derivedStatus: string | undefined;\n if (completedChildCount === totalChildCount) {\n derivedStatus = 'closed';\n } else if (inProgressChildCount > 0) {\n derivedStatus = 'in_progress';\n }\n\n return {\n ...issue,\n derivedStatus,\n totalChildCount,\n completedChildCount,\n inProgressChildCount,\n };\n });\n }\n\n private async pollRally(): Promise<void> {\n const globalConfig = getRallyConfig();\n if (!globalConfig) {\n this.trackers.rally.lastFetchedIssues = [];\n return;\n }\n\n // Validate config on first poll and log warnings\n if (!this.trackers.rally.lastFetchedAt) {\n const validation = validateRallyConfig(globalConfig);\n if (validation.warnings.length > 0) {\n console.warn('[Rally] Configuration warnings:', validation.warnings.join('; '));\n }\n }\n\n // Only fetch if cache is stale\n if (!this.cache.isStale('rally', 'issues') && this.trackers.rally.lastFetchedIssues.length > 0) {\n return;\n }\n\n try {\n const { RallyTracker } = await import('../../../lib/tracker/rally.js');\n const { findProjectsByRallyProject } = await import('../../../lib/projects.js');\n\n const rallyProjects = findProjectsByRallyProject();\n let allFormatted: any[] = [];\n\n if (rallyProjects.length > 0) {\n // Per-project mode: create separate tracker per Rally project OID\n const projectQueries = rallyProjects.map(async ({ key, config: projConfig }) => {\n try {\n const tracker = new RallyTracker({\n apiKey: globalConfig.apiKey,\n server: globalConfig.server,\n workspace: globalConfig.workspace,\n project: projConfig.rally_project,\n });\n\n const issues = await Effect.runPromise(tracker.listIssues({\n includeClosed: false,\n limit: 100,\n }));\n\n const projectInfo = {\n id: `rally-${key}`,\n name: projConfig.name,\n color: '#00C7B1',\n icon: 'rally',\n };\n\n return issues.map((issue: any) => this.formatRallyIssue(issue, projectInfo));\n } catch (err: any) {\n console.error(`[IssueDataService] Rally poll error for project ${key}:`, err.message);\n return [];\n }\n });\n\n const results = await Promise.all(projectQueries);\n allFormatted = results.flat();\n } else {\n // Fallback: use global RALLY_PROJECT env (backward compat)\n const tracker = new RallyTracker({\n apiKey: globalConfig.apiKey,\n server: globalConfig.server,\n workspace: globalConfig.workspace,\n project: globalConfig.project,\n });\n\n const issues = await Effect.runPromise(tracker.listIssues({\n includeClosed: false,\n limit: 100,\n }));\n\n const projectInfo = {\n id: 'rally-project',\n name: 'Rally',\n color: '#00C7B1',\n icon: 'rally',\n };\n\n allFormatted = issues.map((issue: any) => this.formatRallyIssue(issue, projectInfo));\n }\n\n // Compute derived feature status from child stories\n allFormatted = this.computeDerivedFeatureStatus(allFormatted);\n\n const oldData = this.trackers.rally.lastFetchedIssues;\n const changed = issuesChanged(allFormatted, oldData);\n\n this.trackers.rally.lastFetchedIssues = allFormatted;\n this.trackers.rally.lastFetchedAt = new Date().toISOString();\n this.trackers.rally.lastError = null;\n\n this.cache.set('rally', 'issues', allFormatted, { ttlSeconds: DEFAULT_TTLS.rally });\n\n if (changed) {\n console.log(`[IssueDataService] Rally: ${allFormatted.length} issues (changed)`);\n this.pushUpdated();\n this.pushMeta();\n }\n } catch (err: any) {\n const errorMsg = err.message?.includes('Could not parse')\n ? `${err.message} - Check Rally workspace/project configuration. Enable DEBUG=rally for query details.`\n : err.message;\n console.error('[IssueDataService] Rally poll error:', errorMsg);\n this.trackers.rally.lastError = errorMsg;\n }\n }\n}\n","/**\n * Shared IssueDataService singleton — used by read model (bootstrap),\n * route handlers (force-refresh, diagnostics), and event emission.\n */\nimport { IssueDataService } from './issue-data-service.js';\nimport { CacheService } from './cache-service.js';\n\nlet _service: IssueDataService | null = null;\nlet _startPromise: Promise<void> | null = null;\n\nexport function getSharedIssueService(): IssueDataService {\n if (!_service) {\n _service = new IssueDataService(new CacheService());\n }\n return _service;\n}\n\nexport function startSharedIssueService(options?: { skipPolling?: boolean }): Promise<void> {\n if (_startPromise) return _startPromise;\n _startPromise = getSharedIssueService().start(options).catch((err: unknown) => {\n console.error('[issue-service-singleton] start failed:', err);\n });\n return _startPromise;\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { o as init_archive_planning, t as archivePlanning } from "./archive-planning-zTvWWHG_.js";
|
|
2
2
|
import { a as stepSkipped, i as stepOk, n as init_types, r as stepFailed } from "./types-B5u3NxMl.js";
|
|
3
|
-
import { t as closeIssue } from "./close-issue-
|
|
4
|
-
import { t as teardownWorkspace } from "./teardown-workspace-
|
|
3
|
+
import { t as closeIssue } from "./close-issue-BWf3pxn4.js";
|
|
4
|
+
import { t as teardownWorkspace } from "./teardown-workspace-DhA6oAIK.js";
|
|
5
5
|
import { n as init_compact_beads, t as compactBeads } from "./compact-beads-RFwdZFPA.js";
|
|
6
|
-
import { a as deepWipe, i as closeOut, n as cancelIssueWorkflow, o as resetToTodo, r as close, t as approve } from "./workflows-
|
|
6
|
+
import { a as deepWipe, i as closeOut, n as cancelIssueWorkflow, o as resetToTodo, r as close, t as approve } from "./workflows-CSKX5MhC.js";
|
|
7
7
|
//#region ../../lib/lifecycle/index.ts
|
|
8
8
|
init_types();
|
|
9
9
|
init_archive_planning();
|
|
@@ -11,4 +11,4 @@ init_compact_beads();
|
|
|
11
11
|
//#endregion
|
|
12
12
|
export { approve, archivePlanning, cancelIssueWorkflow, close, closeIssue, closeOut, compactBeads, deepWipe, resetToTodo, stepFailed, stepOk, stepSkipped, teardownWorkspace };
|
|
13
13
|
|
|
14
|
-
//# sourceMappingURL=lifecycle-
|
|
14
|
+
//# sourceMappingURL=lifecycle-CEg4sHZH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle-
|
|
1
|
+
{"version":3,"file":"lifecycle-CEg4sHZH.js","names":[],"sources":["../../src/lib/lifecycle/index.ts"],"sourcesContent":["/**\n * Lifecycle — Composable, idempotent post-merge operations.\n *\n * Atomic operations:\n * archive-planning — PRD active→completed + workspace .pan/ preservation\n * close-issue — IssueTracker transition + label management\n * teardown-workspace — Full workspace cleanup (agent, worktree, Docker, tmux)\n * compact-beads — Beads compaction + git commit/push\n *\n * Workflows (compose the above):\n * approve() — merge + close + archive + teardown + compact\n * close() — close + teardown\n * closeOut() — verify-merged + archive + teardown + close + label + clear-status\n * deepWipe() — teardown(deleteBranches) + delete agent state + reset issue\n */\n\n// Types\nexport type {\n StepResult,\n WorkflowResult,\n LifecycleContext,\n TeardownOptions,\n ArchiveOptions,\n ApproveOptions,\n DeepWipeOptions,\n} from './types.js';\n\nexport { stepOk, stepSkipped, stepFailed } from './types.js';\n\n// Atomic operations\nexport { archivePlanning } from './archive-planning.js';\nexport { closeIssue } from './close-issue.js';\nexport { teardownWorkspace } from './teardown-workspace.js';\nexport { compactBeads } from './compact-beads.js';\n\n// Workflows\nexport { approve, close, closeOut, deepWipe, resetToTodo, cancelIssueWorkflow } from './workflows.js';\n"],"mappings":";;;;;;;YA2B6D;uBAGL;oBAGN"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { _ as scanGitPatterns, a as init_merge_agent, c as isSyncMainMainPreferredPath, d as parseAgentOutput, f as postMergeLifecycle, g as scanForConflictMarkers, h as salvageStrandedMerge, i as captureTmuxOutput, l as logMergeHistory, m as runProjectQualityGates, n as GIT_PATTERNS, o as isAutoCommitExcludedPath, p as resetPostMergeState, r as autoCommitWorkspaceChangesBeforeSync, s as isMergeAgentRunning, t as AUTO_COMMIT_EXCLUDED_PATHS, u as notifyTldrDaemon, v as sendMessageToAgent, y as syncMainIntoWorkspace } from "./merge-agent-
|
|
1
|
+
import { _ as scanGitPatterns, a as init_merge_agent, c as isSyncMainMainPreferredPath, d as parseAgentOutput, f as postMergeLifecycle, g as scanForConflictMarkers, h as salvageStrandedMerge, i as captureTmuxOutput, l as logMergeHistory, m as runProjectQualityGates, n as GIT_PATTERNS, o as isAutoCommitExcludedPath, p as resetPostMergeState, r as autoCommitWorkspaceChangesBeforeSync, s as isMergeAgentRunning, t as AUTO_COMMIT_EXCLUDED_PATHS, u as notifyTldrDaemon, v as sendMessageToAgent, y as syncMainIntoWorkspace } from "./merge-agent-C81xPIGd.js";
|
|
2
2
|
init_merge_agent();
|
|
3
3
|
export { AUTO_COMMIT_EXCLUDED_PATHS, GIT_PATTERNS, autoCommitWorkspaceChangesBeforeSync, captureTmuxOutput, isAutoCommitExcludedPath, isMergeAgentRunning, isSyncMainMainPreferredPath, logMergeHistory, notifyTldrDaemon, parseAgentOutput, postMergeLifecycle, resetPostMergeState, runProjectQualityGates, salvageStrandedMerge, scanForConflictMarkers, scanGitPatterns, sendMessageToAgent, syncMainIntoWorkspace };
|
|
@@ -3,7 +3,7 @@ import { Nn as init_projects, Or as OVERDECK_HOME, Vr as init_paths, _r as VcsEr
|
|
|
3
3
|
import { a as init_activity_logger, n as emitActivityEntrySync, r as emitActivityTtsSync } from "./activity-logger-pcbf-e30.js";
|
|
4
4
|
import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-CpKlYZA9.js";
|
|
5
5
|
import { L as sessionExists, O as listSessionNames, P as sendKeys, i as capturePane, w as killSession, x as init_tmux } from "./tmux-ePNhMIMU.js";
|
|
6
|
-
import { c as init_review_status, f as markWorkspaceStuck, y as setReviewStatusSync } from "./review-status-
|
|
6
|
+
import { c as init_review_status, f as markWorkspaceStuck, y as setReviewStatusSync } from "./review-status-B5ZASOo4.js";
|
|
7
7
|
import { i as resolveGitHubIssueSync, n as init_tracker_utils } from "./tracker-utils-bpWs7Xyx.js";
|
|
8
8
|
import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population-Dwyj9gEM.js";
|
|
9
9
|
import { n as restoreTrackedBeadsExport, t as init_beads_restore } from "./beads-restore-CMbDSB5R.js";
|
|
@@ -545,7 +545,7 @@ async function postMergeLifecycle(issueId, projectPath, sourceBranch, options) {
|
|
|
545
545
|
console.warn(`[merge-agent] Beads compaction failed: ${err}`);
|
|
546
546
|
}
|
|
547
547
|
try {
|
|
548
|
-
const { setAgentPaused, getAgentState } = await import("./agents-
|
|
548
|
+
const { setAgentPaused, getAgentState } = await import("./agents-C-vqmKDi.js");
|
|
549
549
|
const { killSession, sessionExists } = await import("./tmux-CeDD14p1.js");
|
|
550
550
|
const issueLower = issueId.toLowerCase();
|
|
551
551
|
const reason = "awaiting close-out (verify on main)";
|
|
@@ -571,7 +571,7 @@ async function postMergeLifecycle(issueId, projectPath, sourceBranch, options) {
|
|
|
571
571
|
console.warn(`[merge-agent] Could not pause or kill agent sessions: ${err}`);
|
|
572
572
|
}
|
|
573
573
|
try {
|
|
574
|
-
const { killAllReviewerSessions } = await import("./review-agent-
|
|
574
|
+
const { killAllReviewerSessions } = await import("./review-agent-DTINEffB.js");
|
|
575
575
|
const { resolveProjectFromIssueSync } = await import("./projects-BNmVxnNt.js");
|
|
576
576
|
const projectKey = resolveProjectFromIssueSync(issueId)?.projectKey;
|
|
577
577
|
if (projectKey) {
|
|
@@ -586,7 +586,7 @@ async function postMergeLifecycle(issueId, projectPath, sourceBranch, options) {
|
|
|
586
586
|
}
|
|
587
587
|
await killPostMergeRoleSessions(issueId);
|
|
588
588
|
try {
|
|
589
|
-
const { createResetMarker } = await import("./cli-
|
|
589
|
+
const { createResetMarker } = await import("./cli-DCqy_wFa.js");
|
|
590
590
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
591
591
|
const marker = await createResetMarker({
|
|
592
592
|
projectId: basename(projectPath),
|
|
@@ -630,7 +630,7 @@ async function postMergeLifecycle(issueId, projectPath, sourceBranch, options) {
|
|
|
630
630
|
async function transitionIssueToVerifyingOnMain(issueId, projectPath) {
|
|
631
631
|
const [effectModule, issueLifecycleModule, githubModule, linearModule, rallyModule, errorsModule] = await Promise.all([
|
|
632
632
|
import("effect"),
|
|
633
|
-
import("./issue-lifecycle-
|
|
633
|
+
import("./issue-lifecycle-CjYqQ669.js"),
|
|
634
634
|
import("./github-client-VRiB6N-4.js"),
|
|
635
635
|
import("./linear-client-BxKvL6U8.js"),
|
|
636
636
|
import("./rally-client-X0bEEN2B.js"),
|
|
@@ -1366,4 +1366,4 @@ var init_merge_agent = __esmMin((() => {
|
|
|
1366
1366
|
//#endregion
|
|
1367
1367
|
export { init_git_activity as C, init_operations as S, scanGitPatterns as _, init_merge_agent as a, MainDivergedError as b, isSyncMainMainPreferredPath as c, parseAgentOutput as d, postMergeLifecycle as f, scanForConflictMarkers as g, salvageStrandedMerge as h, captureTmuxOutput as i, logMergeHistory as l, runProjectQualityGates as m, GIT_PATTERNS as n, isAutoCommitExcludedPath as o, resetPostMergeState as p, autoCommitWorkspaceChangesBeforeSync as r, isMergeAgentRunning as s, AUTO_COMMIT_EXCLUDED_PATHS as t, notifyTldrDaemon as u, sendMessageToAgent as v, listGitOperationsSync as w, gitPush as x, syncMainIntoWorkspace as y };
|
|
1368
1368
|
|
|
1369
|
-
//# sourceMappingURL=merge-agent-
|
|
1369
|
+
//# sourceMappingURL=merge-agent-C81xPIGd.js.map
|