@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":"flywheel-DefHRzY4.js","names":["execAsync","execAsync","nodeServicesLayer"],"sources":["../../src/lib/flywheel-start.ts","../../src/cli/commands/flywheel-merge-backend.ts","../../src/cli/commands/flywheel-surfaces.ts","../../src/cli/commands/flywheel.ts"],"sourcesContent":["import { exec } from 'node:child_process';\nimport { readFile, realpath } from 'node:fs/promises';\nimport { dirname, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\nexport const DEFAULT_BRIEF_PATH = 'docs/flywheel-brief.md';\n\nfunction isInsideRoot(projectRoot: string, candidate: string): boolean {\n const relativePath = relative(projectRoot, candidate);\n return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));\n}\n\nexport function resolveFlywheelStartBriefPath(cwd: string, requestedPath?: string): { absolutePath: string; displayPath: string } {\n const rawPath = requestedPath?.trim() || DEFAULT_BRIEF_PATH;\n if (rawPath.includes('\\0')) throw new Error('Brief path is invalid');\n\n const root = resolve(cwd);\n const absolutePath = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);\n if (!isInsideRoot(root, absolutePath)) throw new Error('Brief path must stay inside the project root');\n\n const normalizedRoot = root.endsWith(sep) ? root : `${root}${sep}`;\n const displayPath = absolutePath === root ? '.' : relative(root, absolutePath);\n return { absolutePath, displayPath: absolutePath.startsWith(normalizedRoot) ? displayPath : absolutePath };\n}\n\nasync function assertExistingPathInsideRoot(projectRoot: string, candidate: string): Promise<void> {\n const [realRoot, realCandidate] = await Promise.all([realpath(projectRoot), realpath(candidate)]);\n if (!isInsideRoot(realRoot, realCandidate)) throw new Error('Brief path must stay inside the project root');\n}\n\nexport async function requireFlywheelBrief(cwd: string, requestedPath?: string): Promise<{ absolutePath: string; displayPath: string }> {\n const resolved = resolveFlywheelStartBriefPath(cwd, requestedPath);\n try {\n await assertExistingPathInsideRoot(cwd, resolved.absolutePath);\n await readFile(resolved.absolutePath, 'utf8');\n return resolved;\n } catch (error) {\n const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;\n if (code === 'ENOENT') throw new Error(`Flywheel brief not found: ${resolved.displayPath}`);\n throw error;\n }\n}\n\n/**\n * The flywheel is a project-scoped singleton: it must run from the PRIMARY\n * worktree root, never a linked feature worktree. Every worktree contains\n * docs/flywheel-brief.md, so the brief check alone cannot catch a caller whose\n * shell happens to sit inside a workspaces/feature-* checkout, and an\n * orchestrator spawned there operates inside a live work agent's worktree.\n * A linked worktree is\n * detected by `git rev-parse --git-common-dir` resolving to the primary's\n * .git directory; outside a repo (or on failure) the cwd is kept and the\n * brief check reports its own error.\n */\n\nexport async function resolvePrimaryWorktreeRoot(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync(\n 'git rev-parse --path-format=absolute --git-common-dir',\n { cwd, timeout: 5000 },\n );\n const commonDir = stdout.trim();\n if (commonDir.endsWith(`${sep}.git`)) return dirname(commonDir);\n return cwd;\n } catch {\n return cwd;\n }\n}\n","import { getMergeBackendStatus, type MergeBackendStatus } from '../../lib/github-app.js';\n\nexport function formatMergeBackendStatus(status: MergeBackendStatus): string {\n if (status.available) {\n return `Merge backend: ${status.mode} (${status.detail})`;\n }\n return `Merge backend: UNAVAILABLE - ${status.detail}`;\n}\n\nexport async function loadMergeBackendStatusForCli(): Promise<MergeBackendStatus> {\n try {\n return await getMergeBackendStatus();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n available: false,\n mode: 'none',\n detail: `Unable to determine merge backend: ${message}`,\n };\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { exec, execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport type { Command } from 'commander';\nimport { parseSequenceMd } from '../../lib/backlog/sequence-io.js';\nimport { buildClassifyLookups } from '../../lib/backlog/lookups.js';\nimport {\n classifyIssue,\n computeWaves,\n computeCohort,\n computeStats,\n selectNeedsPlanning,\n} from '../../lib/backlog/pickup.js';\nimport { isFlywheelAutoPickupBacklog } from '../../lib/overdeck/control-settings.js';\nimport { getMergeBlockersPayload } from '../../lib/cloister/merge-blockers.js';\nimport { isGitHubAppConfigured, listOpenIssuesWithLabels } from '../../lib/github-app.js';\n\n/**\n * Sandbox-safe Flywheel data surfaces. These read state DIRECTLY (sequence.md + SQLite) and\n * emit no HTTP, so they work inside a network-isolated harness sandbox (e.g. codex's bwrap)\n * where the orchestrator cannot `curl http://127.0.0.1:3011/api/...`. They share the pickup\n * model (PAN-2006) and the merge-blocker model (PAN-1620) with the dashboard routes, so the\n * CLI and the dashboard can never disagree.\n */\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\nfunction parseGitHubRemoteUrl(remoteUrl: string): { owner: string; repo: string } | null {\n const trimmed = remoteUrl.trim();\n const match = trimmed.match(/github\\.com[:/]([^/]+)\\/([^/\\s]+?)(?:\\.git)?$/i);\n if (!match) return null;\n return { owner: match[1]!, repo: match[2]! };\n}\n\nasync function resolveGitHubOwnerRepo(): Promise<{ owner: string; repo: string } | null> {\n try {\n const { stdout } = await execAsync('git remote get-url origin', { encoding: 'utf8' });\n return parseGitHubRemoteUrl(stdout);\n } catch {\n return null;\n }\n}\n\n/** Labels for all open issues, keyed by bare number — fetched via REST so it works inside a\n * sandboxed harness without burning the gh GraphQL budget. */\nexport async function fetchOpenIssueLabels(): Promise<Map<string, string[]>> {\n const byNumber = new Map<string, string[]>();\n try {\n const repo = await resolveGitHubOwnerRepo();\n if (!repo) return byNumber;\n\n if (isGitHubAppConfigured()) {\n const issues = await Effect.runPromise(listOpenIssuesWithLabels(repo.owner, repo.repo));\n for (const issue of issues) {\n byNumber.set(String(issue.number), issue.labels);\n }\n return byNumber;\n }\n\n const { stdout } = await execFileAsync(\n 'gh',\n ['api', '--paginate', '--slurp', `repos/${repo.owner}/${repo.repo}/issues?state=open&per_page=100`],\n { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 },\n );\n const pages = JSON.parse(stdout || '[]') as Array<Array<{\n number: number;\n pull_request?: unknown;\n labels?: Array<{ name?: string | null } | string>;\n }>>;\n for (const issue of pages.flat()) {\n if (issue.pull_request) continue;\n byNumber.set(String(issue.number), (issue.labels ?? [])\n .map((label) => typeof label === 'string' ? label : label.name)\n .filter((name): name is string => typeof name === 'string' && name.length > 0));\n }\n } catch { /* GitHub unavailable — labels stay empty (degraded, never throws) */ }\n return byNumber;\n}\n\n/** `pan backlog forecast` — the pickup forecast (pickable waves, needs-planning, cohort, stats). */\nexport async function backlogForecastCommand(opts: { n?: string } = {}): Promise<void> {\n const projectRoot = process.cwd();\n const n = Math.max(1, Math.min(20, Number.parseInt(opts.n ?? '5', 10) || 5));\n const seqPath = join(projectRoot, '.pan', 'backlog', 'sequence.md');\n if (!existsSync(seqPath)) {\n console.log(JSON.stringify({ n, stats: null, inFlight: [], needsPlanning: [], waves: [], cohort: [] }, null, 2));\n return;\n }\n const parsed = parseSequenceMd(readFileSync(seqPath, 'utf-8'));\n if (!parsed.ok) {\n console.error(`parse error: ${parsed.error}`);\n process.exitCode = 1;\n return;\n }\n const nodes = parsed.doc.nodes;\n const labelsByNumber = await fetchOpenIssueLabels();\n const lk = buildClassifyLookups(projectRoot, { labels: (id) => labelsByNumber.get(id.replace(/^[A-Za-z]+-/, '')) ?? [] });\n const autoPickupBacklog = isFlywheelAutoPickupBacklog();\n const inFlight = nodes\n .map((x) => ({ issue: x.issue, rank: x.rank, state: classifyIssue(x, lk) }))\n .filter((x) => x.state.inPipeline)\n .sort((a, b) => a.rank - b.rank)\n .map((x) => x.issue);\n const needsPlanning = selectNeedsPlanning(nodes, lk, { cap: n * 2 }).map((x) => x.issue);\n const waves = computeWaves(nodes, lk, n, autoPickupBacklog).map((w) => w.map((x) => x.issue));\n const cohort = computeCohort(nodes, lk, n, autoPickupBacklog);\n const stats = computeStats(nodes, lk, autoPickupBacklog);\n console.log(JSON.stringify({ n, autoPickupBacklog, stats, inFlight, needsPlanning, waves, cohort }, null, 2));\n}\n\n/** `pan flywheel merge-blockers` — PRs that passed review but cannot merge (GitHub-native reasons). */\nexport function flywheelMergeBlockersCommand(opts: { json?: boolean } = {}): void {\n const blockers = getMergeBlockersPayload();\n if (opts.json) {\n console.log(JSON.stringify(blockers, null, 2));\n return;\n }\n if (blockers.length === 0) {\n console.log('No merge-blocked PRs.');\n return;\n }\n for (const b of blockers) {\n const reasonTypes = b.reasons.map((r) => r.type).join(', ');\n console.log(`${b.issueId} [${reasonTypes}]${b.prUrl ? ` ${b.prUrl}` : ''}`);\n for (const r of b.reasons) console.log(` - ${r.type}: ${r.summary}`);\n }\n}\n\n/**\n * Attaches the sandbox-safe surface subcommands onto the already-registered `flywheel` and\n * `backlog` commands. Kept here (not in flywheel.ts) so flywheel.ts stays under the file-size\n * ceiling. Call AFTER registerFlywheelCommands(program) and the backlog command are set up.\n */\nexport function registerFlywheelSurfaceCommands(program: Command): void {\n const flywheel = program.commands.find((c) => c.name() === 'flywheel');\n flywheel\n ?.command('merge-blockers')\n .description('PRs that passed review but cannot merge (GitHub-native reasons) — reads SQLite directly, no HTTP (sandbox-safe)')\n .option('--json', 'Output JSON')\n .action((o: { json?: boolean }) => flywheelMergeBlockersCommand(o));\n\n const backlog = program.commands.find((c) => c.name() === 'backlog');\n backlog\n ?.command('forecast')\n .description('Pickup forecast (waves / needs-planning / cohort / stats) from sequence.md + SQLite — no HTTP (sandbox-safe)')\n .option('-n, --n <n>', 'Wave size (default 5)')\n .action((o: { n?: string }) => backlogForecastCommand(o));\n}\n","import { exec, spawn } from 'node:child_process';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { freemem, totalmem } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { Effect, Schema } from 'effect';\nimport { layer as nodeServicesLayer } from '@effect/platform-node/NodeServices';\nimport { Command } from 'commander';\nimport {\n FlywheelStats,\n FlywheelStatus,\n type FlywheelStats as FlywheelStatsPayload,\n type FlywheelStatsCriteria,\n type FlywheelStatsCriterion,\n type FlywheelStatsCriterionStatus,\n type FlywheelStatsTrend,\n} from '@overdeck/contracts';\nimport { abortFlywheelRun, clearFlywheelGate, getFlywheelRunDetail, getFlywheelRunDir, listFlywheelRuns, nextFlywheelRunId, readFlywheelLaunchMetadata, resolveLiveFlywheelRunId, writeFlywheelLaunchMetadata, writeLatestFlywheelStatus } from '../../dashboard/server/services/flywheel-run-state.js';\nimport { loadConfigSync, resolveModel, type FlywheelScope, type RoleEffort } from '../../lib/config-yaml.js';\nimport { FLYWHEEL_ORCHESTRATOR_AGENT_ID, pauseFlywheel, resumeFlywheel, spawnFlywheel } from '../../lib/cloister/flywheel.js';\nimport { stopAgent } from '../../lib/agents.js';\nimport type { RuntimeName } from '../../lib/runtimes/types.js';\nimport { resolveHarness } from '../../lib/harness-resolve.js';\nimport {\n FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY,\n FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY,\n getFlywheelActiveRunId,\n isFlywheelAutoPickupBacklog,\n isFlywheelGloballyPaused,\n isFlywheelRequireUatBeforeMerge,\n setFlywheelAutoPickupBacklog,\n setFlywheelRequireUatBeforeMerge,\n} from '../../lib/overdeck/control-settings.js';\nimport { sessionExists } from '../../lib/tmux.js';\nimport { ensureInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../../lib/internal-token.js';\nimport { computeMergeQueue, type MergeQueueItem } from '../../lib/flywheel-merge-order.js';\nimport { DEFAULT_BRIEF_PATH, requireFlywheelBrief, resolvePrimaryWorktreeRoot } from '../../lib/flywheel-start.js';\nimport { formatMergeBackendStatus, loadMergeBackendStatusForCli } from './flywheel-merge-backend.js';\nimport { registerFlywheelSurfaceCommands } from './flywheel-surfaces.js';\n\ntype InputStream = AsyncIterable<string | Buffer | Uint8Array>;\n\ninterface EmitStatusOptions {\n file: string;\n}\n\ninterface StatusOptions {\n json?: boolean;\n}\n\ninterface StatsOptions {\n window?: string;\n json?: boolean;\n}\n\ninterface FormatStatsOptions {\n color?: boolean;\n}\n\ninterface ConfigOptions {\n get?: true | string;\n set?: string;\n}\n\ninterface StartOptions {\n brief?: string;\n cwd?: string;\n}\n\ninterface StartFlywheelRunResult {\n runId: string;\n briefDisplayPath: string;\n agentModel?: string;\n}\n\ninterface ReportOptions {\n cwd?: string;\n force?: boolean;\n}\n\ninterface ReportOpenOptions {\n runId?: string;\n opener?: (path: string) => void | Promise<void>;\n}\n\ninterface FlywheelGateSnapshot {\n paused: boolean;\n activeRunId: string | null;\n}\n\ninterface ResolvedFlywheelRoleConfig {\n harness: 'claude-code' | 'ohmypi' | 'codex';\n model: string;\n effort: RoleEffort;\n minAgents: number;\n maxAgents: number;\n scope: FlywheelScope;\n autoPickupBacklog: boolean;\n requireUatBeforeMerge: boolean;\n}\n\nconst decodeFlywheelStatus = Schema.decodeUnknownSync(FlywheelStatus);\nconst decodeFlywheelStats = Schema.decodeUnknownSync(FlywheelStats);\nconst execAsync = promisify(exec);\nconst DEFAULT_STATS_WINDOW = '30d';\n\nfunction dashboardBaseUrl(): string {\n return (process.env.OVERDECK_DASHBOARD_URL || process.env.DASHBOARD_URL || 'http://localhost:3011').replace(/\\/$/, '');\n}\n\nexport async function readFlywheelStatusJson(file: string, input: InputStream = process.stdin): Promise<string> {\n if (file !== '-') return readFile(file, 'utf8');\n\n const chunks: string[] = [];\n for await (const chunk of input) {\n chunks.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8'));\n }\n return chunks.join('');\n}\n\nexport function parseFlywheelStatusJson(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid JSON: ${message}`);\n }\n}\n\nexport function validateFlywheelStatusPayload(payload: unknown): FlywheelStatus {\n try {\n return decodeFlywheelStatus(payload);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid FlywheelStatus: ${message}`);\n }\n}\n\nfunction isFlywheelConfigKey(key: string): key is FlywheelConfigKey {\n return FLYWHEEL_CONFIG_KEYS.includes(key as FlywheelConfigKey);\n}\n\nfunction parseFlywheelConfigKey(key: string): FlywheelConfigKey {\n if (!isFlywheelConfigKey(key)) throw new Error(`Unknown flywheel config key: ${key}`);\n return key;\n}\n\nfunction readFlywheelConfigValue(key: FlywheelConfigKey): boolean {\n switch (key) {\n case FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY:\n return isFlywheelAutoPickupBacklog();\n case FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY:\n return isFlywheelRequireUatBeforeMerge();\n }\n}\n\nfunction writeFlywheelConfigValue(key: FlywheelConfigKey, value: boolean): void {\n switch (key) {\n case FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY:\n setFlywheelAutoPickupBacklog(value);\n return;\n case FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY:\n setFlywheelRequireUatBeforeMerge(value);\n return;\n }\n}\n\nfunction formatFlywheelConfigValue(key: FlywheelConfigKey): string {\n return `${key}=${readFlywheelConfigValue(key)}`;\n}\n\nfunction parseConfigBoolean(key: string, rawValue: string): boolean {\n if (rawValue === 'true') return true;\n if (rawValue === 'false') return false;\n throw new Error(`Boolean value required for ${key}: ${rawValue}`);\n}\n\nfunction parseFlywheelConfigAssignment(assignment: string): { key: FlywheelConfigKey; value: boolean } {\n const separator = assignment.indexOf('=');\n if (separator === -1) throw new Error('Flywheel config assignment must use <key>=<bool>');\n const key = parseFlywheelConfigKey(assignment.slice(0, separator));\n const value = parseConfigBoolean(key, assignment.slice(separator + 1));\n return { key, value };\n}\n\nexport async function flywheelConfigCommand(options: ConfigOptions = {}): Promise<void> {\n try {\n if (options.get !== undefined && options.set !== undefined) {\n throw new Error('Use either --get or --set, not both');\n }\n\n if (options.set !== undefined) {\n const { key, value } = parseFlywheelConfigAssignment(options.set);\n writeFlywheelConfigValue(key, value);\n console.log(`${key}=${value}`);\n return;\n }\n\n if (typeof options.get === 'string') {\n console.log(formatFlywheelConfigValue(parseFlywheelConfigKey(options.get)));\n return;\n }\n\n console.log(FLYWHEEL_CONFIG_KEYS.map(formatFlywheelConfigValue).join('\\n'));\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nconst FLYWHEEL_CONFIG_KEYS = [\n FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY,\n FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY,\n] as const;\n\ntype FlywheelConfigKey = typeof FLYWHEEL_CONFIG_KEYS[number];\n\nfunction mb(bytes: number): number {\n return Math.round(bytes / 1024 / 1024);\n}\n\nasync function resolveFlywheelRoleConfig(): Promise<ResolvedFlywheelRoleConfig> {\n const { config } = loadConfigSync();\n const flywheel = config.roles?.flywheel;\n const model = resolveModel('flywheel', undefined, config);\n // PAN-1984/PAN-1865: derive the harness from the model provider; never pin\n // flywheel to claude-code or a hardcoded fallback.\n const harness = await resolveHarness({ model });\n return {\n harness,\n model,\n effort: flywheel?.effort ?? 'high',\n minAgents: flywheel?.minAgents ?? 20,\n maxAgents: flywheel?.maxAgents ?? 30,\n scope: flywheel?.scope ?? 'pan-only',\n autoPickupBacklog: isFlywheelAutoPickupBacklog(),\n requireUatBeforeMerge: isFlywheelRequireUatBeforeMerge(),\n };\n}\n\nasync function createInitialFlywheelStatus(\n runId: string,\n startedAt: string,\n cwd: string,\n agentModel: string | undefined,\n agentHarness: RuntimeName | undefined,\n roleConfig: ResolvedFlywheelRoleConfig,\n): Promise<FlywheelStatus> {\n const ramTotalMb = mb(totalmem());\n return {\n runId,\n startedAt,\n elapsedMs: 0,\n orchestrator: {\n harness: agentHarness ?? roleConfig.harness,\n model: agentModel ?? roleConfig.model,\n effort: roleConfig.effort,\n ctxPercent: 0,\n },\n headline: {\n bugsFixed: 0,\n swarmItemsMerged: 0,\n swarmItemsTotal: 0,\n prsMerged: 0,\n awaitingUat: 0,\n },\n activePipeline: [],\n substrateBugs: [],\n agents: [{\n id: FLYWHEEL_ORCHESTRATOR_AGENT_ID,\n label: 'flywheel-orchestrator',\n status: 'running',\n role: 'flywheel',\n model: agentModel,\n }],\n parked: [],\n suggestions: [],\n system: {\n mainHead: await gitOutput('git rev-parse --short HEAD', cwd).catch(() => 'unknown'),\n ramUsedMb: Math.max(0, ramTotalMb - mb(freemem())),\n ramTotalMb,\n swapUsedMb: 0,\n swapTotalMb: 0,\n agentsActive: 1,\n agentsCap: roleConfig.maxAgents,\n },\n openQuestions: [],\n ticks: 0,\n lastTickAt: startedAt,\n };\n}\n\nexport async function postFlywheelStatus(status: FlywheelStatus, fetchImpl: typeof fetch = fetch): Promise<void> {\n const internalToken = ensureInternalTokenSync();\n const res = await fetchImpl(`${dashboardBaseUrl()}/api/flywheel/status`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [INTERNAL_TOKEN_HEADER]: internalToken,\n },\n body: JSON.stringify(status),\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`Dashboard rejected FlywheelStatus (${res.status})${body ? `: ${body}` : ''}`);\n }\n}\n\nexport async function emitStatusCommand(options: EmitStatusOptions): Promise<void> {\n try {\n const raw = await readFlywheelStatusJson(options.file);\n const payload = parseFlywheelStatusJson(raw);\n const status = validateFlywheelStatusPayload(payload);\n await postFlywheelStatus(status);\n console.log(`Flywheel status emitted for ${status.runId}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport async function startFlywheelRun(options: StartOptions = {}): Promise<StartFlywheelRunResult> {\n const cwd = options.cwd ?? await resolvePrimaryWorktreeRoot(process.cwd());\n const brief = await requireFlywheelBrief(cwd, options.brief);\n const runId = await nextFlywheelRunId();\n const startedAt = new Date().toISOString();\n await writeFlywheelLaunchMetadata({\n version: 1,\n runId,\n workspace: cwd,\n briefPath: brief.absolutePath,\n briefDisplayPath: brief.displayPath,\n });\n const roleConfig = await resolveFlywheelRoleConfig();\n const agent = await spawnFlywheel({\n runId,\n briefPath: brief.absolutePath,\n workspace: cwd,\n model: roleConfig.model,\n harness: roleConfig.harness,\n effort: roleConfig.effort,\n minAgents: roleConfig.minAgents,\n maxAgents: roleConfig.maxAgents,\n scope: roleConfig.scope,\n autoPickupBacklog: roleConfig.autoPickupBacklog,\n requireUatBeforeMerge: roleConfig.requireUatBeforeMerge,\n });\n await writeLatestFlywheelStatus(await createInitialFlywheelStatus(\n runId,\n startedAt,\n cwd,\n agent.model,\n agent.harness,\n roleConfig,\n ));\n return { runId, briefDisplayPath: brief.displayPath, agentModel: agent.model };\n}\n\nexport async function flywheelStartCommand(options: StartOptions = {}): Promise<void> {\n try {\n const result = await startFlywheelRun(options);\n\n console.log(`Flywheel started: ${result.runId}`);\n console.log(`Brief: ${result.briefDisplayPath}`);\n console.log(`Run URL: ${dashboardBaseUrl()}/flywheel`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction formatElapsed(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) return `${hours}h ${minutes}m`;\n if (minutes > 0) return `${minutes}m ${seconds}s`;\n return `${seconds}s`;\n}\n\nasync function loadActiveFlywheelStatus(): Promise<FlywheelStatus | null> {\n // resolveLiveFlywheelRunId self-heals the SQLite gate when the prior run\n // has ended or its on-disk state is gone (PAN-1245). Status and start now\n // consult the same source of truth.\n const activeRunId = await resolveLiveFlywheelRunId();\n if (!activeRunId) return null;\n const detail = await getFlywheelRunDetail(activeRunId);\n return detail?.status === 'running' ? detail.latest : null;\n}\n\nexport function formatFlywheelStatus(status: FlywheelStatus): string {\n return [\n `Run: ${status.runId}`,\n `Elapsed: ${formatElapsed(status.elapsedMs)}`,\n `Bugs fixed: ${status.headline.bugsFixed}`,\n `SWARM items: ${status.headline.swarmItemsMerged}/${status.headline.swarmItemsTotal}`,\n `PRs merged: ${status.headline.prsMerged}`,\n `Awaiting UAT: ${status.headline.awaitingUat}`,\n `Active agents: ${status.system.agentsActive}/${status.system.agentsCap}`,\n `RAM: ${status.system.ramUsedMb} MiB used / ${status.system.ramTotalMb} MiB total`,\n `Main HEAD: ${status.system.mainHead.slice(0, 7)}`,\n `Last tick: ${status.lastTickAt}`,\n ].join('\\n');\n}\n\nexport async function flywheelStatusCommand(options: StatusOptions): Promise<void> {\n try {\n const status = await loadActiveFlywheelStatus();\n if (!status) {\n console.error('no active flywheel run');\n process.exitCode = 1;\n return;\n }\n\n const mergeBackend = await loadMergeBackendStatusForCli();\n console.log(options.json\n ? JSON.stringify({ ...status, mergeBackend }, null, 2)\n : `${formatFlywheelStatus(status)}\\n${formatMergeBackendStatus(mergeBackend)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nconst STATS_CRITERION_KEYS = [\n 'c1_bugRate',\n 'c2_p0Bugs',\n 'c3_passRate',\n 'c4_mttr',\n 'c5_intervention',\n 'c6_timeConsistency',\n 'c7_flake',\n] as const satisfies readonly (keyof FlywheelStatsCriteria)[];\n\nconst STATUS_GLYPH: Record<FlywheelStatsCriterionStatus, string> = {\n green: '● green',\n yellow: '● yellow',\n red: '● red',\n insufficient_data: '○ insufficient_data',\n};\n\nconst STATUS_COLOR: Record<FlywheelStatsCriterionStatus, string> = {\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n insufficient_data: '\\x1b[90m',\n};\n\nconst TREND_LABEL: Record<FlywheelStatsTrend, string> = {\n up: '↗ up',\n down: '↘ down',\n flat: '→ flat',\n};\n\nexport async function fetchFlywheelStats(window: string, fetchImpl: typeof fetch = fetch): Promise<FlywheelStatsPayload> {\n const internalToken = ensureInternalTokenSync();\n const url = new URL(`${dashboardBaseUrl()}/api/flywheel/stats`);\n url.searchParams.set('window', window);\n const res = await fetchImpl(url.toString(), {\n headers: {\n [INTERNAL_TOKEN_HEADER]: internalToken,\n },\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`Dashboard rejected Flywheel stats request (${res.status})${body ? `: ${body}` : ''}`);\n }\n\n return decodeFlywheelStats(await res.json());\n}\n\nfunction formatScalar(value: number): string {\n if (!Number.isFinite(value)) return '—';\n if (value > 0 && Math.abs(value) < 1) return `${(value * 100).toFixed(1)}%`;\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n}\n\nfunction formatDuration(ms: number): string {\n const abs = Math.abs(ms);\n if (abs >= 24 * 60 * 60 * 1000) return `${(ms / (24 * 60 * 60 * 1000)).toFixed(1)}d`;\n if (abs >= 60 * 60 * 1000) return `${(ms / (60 * 60 * 1000)).toFixed(1)}h`;\n if (abs >= 60 * 1000) return `${(ms / (60 * 1000)).toFixed(1)}m`;\n return `${Math.round(ms)}ms`;\n}\n\nfunction formatObjectValue(value: Record<string, unknown>): string {\n return Object.entries(value)\n .map(([key, entry]) => {\n if (typeof entry === 'number' && key.toLowerCase().endsWith('ms')) return `${key}: ${formatDuration(entry)}`;\n if (typeof entry === 'number') return `${key}: ${formatScalar(entry)}`;\n if (typeof entry === 'string' || typeof entry === 'boolean' || entry === null) return `${key}: ${String(entry)}`;\n return `${key}: ${JSON.stringify(entry)}`;\n })\n .join(', ');\n}\n\nfunction formatCriterionValue(value: FlywheelStatsCriterion['value']): string {\n return typeof value === 'number' ? formatScalar(value) : formatObjectValue(value as Record<string, unknown>);\n}\n\nfunction colorStatus(status: FlywheelStatsCriterionStatus, color: boolean): string {\n if (!color) return STATUS_GLYPH[status];\n const [glyph, label] = STATUS_GLYPH[status].split(' ');\n return `${STATUS_COLOR[status]}${glyph}\\x1b[0m ${label}`;\n}\n\nfunction tableStatsCell(value: string | number | undefined): string {\n return String(value ?? '—').replace(/\\|/g, '\\\\|').replace(/\\n/g, ' ');\n}\n\nexport function formatFlywheelStats(stats: FlywheelStatsPayload, options: FormatStatsOptions = {}): string {\n const color = options.color ?? process.stdout.isTTY === true;\n const rows = STATS_CRITERION_KEYS.map((key) => {\n const criterion = stats.criteria[key];\n return `| ${tableStatsCell(criterion.label)} | ${tableStatsCell(formatCriterionValue(criterion.value))} | ${tableStatsCell(formatCriterionValue(criterion.target))} | ${tableStatsCell(colorStatus(criterion.status, color))} | ${tableStatsCell(criterion.trend ? TREND_LABEL[criterion.trend] : '—')} | ${criterion.sampleSize} |`;\n });\n return [\n `Flywheel stats (${stats.window})`,\n `Generated: ${stats.generatedAt}`,\n '',\n '| Criterion | Value | Target | Status | Trend | Sample |',\n '|---|---:|---:|---|---|---:|',\n ...rows,\n ].join('\\n');\n}\n\nexport async function flywheelStatsCommand(options: StatsOptions = {}): Promise<void> {\n try {\n const stats = await fetchFlywheelStats(options.window ?? DEFAULT_STATS_WINDOW);\n console.log(options.json ? JSON.stringify(stats, null, 2) : formatFlywheelStats(stats));\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction runNumberFromRunId(runId: string): number {\n const match = /^RUN-(\\d+)$/.exec(runId);\n if (!match) throw new Error(`Invalid Flywheel run id for report: ${runId}`);\n return Number(match[1]);\n}\n\nfunction reportDate(status: FlywheelStatus): string {\n return status.lastTickAt.slice(0, 10);\n}\n\nfunction tableCell(value: string | number | undefined): string {\n return String(value ?? '').replace(/\\|/g, '\\\\|').replace(/\\n/g, ' ');\n}\n\nexport function formatFlywheelStateReport(status: FlywheelStatus, mergeQueue: MergeQueueItem[] = []): string {\n const runNumber = runNumberFromRunId(status.runId);\n const suggestionRows = status.suggestions.length > 0\n ? status.suggestions.map((suggestion) => `| ${tableCell(suggestion.priority)} | ${tableCell(suggestion.action)} | ${tableCell(suggestion.issueId)} | ${tableCell(suggestion.rationale)} |`).join('\\n')\n : 'No suggestions emitted this run.';\n const activePipelineRows = status.activePipeline.length > 0\n ? status.activePipeline.map((item) => `| ${tableCell(item.issueId)} | ${tableCell(item.verb)} | ${tableCell(item.status)} | ${tableCell(item.title)} | ${tableCell(item.progressPercent)} | ${tableCell(item.pr)} |`).join('\\n')\n : '| _None_ | | | | | |';\n const substrateRows = status.substrateBugs.length > 0\n ? status.substrateBugs.map((bug) => `| ${tableCell(bug.issueId)} | ${tableCell(bug.status)} | ${tableCell(bug.title)} | ${tableCell(bug.commitSha?.slice(0, 10))} |`).join('\\n')\n : '| _None_ | | | |';\n const patternLines = status.parked.length > 0\n ? status.parked.map((item) => `- **${item.issueId}** (${item.reason}) — ${item.title}`).join('\\n')\n : '- None recorded this run.';\n const questionLines = status.openQuestions.length > 0\n ? status.openQuestions.map((question) => `- ${question}`).join('\\n')\n : '- None.';\n const mergeQueueSection = mergeQueue.length > 0\n ? `## Merge Queue\n\nMerge ship-ready PRs in this order to avoid rebase conflicts. Branches with overlapping file changes must be merged in sequence; non-overlapping branches can be merged in any order.\n\n| # | Issue | PR | Conflicts With |\n|---|---|---|---|\n${mergeQueue.map((item) => {\n const prCell = item.pr != null ? `#${item.pr}` : '—';\n const conflictsCell = item.conflictsWith.length > 0 ? item.conflictsWith.join(', ') : '—';\n return `| ${item.mergeOrder} | ${item.issueId} | ${prCell} | ${conflictsCell} |`;\n}).join('\\n')}\n\n---\n\n`\n : '';\n\n return `# Flywheel Run ${runNumber} Report — ${reportDate(status)}\n\nPer-run report derived from the last \\`FlywheelStatus\\` snapshot. Durable cumulative memory across runs lives in \\`docs/FLYWHEEL-STATE.md\\`.\n\n**Run window:** ${status.startedAt} → ${status.lastTickAt} (${status.orchestrator.harness}, ${status.orchestrator.model}, ${status.orchestrator.effort})\n\n**Headline result:** ${status.headline.bugsFixed} substrate bugs fixed; ${status.headline.swarmItemsMerged}/${status.headline.swarmItemsTotal} SWARM items merged; ${status.headline.prsMerged} PRs merged; ${status.headline.awaitingUat} awaiting UAT.\n\n**Run counters:** ticks ${status.ticks}; last tick ${status.lastTickAt}; orchestrator context ${status.orchestrator.ctxPercent}%.\n\n---\n\n${mergeQueueSection}## Suggestions\n\n${status.suggestions.length > 0 ? `| Priority | Action | Issue | Rationale |\n|---|---|---|---|\n${suggestionRows}` : suggestionRows}\n\n---\n\n## Active Pipeline\n\n| Issue | Verb | Status | Title | Progress | PR |\n|---|---|---|---|---:|---:|\n${activePipelineRows}\n\n---\n\n## Cycling Alerts\n\n${questionLines}\n\n---\n\n## Infrastructure Gaps\n\n| Issue | Status | Notes | Commit |\n|---|---|---|---|\n${substrateRows}\n\n---\n\n## Pattern Ledger\n\n${patternLines}\n\n---\n\n## Skill Gaps\n\n- Keep \\`pan flywheel report\\` in the closeout path so run summaries stay archived under the run directory.\n`;\n}\n\nasync function isFlywheelStateDirty(cwd: string): Promise<boolean> {\n // Tolerate non-git cwd / missing file: a non-zero git exit means there are\n // no orchestrator-authored changes worth committing here (PAN-1245). The\n // gate clear in flywheelReportCommand must not be blocked by this check.\n try {\n const { stdout } = await execAsync(\n 'git status --porcelain docs/FLYWHEEL-STATE.md',\n { cwd, encoding: 'utf8' },\n );\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\nasync function loadReportFlywheelStatus(): Promise<FlywheelStatus | null> {\n const activeRunId = getFlywheelActiveRunId();\n if (activeRunId) {\n const activeDetail = await getFlywheelRunDetail(activeRunId);\n if (activeDetail?.latest) return activeDetail.latest;\n }\n const runs = await listFlywheelRuns();\n const run = runs.find((candidate) => candidate.status === 'running') ?? runs[0];\n if (!run) return null;\n const detail = await getFlywheelRunDetail(run.id);\n return detail?.latest ?? null;\n}\n\nasync function gitOutput(command: string, cwd: string): Promise<string> {\n const { stdout } = await execAsync(command, { cwd, encoding: 'utf8' });\n return stdout.trim();\n}\n\nasync function commitFlywheelStateChanges(cwd: string, runNumber: number): Promise<void> {\n const subject = `docs(flywheel): run ${runNumber}`;\n await execAsync('git add docs/FLYWHEEL-STATE.md', { cwd });\n const headSubject = await gitOutput('git log -1 --format=%s', cwd).catch(() => '');\n const command = headSubject === subject\n ? `git commit --amend -m ${JSON.stringify(subject)}`\n : `git commit -m ${JSON.stringify(subject)}`;\n await execAsync(command, { cwd, encoding: 'utf8' });\n}\n\nfunction readFlywheelGateSnapshot(): FlywheelGateSnapshot {\n return {\n paused: isFlywheelGloballyPaused(),\n activeRunId: getFlywheelActiveRunId(),\n };\n}\n\nfunction formatGateSnapshot(snapshot: FlywheelGateSnapshot): string {\n return `paused=${snapshot.paused ? 'true' : 'false'} active_run_id=${snapshot.activeRunId ?? 'none'}`;\n}\n\nexport async function pauseFlywheelRun(): Promise<{ before: FlywheelGateSnapshot; after: FlywheelGateSnapshot; changed: boolean }> {\n const before = readFlywheelGateSnapshot();\n if (before.paused) return { before, after: before, changed: false };\n await pauseFlywheel();\n return { before, after: readFlywheelGateSnapshot(), changed: true };\n}\n\nexport async function flywheelPauseCommand(): Promise<void> {\n try {\n const result = await pauseFlywheelRun();\n if (!result.changed) {\n console.log(`Flywheel already paused (${formatGateSnapshot(result.before)})`);\n return;\n }\n\n console.log(`Flywheel paused: before ${formatGateSnapshot(result.before)}; after ${formatGateSnapshot(result.after)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport async function resumeFlywheelRun(): Promise<{ before: FlywheelGateSnapshot; after: FlywheelGateSnapshot; changed: boolean }> {\n const before = readFlywheelGateSnapshot();\n if (!before.paused && await Effect.runPromise(sessionExists(FLYWHEEL_ORCHESTRATOR_AGENT_ID))) {\n return { before, after: before, changed: false };\n }\n if (!before.activeRunId) throw new Error('No active flywheel run to resume');\n const launch = await readFlywheelLaunchMetadata(before.activeRunId);\n if (!launch) {\n throw new Error(`Flywheel run ${before.activeRunId} is missing launch metadata; cannot resume safely`);\n }\n const brief = await requireFlywheelBrief(launch.workspace, launch.briefPath);\n const roleConfig = await resolveFlywheelRoleConfig();\n await resumeFlywheel({\n workspace: launch.workspace,\n briefPath: brief.absolutePath,\n model: roleConfig.model,\n harness: roleConfig.harness,\n effort: roleConfig.effort,\n minAgents: roleConfig.minAgents,\n maxAgents: roleConfig.maxAgents,\n scope: roleConfig.scope,\n autoPickupBacklog: roleConfig.autoPickupBacklog,\n requireUatBeforeMerge: roleConfig.requireUatBeforeMerge,\n });\n return { before, after: readFlywheelGateSnapshot(), changed: true };\n}\n\nexport async function flywheelResumeCommand(): Promise<void> {\n try {\n const result = await resumeFlywheelRun();\n if (!result.changed) {\n console.log(`Flywheel already running (${formatGateSnapshot(result.before)})`);\n return;\n }\n\n console.log(`Flywheel resumed: before ${formatGateSnapshot(result.before)}; after ${formatGateSnapshot(result.after)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction clearFlywheelRunGate(runId: string): void {\n if (getFlywheelActiveRunId() === runId) {\n clearFlywheelGate();\n }\n}\n\n// Stop the orchestrator, write the report, commit state changes, and clear the gate.\nexport async function flywheelStopCommand(): Promise<void> {\n try {\n const sessionAlive = await Effect.runPromise(sessionExists(FLYWHEEL_ORCHESTRATOR_AGENT_ID));\n if (sessionAlive) {\n await Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID));\n }\n\n const status = await loadReportFlywheelStatus();\n if (!status) {\n console.log('No flywheel run is active and nothing is left to report.');\n return;\n }\n\n // The orchestrator has already been stopped, so force the report path to\n // bypass its alive-session guard and finalize the run.\n await flywheelReportCommand({ force: true });\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport async function flywheelReportCommand(options: ReportOptions = {}): Promise<void> {\n try {\n const cwd = options.cwd ?? process.cwd();\n\n // Writing report.md finalizes the run. Refuse while the orchestrator is alive;\n // its own end-of-run call passes --force to bypass this guard.\n if (!options.force && await Effect.runPromise(sessionExists(FLYWHEEL_ORCHESTRATOR_AGENT_ID))) {\n console.error('Refusing to write report — flywheel orchestrator session is still alive.');\n console.error('This command finalizes the run (writes report.md and clears the active-run gate).');\n console.error('Run `pan flywheel pause` (or `pan flywheel abort`) first, or pass --force to override.');\n process.exitCode = 1;\n return;\n }\n\n const status = await loadReportFlywheelStatus();\n if (!status) {\n console.error('no flywheel run to report');\n process.exitCode = 1;\n return;\n }\n\n const runNumber = runNumberFromRunId(status.runId);\n const mergeQueue = await Effect.runPromise(\n computeMergeQueue(status.activePipeline, cwd).pipe(Effect.provide(nodeServicesLayer)),\n );\n const runReport = formatFlywheelStateReport(status, mergeQueue);\n await writeFile(join(getFlywheelRunDir(status.runId), 'report.md'), runReport, 'utf8');\n\n // PAN-1245: clear the gate after writing report.md even if commit fails.\n try {\n const stateChanged = await isFlywheelStateDirty(cwd);\n if (stateChanged) {\n await commitFlywheelStateChanges(cwd, runNumber);\n console.log(`Wrote per-run report and committed FLYWHEEL-STATE.md changes for run ${runNumber}.`);\n } else {\n console.log(`Wrote per-run report for run ${runNumber}. No FLYWHEEL-STATE.md changes to commit.`);\n }\n } finally {\n clearFlywheelRunGate(status.runId);\n }\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction getPlatformOpenCommand(): string {\n switch (process.platform) {\n case 'linux': return 'xdg-open';\n case 'darwin': return 'open';\n case 'win32': return 'explorer';\n default: throw new Error(`Opening files is not supported on ${process.platform}`);\n }\n}\n\nfunction openPathDetached(path: string): void {\n const child = spawn(getPlatformOpenCommand(), [path], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n}\n\nasync function resolveReportOpenRunId(runId: string | undefined): Promise<string> {\n if (runId) return runId;\n const runs = await listFlywheelRuns();\n const run = runs.find((candidate) => candidate.status === 'complete') ?? runs[0];\n if (!run) throw new Error('no flywheel run report to open');\n return run.id;\n}\n\nexport async function openFlywheelRunReport(options: ReportOpenOptions = {}): Promise<{ runId: string; path: string }> {\n const runId = await resolveReportOpenRunId(options.runId);\n const detail = await getFlywheelRunDetail(runId);\n if (!detail) throw new Error(`Flywheel run not found: ${runId}`);\n if (!detail.paths.report) throw new Error(`No report exists for ${runId}`);\n await (options.opener ?? openPathDetached)(detail.paths.report);\n return { runId, path: detail.paths.report };\n}\n\nexport async function flywheelReportOpenCommand(options: ReportOpenOptions = {}): Promise<void> {\n try {\n const result = await openFlywheelRunReport(options);\n console.log(`Opened Flywheel report for ${result.runId}: ${result.path}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\n// Discard the current run without writing a report (PAN-1245).\nexport async function flywheelAbortCommand(): Promise<void> {\n try {\n const candidate = getFlywheelActiveRunId();\n if (!candidate) {\n console.log('No active flywheel run to abort.');\n return;\n }\n await Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID));\n await abortFlywheelRun(candidate);\n console.log(`Aborted flywheel run ${candidate}.`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport function registerFlywheelCommands(program: Command): void {\n const flywheel = program\n .command('flywheel')\n .description('Flywheel orchestrator lifecycle and status helpers');\n registerFlywheelSurfaceCommands(program);\n\n flywheel\n .command('start')\n .description('Start the Flywheel orchestrator')\n .option('--brief <path>', 'Path to the Flywheel brief', DEFAULT_BRIEF_PATH)\n .action(flywheelStartCommand);\n\n flywheel\n .command('emit-status')\n .description('Validate and publish a FlywheelStatus JSON snapshot to the local dashboard')\n .requiredOption('--file <path>', 'Path to FlywheelStatus JSON, or - to read from stdin')\n .action(emitStatusCommand);\n\n flywheel\n .command('config')\n .description('Get or set Flywheel autonomy configuration')\n .option('--get [key]', 'Print Flywheel config values')\n .option('--set <key=value>', 'Set a Flywheel config boolean')\n .action(flywheelConfigCommand);\n\n flywheel\n .command('status')\n .description('Show the active Flywheel run status')\n .option('--json', 'Emit the raw FlywheelStatus JSON')\n .action(flywheelStatusCommand);\n\n flywheel\n .command('stats')\n .description('Show Flywheel v1.0 readiness stats')\n .option('--window <duration>', 'Stats window duration', DEFAULT_STATS_WINDOW)\n .option('--json', 'Emit the raw FlywheelStats JSON')\n .action(flywheelStatsCommand);\n\n flywheel\n .command('pause')\n .description('Pause the active Flywheel orchestrator run')\n .action(flywheelPauseCommand);\n\n flywheel\n .command('resume')\n .description('Resume the paused Flywheel orchestrator run')\n .action(flywheelResumeCommand);\n\n flywheel\n .command('report')\n .description('Finalize the active Flywheel run: write report.md, commit FLYWHEEL-STATE.md changes, and clear the active-run gate. Refuses to run while the orchestrator session is alive (pause or abort first).')\n .option('--force', 'Bypass the orchestrator-alive guard. Intended for the orchestrator role\\'s own end-of-run call.')\n .action(flywheelReportCommand);\n\n flywheel\n .command('stop')\n .description('Stop the Flywheel orchestrator gracefully: kill any live session, write report.md, commit FLYWHEEL-STATE.md changes, and clear the active-run gate')\n .action(flywheelStopCommand);\n\n flywheel\n .command('abort')\n .description('Discard the active Flywheel run without writing a report')\n .action(flywheelAbortCommand);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flywheel-Cqf8vu7N.js","names":["execAsync","execAsync","nodeServicesLayer"],"sources":["../../src/lib/flywheel-start.ts","../../src/cli/commands/flywheel-merge-backend.ts","../../src/cli/commands/flywheel-surfaces.ts","../../src/cli/commands/flywheel.ts"],"sourcesContent":["import { exec } from 'node:child_process';\nimport { readFile, realpath } from 'node:fs/promises';\nimport { dirname, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\nexport const DEFAULT_BRIEF_PATH = 'docs/flywheel-brief.md';\n\nfunction isInsideRoot(projectRoot: string, candidate: string): boolean {\n const relativePath = relative(projectRoot, candidate);\n return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));\n}\n\nexport function resolveFlywheelStartBriefPath(cwd: string, requestedPath?: string): { absolutePath: string; displayPath: string } {\n const rawPath = requestedPath?.trim() || DEFAULT_BRIEF_PATH;\n if (rawPath.includes('\\0')) throw new Error('Brief path is invalid');\n\n const root = resolve(cwd);\n const absolutePath = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);\n if (!isInsideRoot(root, absolutePath)) throw new Error('Brief path must stay inside the project root');\n\n const normalizedRoot = root.endsWith(sep) ? root : `${root}${sep}`;\n const displayPath = absolutePath === root ? '.' : relative(root, absolutePath);\n return { absolutePath, displayPath: absolutePath.startsWith(normalizedRoot) ? displayPath : absolutePath };\n}\n\nasync function assertExistingPathInsideRoot(projectRoot: string, candidate: string): Promise<void> {\n const [realRoot, realCandidate] = await Promise.all([realpath(projectRoot), realpath(candidate)]);\n if (!isInsideRoot(realRoot, realCandidate)) throw new Error('Brief path must stay inside the project root');\n}\n\nexport async function requireFlywheelBrief(cwd: string, requestedPath?: string): Promise<{ absolutePath: string; displayPath: string }> {\n const resolved = resolveFlywheelStartBriefPath(cwd, requestedPath);\n try {\n await assertExistingPathInsideRoot(cwd, resolved.absolutePath);\n await readFile(resolved.absolutePath, 'utf8');\n return resolved;\n } catch (error) {\n const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;\n if (code === 'ENOENT') throw new Error(`Flywheel brief not found: ${resolved.displayPath}`);\n throw error;\n }\n}\n\n/**\n * The flywheel is a project-scoped singleton: it must run from the PRIMARY\n * worktree root, never a linked feature worktree. Every worktree contains\n * docs/flywheel-brief.md, so the brief check alone cannot catch a caller whose\n * shell happens to sit inside a workspaces/feature-* checkout, and an\n * orchestrator spawned there operates inside a live work agent's worktree.\n * A linked worktree is\n * detected by `git rev-parse --git-common-dir` resolving to the primary's\n * .git directory; outside a repo (or on failure) the cwd is kept and the\n * brief check reports its own error.\n */\n\nexport async function resolvePrimaryWorktreeRoot(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync(\n 'git rev-parse --path-format=absolute --git-common-dir',\n { cwd, timeout: 5000 },\n );\n const commonDir = stdout.trim();\n if (commonDir.endsWith(`${sep}.git`)) return dirname(commonDir);\n return cwd;\n } catch {\n return cwd;\n }\n}\n","import { getMergeBackendStatus, type MergeBackendStatus } from '../../lib/github-app.js';\n\nexport function formatMergeBackendStatus(status: MergeBackendStatus): string {\n if (status.available) {\n return `Merge backend: ${status.mode} (${status.detail})`;\n }\n return `Merge backend: UNAVAILABLE - ${status.detail}`;\n}\n\nexport async function loadMergeBackendStatusForCli(): Promise<MergeBackendStatus> {\n try {\n return await getMergeBackendStatus();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n available: false,\n mode: 'none',\n detail: `Unable to determine merge backend: ${message}`,\n };\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { exec, execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport type { Command } from 'commander';\nimport { parseSequenceMd } from '../../lib/backlog/sequence-io.js';\nimport { buildClassifyLookups } from '../../lib/backlog/lookups.js';\nimport {\n classifyIssue,\n computeWaves,\n computeCohort,\n computeStats,\n selectNeedsPlanning,\n} from '../../lib/backlog/pickup.js';\nimport { isFlywheelAutoPickupBacklog } from '../../lib/overdeck/control-settings.js';\nimport { getMergeBlockersPayload } from '../../lib/cloister/merge-blockers.js';\nimport { isGitHubAppConfigured, listOpenIssuesWithLabels } from '../../lib/github-app.js';\n\n/**\n * Sandbox-safe Flywheel data surfaces. These read state DIRECTLY (sequence.md + SQLite) and\n * emit no HTTP, so they work inside a network-isolated harness sandbox (e.g. codex's bwrap)\n * where the orchestrator cannot `curl http://127.0.0.1:3011/api/...`. They share the pickup\n * model (PAN-2006) and the merge-blocker model (PAN-1620) with the dashboard routes, so the\n * CLI and the dashboard can never disagree.\n */\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\nfunction parseGitHubRemoteUrl(remoteUrl: string): { owner: string; repo: string } | null {\n const trimmed = remoteUrl.trim();\n const match = trimmed.match(/github\\.com[:/]([^/]+)\\/([^/\\s]+?)(?:\\.git)?$/i);\n if (!match) return null;\n return { owner: match[1]!, repo: match[2]! };\n}\n\nasync function resolveGitHubOwnerRepo(): Promise<{ owner: string; repo: string } | null> {\n try {\n const { stdout } = await execAsync('git remote get-url origin', { encoding: 'utf8' });\n return parseGitHubRemoteUrl(stdout);\n } catch {\n return null;\n }\n}\n\n/** Labels for all open issues, keyed by bare number — fetched via REST so it works inside a\n * sandboxed harness without burning the gh GraphQL budget. */\nexport async function fetchOpenIssueLabels(): Promise<Map<string, string[]>> {\n const byNumber = new Map<string, string[]>();\n try {\n const repo = await resolveGitHubOwnerRepo();\n if (!repo) return byNumber;\n\n if (isGitHubAppConfigured()) {\n const issues = await Effect.runPromise(listOpenIssuesWithLabels(repo.owner, repo.repo));\n for (const issue of issues) {\n byNumber.set(String(issue.number), issue.labels);\n }\n return byNumber;\n }\n\n const { stdout } = await execFileAsync(\n 'gh',\n ['api', '--paginate', '--slurp', `repos/${repo.owner}/${repo.repo}/issues?state=open&per_page=100`],\n { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 },\n );\n const pages = JSON.parse(stdout || '[]') as Array<Array<{\n number: number;\n pull_request?: unknown;\n labels?: Array<{ name?: string | null } | string>;\n }>>;\n for (const issue of pages.flat()) {\n if (issue.pull_request) continue;\n byNumber.set(String(issue.number), (issue.labels ?? [])\n .map((label) => typeof label === 'string' ? label : label.name)\n .filter((name): name is string => typeof name === 'string' && name.length > 0));\n }\n } catch { /* GitHub unavailable — labels stay empty (degraded, never throws) */ }\n return byNumber;\n}\n\n/** `pan backlog forecast` — the pickup forecast (pickable waves, needs-planning, cohort, stats). */\nexport async function backlogForecastCommand(opts: { n?: string } = {}): Promise<void> {\n const projectRoot = process.cwd();\n const n = Math.max(1, Math.min(20, Number.parseInt(opts.n ?? '5', 10) || 5));\n const seqPath = join(projectRoot, '.pan', 'backlog', 'sequence.md');\n if (!existsSync(seqPath)) {\n console.log(JSON.stringify({ n, stats: null, inFlight: [], needsPlanning: [], waves: [], cohort: [] }, null, 2));\n return;\n }\n const parsed = parseSequenceMd(readFileSync(seqPath, 'utf-8'));\n if (!parsed.ok) {\n console.error(`parse error: ${parsed.error}`);\n process.exitCode = 1;\n return;\n }\n const nodes = parsed.doc.nodes;\n const labelsByNumber = await fetchOpenIssueLabels();\n const lk = buildClassifyLookups(projectRoot, { labels: (id) => labelsByNumber.get(id.replace(/^[A-Za-z]+-/, '')) ?? [] });\n const autoPickupBacklog = isFlywheelAutoPickupBacklog();\n const inFlight = nodes\n .map((x) => ({ issue: x.issue, rank: x.rank, state: classifyIssue(x, lk) }))\n .filter((x) => x.state.inPipeline)\n .sort((a, b) => a.rank - b.rank)\n .map((x) => x.issue);\n const needsPlanning = selectNeedsPlanning(nodes, lk, { cap: n * 2 }).map((x) => x.issue);\n const waves = computeWaves(nodes, lk, n, autoPickupBacklog).map((w) => w.map((x) => x.issue));\n const cohort = computeCohort(nodes, lk, n, autoPickupBacklog);\n const stats = computeStats(nodes, lk, autoPickupBacklog);\n console.log(JSON.stringify({ n, autoPickupBacklog, stats, inFlight, needsPlanning, waves, cohort }, null, 2));\n}\n\n/** `pan flywheel merge-blockers` — PRs that passed review but cannot merge (GitHub-native reasons). */\nexport function flywheelMergeBlockersCommand(opts: { json?: boolean } = {}): void {\n const blockers = getMergeBlockersPayload();\n if (opts.json) {\n console.log(JSON.stringify(blockers, null, 2));\n return;\n }\n if (blockers.length === 0) {\n console.log('No merge-blocked PRs.');\n return;\n }\n for (const b of blockers) {\n const reasonTypes = b.reasons.map((r) => r.type).join(', ');\n console.log(`${b.issueId} [${reasonTypes}]${b.prUrl ? ` ${b.prUrl}` : ''}`);\n for (const r of b.reasons) console.log(` - ${r.type}: ${r.summary}`);\n }\n}\n\n/**\n * Attaches the sandbox-safe surface subcommands onto the already-registered `flywheel` and\n * `backlog` commands. Kept here (not in flywheel.ts) so flywheel.ts stays under the file-size\n * ceiling. Call AFTER registerFlywheelCommands(program) and the backlog command are set up.\n */\nexport function registerFlywheelSurfaceCommands(program: Command): void {\n const flywheel = program.commands.find((c) => c.name() === 'flywheel');\n flywheel\n ?.command('merge-blockers')\n .description('PRs that passed review but cannot merge (GitHub-native reasons) — reads SQLite directly, no HTTP (sandbox-safe)')\n .option('--json', 'Output JSON')\n .action((o: { json?: boolean }) => flywheelMergeBlockersCommand(o));\n\n const backlog = program.commands.find((c) => c.name() === 'backlog');\n backlog\n ?.command('forecast')\n .description('Pickup forecast (waves / needs-planning / cohort / stats) from sequence.md + SQLite — no HTTP (sandbox-safe)')\n .option('-n, --n <n>', 'Wave size (default 5)')\n .action((o: { n?: string }) => backlogForecastCommand(o));\n}\n","import { exec, spawn } from 'node:child_process';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { freemem, totalmem } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { Effect, Schema } from 'effect';\nimport { layer as nodeServicesLayer } from '@effect/platform-node/NodeServices';\nimport { Command } from 'commander';\nimport {\n FlywheelStats,\n FlywheelStatus,\n type FlywheelStats as FlywheelStatsPayload,\n type FlywheelStatsCriteria,\n type FlywheelStatsCriterion,\n type FlywheelStatsCriterionStatus,\n type FlywheelStatsTrend,\n} from '@overdeck/contracts';\nimport { abortFlywheelRun, clearFlywheelGate, getFlywheelRunDetail, getFlywheelRunDir, listFlywheelRuns, nextFlywheelRunId, readFlywheelLaunchMetadata, resolveLiveFlywheelRunId, writeFlywheelLaunchMetadata, writeLatestFlywheelStatus } from '../../dashboard/server/services/flywheel-run-state.js';\nimport { loadConfigSync, resolveModel, type FlywheelScope, type RoleEffort } from '../../lib/config-yaml.js';\nimport { FLYWHEEL_ORCHESTRATOR_AGENT_ID, pauseFlywheel, resumeFlywheel, spawnFlywheel } from '../../lib/cloister/flywheel.js';\nimport { stopAgent } from '../../lib/agents.js';\nimport type { RuntimeName } from '../../lib/runtimes/types.js';\nimport { resolveHarness } from '../../lib/harness-resolve.js';\nimport {\n FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY,\n FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY,\n getFlywheelActiveRunId,\n isFlywheelAutoPickupBacklog,\n isFlywheelGloballyPaused,\n isFlywheelRequireUatBeforeMerge,\n setFlywheelAutoPickupBacklog,\n setFlywheelRequireUatBeforeMerge,\n} from '../../lib/overdeck/control-settings.js';\nimport { sessionExists } from '../../lib/tmux.js';\nimport { ensureInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../../lib/internal-token.js';\nimport { computeMergeQueue, type MergeQueueItem } from '../../lib/flywheel-merge-order.js';\nimport { DEFAULT_BRIEF_PATH, requireFlywheelBrief, resolvePrimaryWorktreeRoot } from '../../lib/flywheel-start.js';\nimport { formatMergeBackendStatus, loadMergeBackendStatusForCli } from './flywheel-merge-backend.js';\nimport { registerFlywheelSurfaceCommands } from './flywheel-surfaces.js';\n\ntype InputStream = AsyncIterable<string | Buffer | Uint8Array>;\n\ninterface EmitStatusOptions {\n file: string;\n}\n\ninterface StatusOptions {\n json?: boolean;\n}\n\ninterface StatsOptions {\n window?: string;\n json?: boolean;\n}\n\ninterface FormatStatsOptions {\n color?: boolean;\n}\n\ninterface ConfigOptions {\n get?: true | string;\n set?: string;\n}\n\ninterface StartOptions {\n brief?: string;\n cwd?: string;\n}\n\ninterface StartFlywheelRunResult {\n runId: string;\n briefDisplayPath: string;\n agentModel?: string;\n}\n\ninterface ReportOptions {\n cwd?: string;\n force?: boolean;\n}\n\ninterface ReportOpenOptions {\n runId?: string;\n opener?: (path: string) => void | Promise<void>;\n}\n\ninterface FlywheelGateSnapshot {\n paused: boolean;\n activeRunId: string | null;\n}\n\ninterface ResolvedFlywheelRoleConfig {\n harness: 'claude-code' | 'ohmypi' | 'codex';\n model: string;\n effort: RoleEffort;\n minAgents: number;\n maxAgents: number;\n scope: FlywheelScope;\n autoPickupBacklog: boolean;\n requireUatBeforeMerge: boolean;\n}\n\nconst decodeFlywheelStatus = Schema.decodeUnknownSync(FlywheelStatus);\nconst decodeFlywheelStats = Schema.decodeUnknownSync(FlywheelStats);\nconst execAsync = promisify(exec);\nconst DEFAULT_STATS_WINDOW = '30d';\n\nfunction dashboardBaseUrl(): string {\n return (process.env.OVERDECK_DASHBOARD_URL || process.env.DASHBOARD_URL || 'http://localhost:3011').replace(/\\/$/, '');\n}\n\nexport async function readFlywheelStatusJson(file: string, input: InputStream = process.stdin): Promise<string> {\n if (file !== '-') return readFile(file, 'utf8');\n\n const chunks: string[] = [];\n for await (const chunk of input) {\n chunks.push(typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8'));\n }\n return chunks.join('');\n}\n\nexport function parseFlywheelStatusJson(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid JSON: ${message}`);\n }\n}\n\nexport function validateFlywheelStatusPayload(payload: unknown): FlywheelStatus {\n try {\n return decodeFlywheelStatus(payload);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Invalid FlywheelStatus: ${message}`);\n }\n}\n\nfunction isFlywheelConfigKey(key: string): key is FlywheelConfigKey {\n return FLYWHEEL_CONFIG_KEYS.includes(key as FlywheelConfigKey);\n}\n\nfunction parseFlywheelConfigKey(key: string): FlywheelConfigKey {\n if (!isFlywheelConfigKey(key)) throw new Error(`Unknown flywheel config key: ${key}`);\n return key;\n}\n\nfunction readFlywheelConfigValue(key: FlywheelConfigKey): boolean {\n switch (key) {\n case FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY:\n return isFlywheelAutoPickupBacklog();\n case FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY:\n return isFlywheelRequireUatBeforeMerge();\n }\n}\n\nfunction writeFlywheelConfigValue(key: FlywheelConfigKey, value: boolean): void {\n switch (key) {\n case FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY:\n setFlywheelAutoPickupBacklog(value);\n return;\n case FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY:\n setFlywheelRequireUatBeforeMerge(value);\n return;\n }\n}\n\nfunction formatFlywheelConfigValue(key: FlywheelConfigKey): string {\n return `${key}=${readFlywheelConfigValue(key)}`;\n}\n\nfunction parseConfigBoolean(key: string, rawValue: string): boolean {\n if (rawValue === 'true') return true;\n if (rawValue === 'false') return false;\n throw new Error(`Boolean value required for ${key}: ${rawValue}`);\n}\n\nfunction parseFlywheelConfigAssignment(assignment: string): { key: FlywheelConfigKey; value: boolean } {\n const separator = assignment.indexOf('=');\n if (separator === -1) throw new Error('Flywheel config assignment must use <key>=<bool>');\n const key = parseFlywheelConfigKey(assignment.slice(0, separator));\n const value = parseConfigBoolean(key, assignment.slice(separator + 1));\n return { key, value };\n}\n\nexport async function flywheelConfigCommand(options: ConfigOptions = {}): Promise<void> {\n try {\n if (options.get !== undefined && options.set !== undefined) {\n throw new Error('Use either --get or --set, not both');\n }\n\n if (options.set !== undefined) {\n const { key, value } = parseFlywheelConfigAssignment(options.set);\n writeFlywheelConfigValue(key, value);\n console.log(`${key}=${value}`);\n return;\n }\n\n if (typeof options.get === 'string') {\n console.log(formatFlywheelConfigValue(parseFlywheelConfigKey(options.get)));\n return;\n }\n\n console.log(FLYWHEEL_CONFIG_KEYS.map(formatFlywheelConfigValue).join('\\n'));\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nconst FLYWHEEL_CONFIG_KEYS = [\n FLYWHEEL_AUTO_PICKUP_BACKLOG_KEY,\n FLYWHEEL_REQUIRE_UAT_BEFORE_MERGE_KEY,\n] as const;\n\ntype FlywheelConfigKey = typeof FLYWHEEL_CONFIG_KEYS[number];\n\nfunction mb(bytes: number): number {\n return Math.round(bytes / 1024 / 1024);\n}\n\nasync function resolveFlywheelRoleConfig(): Promise<ResolvedFlywheelRoleConfig> {\n const { config } = loadConfigSync();\n const flywheel = config.roles?.flywheel;\n const model = resolveModel('flywheel', undefined, config);\n // PAN-1984/PAN-1865: derive the harness from the model provider; never pin\n // flywheel to claude-code or a hardcoded fallback.\n const harness = await resolveHarness({ model });\n return {\n harness,\n model,\n effort: flywheel?.effort ?? 'high',\n minAgents: flywheel?.minAgents ?? 20,\n maxAgents: flywheel?.maxAgents ?? 30,\n scope: flywheel?.scope ?? 'pan-only',\n autoPickupBacklog: isFlywheelAutoPickupBacklog(),\n requireUatBeforeMerge: isFlywheelRequireUatBeforeMerge(),\n };\n}\n\nasync function createInitialFlywheelStatus(\n runId: string,\n startedAt: string,\n cwd: string,\n agentModel: string | undefined,\n agentHarness: RuntimeName | undefined,\n roleConfig: ResolvedFlywheelRoleConfig,\n): Promise<FlywheelStatus> {\n const ramTotalMb = mb(totalmem());\n return {\n runId,\n startedAt,\n elapsedMs: 0,\n orchestrator: {\n harness: agentHarness ?? roleConfig.harness,\n model: agentModel ?? roleConfig.model,\n effort: roleConfig.effort,\n ctxPercent: 0,\n },\n headline: {\n bugsFixed: 0,\n swarmItemsMerged: 0,\n swarmItemsTotal: 0,\n prsMerged: 0,\n awaitingUat: 0,\n },\n activePipeline: [],\n substrateBugs: [],\n agents: [{\n id: FLYWHEEL_ORCHESTRATOR_AGENT_ID,\n label: 'flywheel-orchestrator',\n status: 'running',\n role: 'flywheel',\n model: agentModel,\n }],\n parked: [],\n suggestions: [],\n system: {\n mainHead: await gitOutput('git rev-parse --short HEAD', cwd).catch(() => 'unknown'),\n ramUsedMb: Math.max(0, ramTotalMb - mb(freemem())),\n ramTotalMb,\n swapUsedMb: 0,\n swapTotalMb: 0,\n agentsActive: 1,\n agentsCap: roleConfig.maxAgents,\n },\n openQuestions: [],\n ticks: 0,\n lastTickAt: startedAt,\n };\n}\n\nexport async function postFlywheelStatus(status: FlywheelStatus, fetchImpl: typeof fetch = fetch): Promise<void> {\n const internalToken = ensureInternalTokenSync();\n const res = await fetchImpl(`${dashboardBaseUrl()}/api/flywheel/status`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [INTERNAL_TOKEN_HEADER]: internalToken,\n },\n body: JSON.stringify(status),\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`Dashboard rejected FlywheelStatus (${res.status})${body ? `: ${body}` : ''}`);\n }\n}\n\nexport async function emitStatusCommand(options: EmitStatusOptions): Promise<void> {\n try {\n const raw = await readFlywheelStatusJson(options.file);\n const payload = parseFlywheelStatusJson(raw);\n const status = validateFlywheelStatusPayload(payload);\n await postFlywheelStatus(status);\n console.log(`Flywheel status emitted for ${status.runId}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport async function startFlywheelRun(options: StartOptions = {}): Promise<StartFlywheelRunResult> {\n const cwd = options.cwd ?? await resolvePrimaryWorktreeRoot(process.cwd());\n const brief = await requireFlywheelBrief(cwd, options.brief);\n const runId = await nextFlywheelRunId();\n const startedAt = new Date().toISOString();\n await writeFlywheelLaunchMetadata({\n version: 1,\n runId,\n workspace: cwd,\n briefPath: brief.absolutePath,\n briefDisplayPath: brief.displayPath,\n });\n const roleConfig = await resolveFlywheelRoleConfig();\n const agent = await spawnFlywheel({\n runId,\n briefPath: brief.absolutePath,\n workspace: cwd,\n model: roleConfig.model,\n harness: roleConfig.harness,\n effort: roleConfig.effort,\n minAgents: roleConfig.minAgents,\n maxAgents: roleConfig.maxAgents,\n scope: roleConfig.scope,\n autoPickupBacklog: roleConfig.autoPickupBacklog,\n requireUatBeforeMerge: roleConfig.requireUatBeforeMerge,\n });\n await writeLatestFlywheelStatus(await createInitialFlywheelStatus(\n runId,\n startedAt,\n cwd,\n agent.model,\n agent.harness,\n roleConfig,\n ));\n return { runId, briefDisplayPath: brief.displayPath, agentModel: agent.model };\n}\n\nexport async function flywheelStartCommand(options: StartOptions = {}): Promise<void> {\n try {\n const result = await startFlywheelRun(options);\n\n console.log(`Flywheel started: ${result.runId}`);\n console.log(`Brief: ${result.briefDisplayPath}`);\n console.log(`Run URL: ${dashboardBaseUrl()}/flywheel`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction formatElapsed(ms: number): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) return `${hours}h ${minutes}m`;\n if (minutes > 0) return `${minutes}m ${seconds}s`;\n return `${seconds}s`;\n}\n\nasync function loadActiveFlywheelStatus(): Promise<FlywheelStatus | null> {\n // resolveLiveFlywheelRunId self-heals the SQLite gate when the prior run\n // has ended or its on-disk state is gone (PAN-1245). Status and start now\n // consult the same source of truth.\n const activeRunId = await resolveLiveFlywheelRunId();\n if (!activeRunId) return null;\n const detail = await getFlywheelRunDetail(activeRunId);\n return detail?.status === 'running' ? detail.latest : null;\n}\n\nexport function formatFlywheelStatus(status: FlywheelStatus): string {\n return [\n `Run: ${status.runId}`,\n `Elapsed: ${formatElapsed(status.elapsedMs)}`,\n `Bugs fixed: ${status.headline.bugsFixed}`,\n `SWARM items: ${status.headline.swarmItemsMerged}/${status.headline.swarmItemsTotal}`,\n `PRs merged: ${status.headline.prsMerged}`,\n `Awaiting UAT: ${status.headline.awaitingUat}`,\n `Active agents: ${status.system.agentsActive}/${status.system.agentsCap}`,\n `RAM: ${status.system.ramUsedMb} MiB used / ${status.system.ramTotalMb} MiB total`,\n `Main HEAD: ${status.system.mainHead.slice(0, 7)}`,\n `Last tick: ${status.lastTickAt}`,\n ].join('\\n');\n}\n\nexport async function flywheelStatusCommand(options: StatusOptions): Promise<void> {\n try {\n const status = await loadActiveFlywheelStatus();\n if (!status) {\n console.error('no active flywheel run');\n process.exitCode = 1;\n return;\n }\n\n const mergeBackend = await loadMergeBackendStatusForCli();\n console.log(options.json\n ? JSON.stringify({ ...status, mergeBackend }, null, 2)\n : `${formatFlywheelStatus(status)}\\n${formatMergeBackendStatus(mergeBackend)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nconst STATS_CRITERION_KEYS = [\n 'c1_bugRate',\n 'c2_p0Bugs',\n 'c3_passRate',\n 'c4_mttr',\n 'c5_intervention',\n 'c6_timeConsistency',\n 'c7_flake',\n] as const satisfies readonly (keyof FlywheelStatsCriteria)[];\n\nconst STATUS_GLYPH: Record<FlywheelStatsCriterionStatus, string> = {\n green: '● green',\n yellow: '● yellow',\n red: '● red',\n insufficient_data: '○ insufficient_data',\n};\n\nconst STATUS_COLOR: Record<FlywheelStatsCriterionStatus, string> = {\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n insufficient_data: '\\x1b[90m',\n};\n\nconst TREND_LABEL: Record<FlywheelStatsTrend, string> = {\n up: '↗ up',\n down: '↘ down',\n flat: '→ flat',\n};\n\nexport async function fetchFlywheelStats(window: string, fetchImpl: typeof fetch = fetch): Promise<FlywheelStatsPayload> {\n const internalToken = ensureInternalTokenSync();\n const url = new URL(`${dashboardBaseUrl()}/api/flywheel/stats`);\n url.searchParams.set('window', window);\n const res = await fetchImpl(url.toString(), {\n headers: {\n [INTERNAL_TOKEN_HEADER]: internalToken,\n },\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new Error(`Dashboard rejected Flywheel stats request (${res.status})${body ? `: ${body}` : ''}`);\n }\n\n return decodeFlywheelStats(await res.json());\n}\n\nfunction formatScalar(value: number): string {\n if (!Number.isFinite(value)) return '—';\n if (value > 0 && Math.abs(value) < 1) return `${(value * 100).toFixed(1)}%`;\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n}\n\nfunction formatDuration(ms: number): string {\n const abs = Math.abs(ms);\n if (abs >= 24 * 60 * 60 * 1000) return `${(ms / (24 * 60 * 60 * 1000)).toFixed(1)}d`;\n if (abs >= 60 * 60 * 1000) return `${(ms / (60 * 60 * 1000)).toFixed(1)}h`;\n if (abs >= 60 * 1000) return `${(ms / (60 * 1000)).toFixed(1)}m`;\n return `${Math.round(ms)}ms`;\n}\n\nfunction formatObjectValue(value: Record<string, unknown>): string {\n return Object.entries(value)\n .map(([key, entry]) => {\n if (typeof entry === 'number' && key.toLowerCase().endsWith('ms')) return `${key}: ${formatDuration(entry)}`;\n if (typeof entry === 'number') return `${key}: ${formatScalar(entry)}`;\n if (typeof entry === 'string' || typeof entry === 'boolean' || entry === null) return `${key}: ${String(entry)}`;\n return `${key}: ${JSON.stringify(entry)}`;\n })\n .join(', ');\n}\n\nfunction formatCriterionValue(value: FlywheelStatsCriterion['value']): string {\n return typeof value === 'number' ? formatScalar(value) : formatObjectValue(value as Record<string, unknown>);\n}\n\nfunction colorStatus(status: FlywheelStatsCriterionStatus, color: boolean): string {\n if (!color) return STATUS_GLYPH[status];\n const [glyph, label] = STATUS_GLYPH[status].split(' ');\n return `${STATUS_COLOR[status]}${glyph}\\x1b[0m ${label}`;\n}\n\nfunction tableStatsCell(value: string | number | undefined): string {\n return String(value ?? '—').replace(/\\|/g, '\\\\|').replace(/\\n/g, ' ');\n}\n\nexport function formatFlywheelStats(stats: FlywheelStatsPayload, options: FormatStatsOptions = {}): string {\n const color = options.color ?? process.stdout.isTTY === true;\n const rows = STATS_CRITERION_KEYS.map((key) => {\n const criterion = stats.criteria[key];\n return `| ${tableStatsCell(criterion.label)} | ${tableStatsCell(formatCriterionValue(criterion.value))} | ${tableStatsCell(formatCriterionValue(criterion.target))} | ${tableStatsCell(colorStatus(criterion.status, color))} | ${tableStatsCell(criterion.trend ? TREND_LABEL[criterion.trend] : '—')} | ${criterion.sampleSize} |`;\n });\n return [\n `Flywheel stats (${stats.window})`,\n `Generated: ${stats.generatedAt}`,\n '',\n '| Criterion | Value | Target | Status | Trend | Sample |',\n '|---|---:|---:|---|---|---:|',\n ...rows,\n ].join('\\n');\n}\n\nexport async function flywheelStatsCommand(options: StatsOptions = {}): Promise<void> {\n try {\n const stats = await fetchFlywheelStats(options.window ?? DEFAULT_STATS_WINDOW);\n console.log(options.json ? JSON.stringify(stats, null, 2) : formatFlywheelStats(stats));\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction runNumberFromRunId(runId: string): number {\n const match = /^RUN-(\\d+)$/.exec(runId);\n if (!match) throw new Error(`Invalid Flywheel run id for report: ${runId}`);\n return Number(match[1]);\n}\n\nfunction reportDate(status: FlywheelStatus): string {\n return status.lastTickAt.slice(0, 10);\n}\n\nfunction tableCell(value: string | number | undefined): string {\n return String(value ?? '').replace(/\\|/g, '\\\\|').replace(/\\n/g, ' ');\n}\n\nexport function formatFlywheelStateReport(status: FlywheelStatus, mergeQueue: MergeQueueItem[] = []): string {\n const runNumber = runNumberFromRunId(status.runId);\n const suggestionRows = status.suggestions.length > 0\n ? status.suggestions.map((suggestion) => `| ${tableCell(suggestion.priority)} | ${tableCell(suggestion.action)} | ${tableCell(suggestion.issueId)} | ${tableCell(suggestion.rationale)} |`).join('\\n')\n : 'No suggestions emitted this run.';\n const activePipelineRows = status.activePipeline.length > 0\n ? status.activePipeline.map((item) => `| ${tableCell(item.issueId)} | ${tableCell(item.verb)} | ${tableCell(item.status)} | ${tableCell(item.title)} | ${tableCell(item.progressPercent)} | ${tableCell(item.pr)} |`).join('\\n')\n : '| _None_ | | | | | |';\n const substrateRows = status.substrateBugs.length > 0\n ? status.substrateBugs.map((bug) => `| ${tableCell(bug.issueId)} | ${tableCell(bug.status)} | ${tableCell(bug.title)} | ${tableCell(bug.commitSha?.slice(0, 10))} |`).join('\\n')\n : '| _None_ | | | |';\n const patternLines = status.parked.length > 0\n ? status.parked.map((item) => `- **${item.issueId}** (${item.reason}) — ${item.title}`).join('\\n')\n : '- None recorded this run.';\n const questionLines = status.openQuestions.length > 0\n ? status.openQuestions.map((question) => `- ${question}`).join('\\n')\n : '- None.';\n const mergeQueueSection = mergeQueue.length > 0\n ? `## Merge Queue\n\nMerge ship-ready PRs in this order to avoid rebase conflicts. Branches with overlapping file changes must be merged in sequence; non-overlapping branches can be merged in any order.\n\n| # | Issue | PR | Conflicts With |\n|---|---|---|---|\n${mergeQueue.map((item) => {\n const prCell = item.pr != null ? `#${item.pr}` : '—';\n const conflictsCell = item.conflictsWith.length > 0 ? item.conflictsWith.join(', ') : '—';\n return `| ${item.mergeOrder} | ${item.issueId} | ${prCell} | ${conflictsCell} |`;\n}).join('\\n')}\n\n---\n\n`\n : '';\n\n return `# Flywheel Run ${runNumber} Report — ${reportDate(status)}\n\nPer-run report derived from the last \\`FlywheelStatus\\` snapshot. Durable cumulative memory across runs lives in \\`docs/FLYWHEEL-STATE.md\\`.\n\n**Run window:** ${status.startedAt} → ${status.lastTickAt} (${status.orchestrator.harness}, ${status.orchestrator.model}, ${status.orchestrator.effort})\n\n**Headline result:** ${status.headline.bugsFixed} substrate bugs fixed; ${status.headline.swarmItemsMerged}/${status.headline.swarmItemsTotal} SWARM items merged; ${status.headline.prsMerged} PRs merged; ${status.headline.awaitingUat} awaiting UAT.\n\n**Run counters:** ticks ${status.ticks}; last tick ${status.lastTickAt}; orchestrator context ${status.orchestrator.ctxPercent}%.\n\n---\n\n${mergeQueueSection}## Suggestions\n\n${status.suggestions.length > 0 ? `| Priority | Action | Issue | Rationale |\n|---|---|---|---|\n${suggestionRows}` : suggestionRows}\n\n---\n\n## Active Pipeline\n\n| Issue | Verb | Status | Title | Progress | PR |\n|---|---|---|---|---:|---:|\n${activePipelineRows}\n\n---\n\n## Cycling Alerts\n\n${questionLines}\n\n---\n\n## Infrastructure Gaps\n\n| Issue | Status | Notes | Commit |\n|---|---|---|---|\n${substrateRows}\n\n---\n\n## Pattern Ledger\n\n${patternLines}\n\n---\n\n## Skill Gaps\n\n- Keep \\`pan flywheel report\\` in the closeout path so run summaries stay archived under the run directory.\n`;\n}\n\nasync function isFlywheelStateDirty(cwd: string): Promise<boolean> {\n // Tolerate non-git cwd / missing file: a non-zero git exit means there are\n // no orchestrator-authored changes worth committing here (PAN-1245). The\n // gate clear in flywheelReportCommand must not be blocked by this check.\n try {\n const { stdout } = await execAsync(\n 'git status --porcelain docs/FLYWHEEL-STATE.md',\n { cwd, encoding: 'utf8' },\n );\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\nasync function loadReportFlywheelStatus(): Promise<FlywheelStatus | null> {\n const activeRunId = getFlywheelActiveRunId();\n if (activeRunId) {\n const activeDetail = await getFlywheelRunDetail(activeRunId);\n if (activeDetail?.latest) return activeDetail.latest;\n }\n const runs = await listFlywheelRuns();\n const run = runs.find((candidate) => candidate.status === 'running') ?? runs[0];\n if (!run) return null;\n const detail = await getFlywheelRunDetail(run.id);\n return detail?.latest ?? null;\n}\n\nasync function gitOutput(command: string, cwd: string): Promise<string> {\n const { stdout } = await execAsync(command, { cwd, encoding: 'utf8' });\n return stdout.trim();\n}\n\nasync function commitFlywheelStateChanges(cwd: string, runNumber: number): Promise<void> {\n const subject = `docs(flywheel): run ${runNumber}`;\n await execAsync('git add docs/FLYWHEEL-STATE.md', { cwd });\n const headSubject = await gitOutput('git log -1 --format=%s', cwd).catch(() => '');\n const command = headSubject === subject\n ? `git commit --amend -m ${JSON.stringify(subject)}`\n : `git commit -m ${JSON.stringify(subject)}`;\n await execAsync(command, { cwd, encoding: 'utf8' });\n}\n\nfunction readFlywheelGateSnapshot(): FlywheelGateSnapshot {\n return {\n paused: isFlywheelGloballyPaused(),\n activeRunId: getFlywheelActiveRunId(),\n };\n}\n\nfunction formatGateSnapshot(snapshot: FlywheelGateSnapshot): string {\n return `paused=${snapshot.paused ? 'true' : 'false'} active_run_id=${snapshot.activeRunId ?? 'none'}`;\n}\n\nexport async function pauseFlywheelRun(): Promise<{ before: FlywheelGateSnapshot; after: FlywheelGateSnapshot; changed: boolean }> {\n const before = readFlywheelGateSnapshot();\n if (before.paused) return { before, after: before, changed: false };\n await pauseFlywheel();\n return { before, after: readFlywheelGateSnapshot(), changed: true };\n}\n\nexport async function flywheelPauseCommand(): Promise<void> {\n try {\n const result = await pauseFlywheelRun();\n if (!result.changed) {\n console.log(`Flywheel already paused (${formatGateSnapshot(result.before)})`);\n return;\n }\n\n console.log(`Flywheel paused: before ${formatGateSnapshot(result.before)}; after ${formatGateSnapshot(result.after)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport async function resumeFlywheelRun(): Promise<{ before: FlywheelGateSnapshot; after: FlywheelGateSnapshot; changed: boolean }> {\n const before = readFlywheelGateSnapshot();\n if (!before.paused && await Effect.runPromise(sessionExists(FLYWHEEL_ORCHESTRATOR_AGENT_ID))) {\n return { before, after: before, changed: false };\n }\n if (!before.activeRunId) throw new Error('No active flywheel run to resume');\n const launch = await readFlywheelLaunchMetadata(before.activeRunId);\n if (!launch) {\n throw new Error(`Flywheel run ${before.activeRunId} is missing launch metadata; cannot resume safely`);\n }\n const brief = await requireFlywheelBrief(launch.workspace, launch.briefPath);\n const roleConfig = await resolveFlywheelRoleConfig();\n await resumeFlywheel({\n workspace: launch.workspace,\n briefPath: brief.absolutePath,\n model: roleConfig.model,\n harness: roleConfig.harness,\n effort: roleConfig.effort,\n minAgents: roleConfig.minAgents,\n maxAgents: roleConfig.maxAgents,\n scope: roleConfig.scope,\n autoPickupBacklog: roleConfig.autoPickupBacklog,\n requireUatBeforeMerge: roleConfig.requireUatBeforeMerge,\n });\n return { before, after: readFlywheelGateSnapshot(), changed: true };\n}\n\nexport async function flywheelResumeCommand(): Promise<void> {\n try {\n const result = await resumeFlywheelRun();\n if (!result.changed) {\n console.log(`Flywheel already running (${formatGateSnapshot(result.before)})`);\n return;\n }\n\n console.log(`Flywheel resumed: before ${formatGateSnapshot(result.before)}; after ${formatGateSnapshot(result.after)}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction clearFlywheelRunGate(runId: string): void {\n if (getFlywheelActiveRunId() === runId) {\n clearFlywheelGate();\n }\n}\n\n// Stop the orchestrator, write the report, commit state changes, and clear the gate.\nexport async function flywheelStopCommand(): Promise<void> {\n try {\n const sessionAlive = await Effect.runPromise(sessionExists(FLYWHEEL_ORCHESTRATOR_AGENT_ID));\n if (sessionAlive) {\n await Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID));\n }\n\n const status = await loadReportFlywheelStatus();\n if (!status) {\n console.log('No flywheel run is active and nothing is left to report.');\n return;\n }\n\n // The orchestrator has already been stopped, so force the report path to\n // bypass its alive-session guard and finalize the run.\n await flywheelReportCommand({ force: true });\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport async function flywheelReportCommand(options: ReportOptions = {}): Promise<void> {\n try {\n const cwd = options.cwd ?? process.cwd();\n\n // Writing report.md finalizes the run. Refuse while the orchestrator is alive;\n // its own end-of-run call passes --force to bypass this guard.\n if (!options.force && await Effect.runPromise(sessionExists(FLYWHEEL_ORCHESTRATOR_AGENT_ID))) {\n console.error('Refusing to write report — flywheel orchestrator session is still alive.');\n console.error('This command finalizes the run (writes report.md and clears the active-run gate).');\n console.error('Run `pan flywheel pause` (or `pan flywheel abort`) first, or pass --force to override.');\n process.exitCode = 1;\n return;\n }\n\n const status = await loadReportFlywheelStatus();\n if (!status) {\n console.error('no flywheel run to report');\n process.exitCode = 1;\n return;\n }\n\n const runNumber = runNumberFromRunId(status.runId);\n const mergeQueue = await Effect.runPromise(\n computeMergeQueue(status.activePipeline, cwd).pipe(Effect.provide(nodeServicesLayer)),\n );\n const runReport = formatFlywheelStateReport(status, mergeQueue);\n await writeFile(join(getFlywheelRunDir(status.runId), 'report.md'), runReport, 'utf8');\n\n // PAN-1245: clear the gate after writing report.md even if commit fails.\n try {\n const stateChanged = await isFlywheelStateDirty(cwd);\n if (stateChanged) {\n await commitFlywheelStateChanges(cwd, runNumber);\n console.log(`Wrote per-run report and committed FLYWHEEL-STATE.md changes for run ${runNumber}.`);\n } else {\n console.log(`Wrote per-run report for run ${runNumber}. No FLYWHEEL-STATE.md changes to commit.`);\n }\n } finally {\n clearFlywheelRunGate(status.runId);\n }\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nfunction getPlatformOpenCommand(): string {\n switch (process.platform) {\n case 'linux': return 'xdg-open';\n case 'darwin': return 'open';\n case 'win32': return 'explorer';\n default: throw new Error(`Opening files is not supported on ${process.platform}`);\n }\n}\n\nfunction openPathDetached(path: string): void {\n const child = spawn(getPlatformOpenCommand(), [path], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n}\n\nasync function resolveReportOpenRunId(runId: string | undefined): Promise<string> {\n if (runId) return runId;\n const runs = await listFlywheelRuns();\n const run = runs.find((candidate) => candidate.status === 'complete') ?? runs[0];\n if (!run) throw new Error('no flywheel run report to open');\n return run.id;\n}\n\nexport async function openFlywheelRunReport(options: ReportOpenOptions = {}): Promise<{ runId: string; path: string }> {\n const runId = await resolveReportOpenRunId(options.runId);\n const detail = await getFlywheelRunDetail(runId);\n if (!detail) throw new Error(`Flywheel run not found: ${runId}`);\n if (!detail.paths.report) throw new Error(`No report exists for ${runId}`);\n await (options.opener ?? openPathDetached)(detail.paths.report);\n return { runId, path: detail.paths.report };\n}\n\nexport async function flywheelReportOpenCommand(options: ReportOpenOptions = {}): Promise<void> {\n try {\n const result = await openFlywheelRunReport(options);\n console.log(`Opened Flywheel report for ${result.runId}: ${result.path}`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\n// Discard the current run without writing a report (PAN-1245).\nexport async function flywheelAbortCommand(): Promise<void> {\n try {\n const candidate = getFlywheelActiveRunId();\n if (!candidate) {\n console.log('No active flywheel run to abort.');\n return;\n }\n await Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID));\n await abortFlywheelRun(candidate);\n console.log(`Aborted flywheel run ${candidate}.`);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n}\n\nexport function registerFlywheelCommands(program: Command): void {\n const flywheel = program\n .command('flywheel')\n .description('Flywheel orchestrator lifecycle and status helpers');\n registerFlywheelSurfaceCommands(program);\n\n flywheel\n .command('start')\n .description('Start the Flywheel orchestrator')\n .option('--brief <path>', 'Path to the Flywheel brief', DEFAULT_BRIEF_PATH)\n .action(flywheelStartCommand);\n\n flywheel\n .command('emit-status')\n .description('Validate and publish a FlywheelStatus JSON snapshot to the local dashboard')\n .requiredOption('--file <path>', 'Path to FlywheelStatus JSON, or - to read from stdin')\n .action(emitStatusCommand);\n\n flywheel\n .command('config')\n .description('Get or set Flywheel autonomy configuration')\n .option('--get [key]', 'Print Flywheel config values')\n .option('--set <key=value>', 'Set a Flywheel config boolean')\n .action(flywheelConfigCommand);\n\n flywheel\n .command('status')\n .description('Show the active Flywheel run status')\n .option('--json', 'Emit the raw FlywheelStatus JSON')\n .action(flywheelStatusCommand);\n\n flywheel\n .command('stats')\n .description('Show Flywheel v1.0 readiness stats')\n .option('--window <duration>', 'Stats window duration', DEFAULT_STATS_WINDOW)\n .option('--json', 'Emit the raw FlywheelStats JSON')\n .action(flywheelStatsCommand);\n\n flywheel\n .command('pause')\n .description('Pause the active Flywheel orchestrator run')\n .action(flywheelPauseCommand);\n\n flywheel\n .command('resume')\n .description('Resume the paused Flywheel orchestrator run')\n .action(flywheelResumeCommand);\n\n flywheel\n .command('report')\n .description('Finalize the active Flywheel run: write report.md, commit FLYWHEEL-STATE.md changes, and clear the active-run gate. Refuses to run while the orchestrator session is alive (pause or abort first).')\n .option('--force', 'Bypass the orchestrator-alive guard. Intended for the orchestrator role\\'s own end-of-run call.')\n .action(flywheelReportCommand);\n\n flywheel\n .command('stop')\n .description('Stop the Flywheel orchestrator gracefully: kill any live session, write report.md, commit FLYWHEEL-STATE.md changes, and clear the active-run gate')\n .action(flywheelStopCommand);\n\n flywheel\n .command('abort')\n .description('Discard the active Flywheel run without writing a report')\n .action(flywheelAbortCommand);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,UAAA,KAAA;;;iBCLyF;;;kBCMtB;iBAWuB;AAUxE,UAAU,KAAK;AACX,UAAU,SAAS;;;uBCIO;AAqEnB,OAAO,kBAAkB,eAAe;AACzC,OAAO,kBAAkB,cAAc;AACnE,MAAM,YAAY,UAAU,KAAK;AAobjC,SAAS,mBAAmB,OAAuB;CACjD,MAAM,QAAQ,cAAc,KAAK,MAAM;AACvC,KAAI,CAAC,MAAO,OAAM,IAAI,MAAM,uCAAuC,QAAQ;AAC3E,QAAO,OAAO,MAAM,GAAG;;AAGzB,SAAS,WAAW,QAAgC;AAClD,QAAO,OAAO,WAAW,MAAM,GAAG,GAAG;;AAGvC,SAAS,UAAU,OAA4C;AAC7D,QAAO,OAAO,SAAS,GAAG,CAAC,QAAQ,OAAO,MAAM,CAAC,QAAQ,OAAO,IAAI;;AAGtE,SAAgB,0BAA0B,QAAwB,aAA+B,EAAE,EAAU;CAC3G,MAAM,YAAY,mBAAmB,OAAO,MAAM;CAClD,MAAM,iBAAiB,OAAO,YAAY,SAAS,IAC/C,OAAO,YAAY,KAAK,eAAe,KAAK,UAAU,WAAW,SAAS,CAAC,KAAK,UAAU,WAAW,OAAO,CAAC,KAAK,UAAU,WAAW,QAAQ,CAAC,KAAK,UAAU,WAAW,UAAU,CAAC,IAAI,CAAC,KAAK,KAAK,GACpM;CACJ,MAAM,qBAAqB,OAAO,eAAe,SAAS,IACtD,OAAO,eAAe,KAAK,SAAS,KAAK,UAAU,KAAK,QAAQ,CAAC,KAAK,UAAU,KAAK,KAAK,CAAC,KAAK,UAAU,KAAK,OAAO,CAAC,KAAK,UAAU,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,gBAAgB,CAAC,KAAK,UAAU,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,GAC9N;CACJ,MAAM,gBAAgB,OAAO,cAAc,SAAS,IAChD,OAAO,cAAc,KAAK,QAAQ,KAAK,UAAU,IAAI,QAAQ,CAAC,KAAK,UAAU,IAAI,OAAO,CAAC,KAAK,UAAU,IAAI,MAAM,CAAC,KAAK,UAAU,IAAI,WAAW,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,GAC9K;CACJ,MAAM,eAAe,OAAO,OAAO,SAAS,IACxC,OAAO,OAAO,KAAK,SAAS,OAAO,KAAK,QAAQ,MAAM,KAAK,OAAO,MAAM,KAAK,QAAQ,CAAC,KAAK,KAAK,GAChG;CACJ,MAAM,gBAAgB,OAAO,cAAc,SAAS,IAChD,OAAO,cAAc,KAAK,aAAa,KAAK,WAAW,CAAC,KAAK,KAAK,GAClE;CACJ,MAAM,oBAAoB,WAAW,SAAS,IAC1C;;;;;;EAMJ,WAAW,KAAK,SAAS;EACzB,MAAM,SAAS,KAAK,MAAM,OAAO,IAAI,KAAK,OAAO;EACjD,MAAM,gBAAgB,KAAK,cAAc,SAAS,IAAI,KAAK,cAAc,KAAK,KAAK,GAAG;AACtF,SAAO,KAAK,KAAK,WAAW,KAAK,KAAK,QAAQ,KAAK,OAAO,KAAK,cAAc;GAC7E,CAAC,KAAK,KAAK,CAAC;;;;IAKR;AAEJ,QAAO,kBAAkB,UAAU,YAAY,WAAW,OAAO,CAAC;;;;kBAIlD,OAAO,UAAU,KAAK,OAAO,WAAW,IAAI,OAAO,aAAa,QAAQ,IAAI,OAAO,aAAa,MAAM,IAAI,OAAO,aAAa,OAAO;;uBAEhI,OAAO,SAAS,UAAU,yBAAyB,OAAO,SAAS,iBAAiB,GAAG,OAAO,SAAS,gBAAgB,uBAAuB,OAAO,SAAS,UAAU,eAAe,OAAO,SAAS,YAAY;;0BAEhN,OAAO,MAAM,cAAc,OAAO,WAAW,yBAAyB,OAAO,aAAa,WAAW;;;;EAI7H,kBAAkB;;EAElB,OAAO,YAAY,SAAS,IAAI;;EAEhC,mBAAmB,eAAe;;;;;;;;EAQlC,mBAAmB;;;;;;EAMnB,cAAc;;;;;;;;EAQd,cAAc;;;;;;EAMd,aAAa;;;;;;;;;AAUf,eAAe,qBAAqB,KAA+B;AAIjE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,UACvB,iDACA;GAAE;GAAK,UAAU;GAAQ,CAC1B;AACD,SAAO,OAAO,MAAM,CAAC,SAAS;SACxB;AACN,SAAO;;;AAIX,eAAe,2BAA2D;CACxE,MAAM,cAAc,wBAAwB;AAC5C,KAAI,aAAa;EACf,MAAM,eAAe,MAAM,qBAAqB,YAAY;AAC5D,MAAI,cAAc,OAAQ,QAAO,aAAa;;CAEhD,MAAM,OAAO,MAAM,kBAAkB;CACrC,MAAM,MAAM,KAAK,MAAM,cAAc,UAAU,WAAW,UAAU,IAAI,KAAK;AAC7E,KAAI,CAAC,IAAK,QAAO;AAEjB,SAAO,MADc,qBAAqB,IAAI,GAAG,GAClC,UAAU;;AAG3B,eAAe,UAAU,SAAiB,KAA8B;CACtE,MAAM,EAAE,WAAW,MAAM,UAAU,SAAS;EAAE;EAAK,UAAU;EAAQ,CAAC;AACtE,QAAO,OAAO,MAAM;;AAGtB,eAAe,2BAA2B,KAAa,WAAkC;CACvF,MAAM,UAAU,uBAAuB;AACvC,OAAM,UAAU,kCAAkC,EAAE,KAAK,CAAC;AAK1D,OAAM,UAHU,MADU,UAAU,0BAA0B,IAAI,CAAC,YAAY,GAAG,KAClD,UAC5B,yBAAyB,KAAK,UAAU,QAAQ,KAChD,iBAAiB,KAAK,UAAU,QAAQ,IACnB;EAAE;EAAK,UAAU;EAAQ,CAAC;;AA8ErD,SAAS,qBAAqB,OAAqB;AACjD,KAAI,wBAAwB,KAAK,MAC/B,oBAAmB;;AA2BvB,eAAsB,sBAAsB,UAAyB,EAAE,EAAiB;AACtF,KAAI;EACF,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;AAIxC,MAAI,CAAC,QAAQ,SAAS,MAAM,OAAO,WAAW,cAAA,wBAA6C,CAAC,EAAE;AAC5F,WAAQ,MAAM,2EAA2E;AACzF,WAAQ,MAAM,oFAAoF;AAClG,WAAQ,MAAM,yFAAyF;AACvG,WAAQ,WAAW;AACnB;;EAGF,MAAM,SAAS,MAAM,0BAA0B;AAC/C,MAAI,CAAC,QAAQ;AACX,WAAQ,MAAM,4BAA4B;AAC1C,WAAQ,WAAW;AACnB;;EAGF,MAAM,YAAY,mBAAmB,OAAO,MAAM;EAIlD,MAAM,YAAY,0BAA0B,QAAQ,MAH3B,OAAO,WAC9B,kBAAkB,OAAO,gBAAgB,IAAI,CAAC,KAAK,OAAO,QAAQE,MAAkB,CAAC,CACtF,CAC8D;AAC/D,QAAM,UAAU,KAAK,kBAAkB,OAAO,MAAM,EAAE,YAAY,EAAE,WAAW,OAAO;AAGtF,MAAI;AAEF,OAAI,MADuB,qBAAqB,IAAI,EAClC;AAChB,UAAM,2BAA2B,KAAK,UAAU;AAChD,YAAQ,IAAI,wEAAwE,UAAU,GAAG;SAEjG,SAAQ,IAAI,gCAAgC,UAAU,2CAA2C;YAE3F;AACR,wBAAqB,OAAO,MAAM;;UAE7B,OAAO;AACd,UAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;AACrE,UAAQ,WAAW"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as pauseFlywheel, o as resumeFlywheel } from "./flywheel-
|
|
1
|
+
import { a as pauseFlywheel, o as resumeFlywheel } from "./flywheel-BLoVNtJV.js";
|
|
2
2
|
export { pauseFlywheel, resumeFlywheel };
|
package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js → flywheel-merge-order-B7BH8mZ3.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { En as getProjectSwarmHotspots, Nn as init_projects, bn as findProjectByPathSync } from "./config-yaml-DPAieL_G.js";
|
|
2
|
-
import { c as init_review_status, p as mergeGateEligibility, s as getReviewStatusSync } from "./review-status-
|
|
2
|
+
import { c as init_review_status, p as mergeGateEligibility, s as getReviewStatusSync } from "./review-status-B5ZASOo4.js";
|
|
3
3
|
import { i as resolveGitHubIssueSync, n as init_tracker_utils } from "./tracker-utils-bpWs7Xyx.js";
|
|
4
4
|
import { i as compileGlob, u as init_dag } from "./dag-jH8ZkewR.js";
|
|
5
5
|
import { n as computeIssueFootprint, r as init_swarm_readiness } from "./swarm-readiness-OBgamRql.js";
|
|
@@ -240,4 +240,4 @@ function pickFromSequence(nodes, opts) {
|
|
|
240
240
|
//#endregion
|
|
241
241
|
export { declaredIssueFootprint as a, resolveMergeQueuePrUrl as c, computePredictedConflictSignals as i, reviewRecordEligibility as l, changedFilesVsMain as n, orderMergeCandidates as o, computeMergeQueue as r, pickFromSequence as s, MERGE_GATE_VERBS as t };
|
|
242
242
|
|
|
243
|
-
//# sourceMappingURL=flywheel-merge-order-
|
|
243
|
+
//# sourceMappingURL=flywheel-merge-order-B7BH8mZ3.js.map
|
package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js.map → flywheel-merge-order-B7BH8mZ3.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flywheel-merge-order-CuDcqTRJ.js","names":[],"sources":["../../src/lib/flywheel-merge-order.ts"],"sourcesContent":["import { Effect } from 'effect';\nimport { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';\nimport type { FlywheelPipelineItem } from '@overdeck/contracts';\nimport { getReviewStatusSync, mergeGateEligibility, type MergeGateEligibility } from './review-status.js';\nimport { resolveGitHubIssueSync } from './tracker-utils.js';\nimport type { SequenceNode } from './backlog/types.js';\nimport { classifyIssue, isAutoPickable, type ClassifyLookups } from './backlog/pickup.js';\nimport { compileGlob, type CompiledGlob } from './vbrief/dag.js';\nimport { computeIssueFootprint } from './vbrief/swarm-readiness.js';\nimport type { VBriefDocument } from './vbrief/types.js';\nimport { findProjectByPathSync, getProjectSwarmHotspots } from './projects.js';\n\nexport interface MergeQueueItem {\n issueId: string;\n title: string;\n branchName: string;\n pr?: number;\n prUrl?: string;\n mergeOrder: number;\n conflictsWith: string[];\n /** PAN-1691: 'batch' = disjoint, mergeable together in one pass; 'serialize' = conflicts, must go one at a time. */\n batchGroup?: 'batch' | 'serialize';\n}\n\nfunction issueNumber(issueId: string): number {\n const match = issueId.match(/\\d+$/);\n return match ? parseInt(match[0], 10) : 0;\n}\n\nexport interface MergeCandidateMeta {\n issueId: string;\n /** Number of files this branch changes vs main. */\n footprint: number;\n /** How many other ready branches this one overlaps files with. */\n conflictCount: number;\n}\n\nexport type FootprintSource = 'declared' | 'actual';\n\nexport interface IssueFileFootprint {\n issueId: string;\n files: Iterable<string>;\n source: FootprintSource;\n}\n\nexport interface PredictedConflictSignal extends MergeCandidateMeta {\n source: FootprintSource;\n conflictsWith: string[];\n}\n\n/**\n * PAN-1691 conflict-aware merge order. Disjoint (no-conflict) items come first\n * so they can batch through in a single verification pass; then conflicting\n * items broadest-file-footprint first, so the remaining cluster members rebase\n * once onto the worst offender instead of repeatedly. Issue number is the\n * stable tiebreak within each tier. Pure — exported for testing.\n */\nexport function orderMergeCandidates<T extends MergeCandidateMeta>(items: ReadonlyArray<T>): T[] {\n return [...items].sort((a, b) => {\n const aConf = a.conflictCount > 0 ? 1 : 0;\n const bConf = b.conflictCount > 0 ? 1 : 0;\n if (aConf !== bConf) return aConf - bConf;\n if (aConf === 1 && a.footprint !== b.footprint) return b.footprint - a.footprint;\n return issueNumber(a.issueId) - issueNumber(b.issueId);\n });\n}\n\nexport interface MergeTrainPlan {\n /** Disjoint candidates that can merge together in one verification pass. */\n batch: string[];\n /** Conflicting candidates that must serialize, broadest-footprint first. */\n serialize: string[];\n /** Full ordered list (batch, then serialize). */\n order: string[];\n}\n\n/**\n * PAN-1691 merge-train plan. Partitions the conflict-aware order into the run of\n * disjoint candidates — which can all merge in a single verification pass — and\n * the conflicting remainder, which must serialize broadest-footprint first.\n * Pure; the executor consumes this once the merge-train flag is enabled.\n */\nexport function planMergeTrain<T extends MergeCandidateMeta>(candidates: ReadonlyArray<T>): MergeTrainPlan {\n const ordered = orderMergeCandidates(candidates);\n return {\n batch: ordered.filter((c) => c.conflictCount === 0).map((c) => c.issueId),\n serialize: ordered.filter((c) => c.conflictCount > 0).map((c) => c.issueId),\n order: ordered.map((c) => c.issueId),\n };\n}\n\nexport function declaredIssueFootprint(issueId: string, doc: VBriefDocument): IssueFileFootprint {\n return { issueId, files: computeIssueFootprint(doc), source: 'declared' };\n}\n\nfunction pathMatchesAnyCompiled(filePath: string, patterns: CompiledGlob[]): boolean {\n return patterns.some(pattern => pattern.regex.test(filePath) || pattern.exactDirectory === filePath);\n}\n\nfunction normalizedFootprintFiles(files: Iterable<string>, hotspots: CompiledGlob[]): Set<string> {\n const normalized = new Set<string>();\n for (const file of files) {\n if (!file || pathMatchesAnyCompiled(file, hotspots)) continue;\n normalized.add(file);\n }\n return normalized;\n}\n\nfunction sourcePriority(source: FootprintSource): number {\n return source === 'actual' ? 2 : 1;\n}\n\nexport function computePredictedConflictSignals(\n footprints: ReadonlyArray<IssueFileFootprint>,\n options: { hotspots?: string[] } = {},\n): PredictedConflictSignal[] {\n const hotspots = (options.hotspots ?? []).map(compileGlob);\n const byIssue = new Map<string, { issueId: string; source: FootprintSource; files: Set<string> }>();\n\n for (const footprint of footprints) {\n const files = normalizedFootprintFiles(footprint.files, hotspots);\n const current = byIssue.get(footprint.issueId);\n if (!current || sourcePriority(footprint.source) > sourcePriority(current.source)) {\n byIssue.set(footprint.issueId, { issueId: footprint.issueId, source: footprint.source, files });\n continue;\n }\n if (sourcePriority(footprint.source) === sourcePriority(current.source)) {\n for (const file of files) current.files.add(file);\n }\n }\n\n const conflictsMap = new Map<string, Set<string>>();\n const entries = Array.from(byIssue.values());\n for (let i = 0; i < entries.length; i++) {\n for (let j = i + 1; j < entries.length; j++) {\n const left = entries[i]!;\n const right = entries[j]!;\n if (![...left.files].some(file => right.files.has(file))) continue;\n if (!conflictsMap.has(left.issueId)) conflictsMap.set(left.issueId, new Set());\n if (!conflictsMap.has(right.issueId)) conflictsMap.set(right.issueId, new Set());\n conflictsMap.get(left.issueId)!.add(right.issueId);\n conflictsMap.get(right.issueId)!.add(left.issueId);\n }\n }\n\n return entries.map(entry => {\n const conflictsWith = [...(conflictsMap.get(entry.issueId) ?? [])]\n .sort((a, b) => issueNumber(a) - issueNumber(b));\n return {\n issueId: entry.issueId,\n source: entry.source,\n footprint: entry.files.size,\n conflictCount: conflictsWith.length,\n conflictsWith,\n };\n });\n}\n\nexport interface UatCandidatePlan {\n /** Branch name for the on-demand UAT candidate (auto-merge-OFF mode). */\n branchName: string;\n /** Issue IDs bundled onto the candidate — the disjoint, mergeable-together batch. */\n bundled: string[];\n}\n\n/**\n * PAN-1691 on-demand UAT candidate. In auto-merge-OFF mode the disjoint \"batch\"\n * (everything that can merge together in one verification pass) is bundled onto\n * a single throwaway branch the human UATs in one sitting. Pure — `dateIso` is\n * injected, and it reads the already-computed `batchGroup` off the merge queue.\n */\nexport function planUatCandidate(\n queue: ReadonlyArray<MergeQueueItem>,\n opts: { dateIso: string; label?: string },\n): UatCandidatePlan {\n const bundled = queue.filter((i) => i.batchGroup === 'batch').map((i) => i.issueId);\n const day = opts.dateIso.slice(0, 10);\n return { branchName: `uat/${opts.label ?? 'candidate'}-${day}`, bundled };\n}\n\nconst branchExists = (branch: string, cwd: string) =>\n Effect.gen(function*() {\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const cmd = ChildProcess.make('git', ['rev-parse', '--verify', branch], { cwd });\n return yield* spawner.exitCode(cmd).pipe(\n Effect.map((code) => code === 0),\n Effect.orElseSucceed(() => false),\n );\n });\n\nexport const changedFilesVsMain = (branch: string, cwd: string, base = 'main') =>\n Effect.gen(function*() {\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const cmd = ChildProcess.make('git', ['diff', '--name-only', `${base}...${branch}`], { cwd });\n return yield* spawner.string(cmd).pipe(\n Effect.map((stdout) => new Set(stdout.trim().split('\\n').filter(Boolean))),\n Effect.orElseSucceed(() => new Set<string>()),\n );\n });\n\n/**\n * Verbs that mean \"at the merge gate\" (PAN-1736). Orchestrators have\n * legitimately emitted both 'shipping' and 'merging' for merge-ready issues;\n * filtering on one alone silently rendered an empty queue with five ready\n * items in RUN-18. Contract documented next to FlywheelPipelineVerb in\n * packages/contracts/src/flywheel.ts — extend BOTH places together.\n */\nexport const MERGE_GATE_VERBS: ReadonlySet<FlywheelPipelineItem['verb']> = new Set(['shipping', 'merging']);\n\nexport const MERGE_QUEUE_GIT_CONCURRENCY = 4;\n\nexport interface ComputeMergeQueueOptions {\n getPrUrl?: (item: FlywheelPipelineItem) => string | undefined;\n gitConcurrency?: number;\n /**\n * Authoritative merge eligibility per issue (PAN-1759). Defaults to the\n * review-status DB predicate; injectable for tests. The verb filter alone is\n * the orchestrator's INTENT — an LLM emission that has tagged mid-review\n * issues as merge-bound. Only verb ∩ eligibility enters the queue.\n */\n eligibility?: (issueId: string) => MergeGateEligibility;\n /** Called for each verb-tagged item the eligibility gate rejects. */\n onIneligible?: (issueId: string, reason: string) => void;\n /** Files treated as common hotspots and excluded from predicted-conflict math. */\n hotspots?: string[];\n}\n\n/** Default eligibility: the issue's review-status record, read synchronously. */\nexport function reviewRecordEligibility(issueId: string): MergeGateEligibility {\n return mergeGateEligibility(getReviewStatusSync(issueId.toUpperCase()));\n}\n\n/**\n * Server-side PR URL resolution for merge-queue items: prefer the review\n * status record, fall back to the GitHub repo + PR number — the browser never\n * guesses repo slugs.\n */\nexport function resolveMergeQueuePrUrl(item: { issueId: string; pr?: number }): string | undefined {\n const issueId = item.issueId.toUpperCase();\n const reviewStatus = getReviewStatusSync(issueId);\n if (reviewStatus?.prUrl) return reviewStatus.prUrl;\n\n const prNumber = reviewStatus?.prNumber ?? item.pr;\n if (prNumber === undefined) return undefined;\n\n const githubIssue = resolveGitHubIssueSync(issueId);\n if (!githubIssue.isGitHub) return undefined;\n return `https://github.com/${githubIssue.owner}/${githubIssue.repo}/pull/${prNumber}`;\n}\n\nexport const computeMergeQueue = (\n items: ReadonlyArray<FlywheelPipelineItem>,\n projectRoot: string,\n options: ComputeMergeQueueOptions = {},\n) =>\n Effect.gen(function*() {\n const eligibility = options.eligibility ?? reviewRecordEligibility;\n const candidates = items.filter((item) => {\n if (!MERGE_GATE_VERBS.has(item.verb)) return false;\n const gate = eligibility(item.issueId);\n if (!gate.eligible) {\n options.onIneligible?.(item.issueId, gate.reason ?? 'not eligible');\n return false;\n }\n return true;\n });\n if (candidates.length === 0) return [] as MergeQueueItem[];\n const gitConcurrency = Math.max(1, Math.floor(options.gitConcurrency ?? MERGE_QUEUE_GIT_CONCURRENCY));\n\n const branches = candidates.map((item) => `feature/${item.issueId.toLowerCase()}`);\n\n const existsFlags = yield* Effect.all(\n branches.map((branch) => branchExists(branch, projectRoot)),\n { concurrency: gitConcurrency },\n );\n\n const existing = candidates\n .map((item, i) => ({ item, branch: branches[i]!, exists: existsFlags[i]! }))\n .filter(({ exists }) => exists);\n\n if (existing.length === 0) return [] as MergeQueueItem[];\n\n const fileSets = yield* Effect.all(\n existing.map(({ branch }) => changedFilesVsMain(branch, projectRoot)),\n { concurrency: gitConcurrency },\n );\n const hotspots = options.hotspots ?? getProjectSwarmHotspots(findProjectByPathSync(projectRoot));\n const conflictSignals = computePredictedConflictSignals(\n existing.map((e, i) => ({ issueId: e.item.issueId, source: 'actual' as const, files: fileSets[i]! })),\n { hotspots },\n );\n const signalByIssue = new Map(conflictSignals.map(signal => [signal.issueId, signal]));\n const conflictsMap = new Map(conflictSignals.map(signal => [signal.issueId, new Set(signal.conflictsWith)]));\n\n // PAN-1691: conflict-aware order. Disjoint (no-conflict) items go first so\n // they can batch through in a single verification pass; then conflicting\n // items broadest-file-footprint first, so the remaining cluster members\n // rebase once onto the worst offender instead of repeatedly. Issue number\n // is the stable tiebreak within each tier.\n const sorted = orderMergeCandidates(\n existing.map((e, i) => ({\n item: e.item,\n issueId: e.item.issueId,\n footprint: signalByIssue.get(e.item.issueId)?.footprint ?? fileSets[i]!.size,\n conflictCount: signalByIssue.get(e.item.issueId)?.conflictCount ?? 0,\n })),\n );\n\n return sorted.map(({ item, conflictCount }, idx) => ({\n issueId: item.issueId,\n title: item.title,\n branchName: `feature/${item.issueId.toLowerCase()}`,\n pr: item.pr,\n prUrl: options.getPrUrl?.(item),\n mergeOrder: idx + 1,\n conflictsWith: [...(conflictsMap.get(item.issueId) ?? [])],\n batchGroup: (conflictCount === 0 ? 'batch' : 'serialize') as 'batch' | 'serialize',\n }));\n });\n\nexport interface SequencePickResult {\n issueId: string;\n rank: number;\n gate: string;\n planning: string;\n predictedConflictCount?: number;\n predictedConflictsWith?: string[];\n}\n\n\n/**\n * PAN-1866: Pick the highest-ranked eligible issue from a sequence node list.\n *\n * Eligibility rules:\n * - gate must not be 'blocked' (the `vetoed` pickup state)\n * - no `vetoed` label — an absolute operator hard-stop (PAN-2006)\n * - not in-pipeline (active review/work/test)\n * - no parked labels (`parked`; legacy `needs-design`/`needs-discussion`)\n * - not in the optional exclusion set (e.g. already running agents)\n * - FR-14: must have a vBRIEF spec (ready) or a PRD draft (hasPrd)\n *\n * Returns null when no eligible issue is found.\n */\nexport function pickFromSequence(\n nodes: ReadonlyArray<SequenceNode>,\n opts?: {\n excludeIssueIds?: ReadonlySet<string>;\n issueLabels?: (issueId: string) => ReadonlyArray<string>;\n /** Flywheel author/assignee safety gate. Return false to skip an issue. When\n * absent every issue passes (backward-compatible default). */\n isAuthorizedIssue?: (issueId: string) => boolean;\n /** FR-14 eligibility gate. Return true if the issue has a vBRIEF spec (ready)\n * or a PRD draft (hasPrd). When absent every issue passes (backward-compatible\n * default). */\n isReadyOrHasPrd?: (issueId: string) => boolean;\n /** Supplement the built-in review-status inPipeline check with live workspace/agent\n * state. Return true to treat an issue as in-pipeline and skip it. When absent only\n * review_status is checked (backward-compatible default). */\n isInPipeline?: (issueId: string) => boolean;\n /** PAN-2006 Definition of Ready: when true, only issues carrying the `ready`\n * label are eligible (the hard entry gate). The live Flywheel passes true;\n * legacy callers omit it and keep their pre-DoR behavior. */\n requireReady?: boolean;\n /** PAN-2059 + vision.mdx blanket release: when auto-pickup is ON the toggle\n * satisfies the per-issue `released` gate for the whole backlog. The live Flywheel\n * passes its auto_pickup_backlog setting; legacy callers omit it (default OFF). */\n autoPickupBacklog?: boolean;\n /**\n * Advisory pre-branch conflict signal. When present, lower predicted-conflict\n * counts sort first among otherwise pickable issues; no issue is filtered out.\n */\n predictedConflictSignals?: ReadonlyArray<PredictedConflictSignal>;\n },\n): SequencePickResult | null {\n // Single source of truth: the same classifier the Forecast UI uses (PAN-2006).\n // `isReadyOrHasPrd` maps to the module's `planned` gate; review_status + the\n // optional callback feed the `inPipeline` gate; vetoed / parked / gate-blocked are\n // derived from labels + the node's gate inside classifyIssue.\n const lookups: ClassifyLookups = {\n labels: opts?.issueLabels ?? (() => []),\n isPlanned: opts?.isReadyOrHasPrd ?? (() => true),\n isInPipeline: (issueId) => {\n const reviewStatus = getReviewStatusSync(issueId.toUpperCase());\n return (reviewStatus !== null && reviewStatus.reviewStatus !== 'pending') ||\n (opts?.isInPipeline?.(issueId) ?? false);\n },\n };\n\n const signalByIssue = new Map((opts?.predictedConflictSignals ?? []).map(signal => [signal.issueId.toUpperCase(), signal]));\n const eligible = [...nodes]\n .sort((a, b) => a.rank - b.rank)\n .filter((node) => {\n const state = classifyIssue(node, lookups);\n // DoR is conditional: when not required, treat readiness as satisfied so the\n // remaining gates (planned / parked / vetoed / in-pipeline) still apply.\n if (!isAutoPickable(opts?.requireReady ? state : { ...state, ready: true }, opts?.autoPickupBacklog ?? false)) return false;\n if (opts?.excludeIssueIds?.has(node.issue)) return false;\n if (opts?.isAuthorizedIssue && !opts.isAuthorizedIssue(node.issue)) return false;\n return true;\n });\n\n if (signalByIssue.size > 0) {\n eligible.sort((a, b) => {\n const aSignal = signalByIssue.get(a.issue.toUpperCase());\n const bSignal = signalByIssue.get(b.issue.toUpperCase());\n const aConflicts = aSignal?.conflictCount ?? 0;\n const bConflicts = bSignal?.conflictCount ?? 0;\n if (aConflicts !== bConflicts) return aConflicts - bConflicts;\n return a.rank - b.rank;\n });\n }\n\n for (const node of eligible) {\n const state = classifyIssue(node, lookups);\n const signal = signalByIssue.get(node.issue.toUpperCase());\n return {\n issueId: node.issue,\n rank: node.rank,\n gate: node.gate,\n planning: node.planning,\n predictedConflictCount: signal?.conflictCount,\n predictedConflictsWith: signal?.conflictsWith,\n };\n }\n return null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flywheel-merge-order-B7BH8mZ3.js","names":[],"sources":["../../src/lib/flywheel-merge-order.ts"],"sourcesContent":["import { Effect } from 'effect';\nimport { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';\nimport type { FlywheelPipelineItem } from '@overdeck/contracts';\nimport { getReviewStatusSync, mergeGateEligibility, type MergeGateEligibility } from './review-status.js';\nimport { resolveGitHubIssueSync } from './tracker-utils.js';\nimport type { SequenceNode } from './backlog/types.js';\nimport { classifyIssue, isAutoPickable, type ClassifyLookups } from './backlog/pickup.js';\nimport { compileGlob, type CompiledGlob } from './vbrief/dag.js';\nimport { computeIssueFootprint } from './vbrief/swarm-readiness.js';\nimport type { VBriefDocument } from './vbrief/types.js';\nimport { findProjectByPathSync, getProjectSwarmHotspots } from './projects.js';\n\nexport interface MergeQueueItem {\n issueId: string;\n title: string;\n branchName: string;\n pr?: number;\n prUrl?: string;\n mergeOrder: number;\n conflictsWith: string[];\n /** PAN-1691: 'batch' = disjoint, mergeable together in one pass; 'serialize' = conflicts, must go one at a time. */\n batchGroup?: 'batch' | 'serialize';\n}\n\nfunction issueNumber(issueId: string): number {\n const match = issueId.match(/\\d+$/);\n return match ? parseInt(match[0], 10) : 0;\n}\n\nexport interface MergeCandidateMeta {\n issueId: string;\n /** Number of files this branch changes vs main. */\n footprint: number;\n /** How many other ready branches this one overlaps files with. */\n conflictCount: number;\n}\n\nexport type FootprintSource = 'declared' | 'actual';\n\nexport interface IssueFileFootprint {\n issueId: string;\n files: Iterable<string>;\n source: FootprintSource;\n}\n\nexport interface PredictedConflictSignal extends MergeCandidateMeta {\n source: FootprintSource;\n conflictsWith: string[];\n}\n\n/**\n * PAN-1691 conflict-aware merge order. Disjoint (no-conflict) items come first\n * so they can batch through in a single verification pass; then conflicting\n * items broadest-file-footprint first, so the remaining cluster members rebase\n * once onto the worst offender instead of repeatedly. Issue number is the\n * stable tiebreak within each tier. Pure — exported for testing.\n */\nexport function orderMergeCandidates<T extends MergeCandidateMeta>(items: ReadonlyArray<T>): T[] {\n return [...items].sort((a, b) => {\n const aConf = a.conflictCount > 0 ? 1 : 0;\n const bConf = b.conflictCount > 0 ? 1 : 0;\n if (aConf !== bConf) return aConf - bConf;\n if (aConf === 1 && a.footprint !== b.footprint) return b.footprint - a.footprint;\n return issueNumber(a.issueId) - issueNumber(b.issueId);\n });\n}\n\nexport interface MergeTrainPlan {\n /** Disjoint candidates that can merge together in one verification pass. */\n batch: string[];\n /** Conflicting candidates that must serialize, broadest-footprint first. */\n serialize: string[];\n /** Full ordered list (batch, then serialize). */\n order: string[];\n}\n\n/**\n * PAN-1691 merge-train plan. Partitions the conflict-aware order into the run of\n * disjoint candidates — which can all merge in a single verification pass — and\n * the conflicting remainder, which must serialize broadest-footprint first.\n * Pure; the executor consumes this once the merge-train flag is enabled.\n */\nexport function planMergeTrain<T extends MergeCandidateMeta>(candidates: ReadonlyArray<T>): MergeTrainPlan {\n const ordered = orderMergeCandidates(candidates);\n return {\n batch: ordered.filter((c) => c.conflictCount === 0).map((c) => c.issueId),\n serialize: ordered.filter((c) => c.conflictCount > 0).map((c) => c.issueId),\n order: ordered.map((c) => c.issueId),\n };\n}\n\nexport function declaredIssueFootprint(issueId: string, doc: VBriefDocument): IssueFileFootprint {\n return { issueId, files: computeIssueFootprint(doc), source: 'declared' };\n}\n\nfunction pathMatchesAnyCompiled(filePath: string, patterns: CompiledGlob[]): boolean {\n return patterns.some(pattern => pattern.regex.test(filePath) || pattern.exactDirectory === filePath);\n}\n\nfunction normalizedFootprintFiles(files: Iterable<string>, hotspots: CompiledGlob[]): Set<string> {\n const normalized = new Set<string>();\n for (const file of files) {\n if (!file || pathMatchesAnyCompiled(file, hotspots)) continue;\n normalized.add(file);\n }\n return normalized;\n}\n\nfunction sourcePriority(source: FootprintSource): number {\n return source === 'actual' ? 2 : 1;\n}\n\nexport function computePredictedConflictSignals(\n footprints: ReadonlyArray<IssueFileFootprint>,\n options: { hotspots?: string[] } = {},\n): PredictedConflictSignal[] {\n const hotspots = (options.hotspots ?? []).map(compileGlob);\n const byIssue = new Map<string, { issueId: string; source: FootprintSource; files: Set<string> }>();\n\n for (const footprint of footprints) {\n const files = normalizedFootprintFiles(footprint.files, hotspots);\n const current = byIssue.get(footprint.issueId);\n if (!current || sourcePriority(footprint.source) > sourcePriority(current.source)) {\n byIssue.set(footprint.issueId, { issueId: footprint.issueId, source: footprint.source, files });\n continue;\n }\n if (sourcePriority(footprint.source) === sourcePriority(current.source)) {\n for (const file of files) current.files.add(file);\n }\n }\n\n const conflictsMap = new Map<string, Set<string>>();\n const entries = Array.from(byIssue.values());\n for (let i = 0; i < entries.length; i++) {\n for (let j = i + 1; j < entries.length; j++) {\n const left = entries[i]!;\n const right = entries[j]!;\n if (![...left.files].some(file => right.files.has(file))) continue;\n if (!conflictsMap.has(left.issueId)) conflictsMap.set(left.issueId, new Set());\n if (!conflictsMap.has(right.issueId)) conflictsMap.set(right.issueId, new Set());\n conflictsMap.get(left.issueId)!.add(right.issueId);\n conflictsMap.get(right.issueId)!.add(left.issueId);\n }\n }\n\n return entries.map(entry => {\n const conflictsWith = [...(conflictsMap.get(entry.issueId) ?? [])]\n .sort((a, b) => issueNumber(a) - issueNumber(b));\n return {\n issueId: entry.issueId,\n source: entry.source,\n footprint: entry.files.size,\n conflictCount: conflictsWith.length,\n conflictsWith,\n };\n });\n}\n\nexport interface UatCandidatePlan {\n /** Branch name for the on-demand UAT candidate (auto-merge-OFF mode). */\n branchName: string;\n /** Issue IDs bundled onto the candidate — the disjoint, mergeable-together batch. */\n bundled: string[];\n}\n\n/**\n * PAN-1691 on-demand UAT candidate. In auto-merge-OFF mode the disjoint \"batch\"\n * (everything that can merge together in one verification pass) is bundled onto\n * a single throwaway branch the human UATs in one sitting. Pure — `dateIso` is\n * injected, and it reads the already-computed `batchGroup` off the merge queue.\n */\nexport function planUatCandidate(\n queue: ReadonlyArray<MergeQueueItem>,\n opts: { dateIso: string; label?: string },\n): UatCandidatePlan {\n const bundled = queue.filter((i) => i.batchGroup === 'batch').map((i) => i.issueId);\n const day = opts.dateIso.slice(0, 10);\n return { branchName: `uat/${opts.label ?? 'candidate'}-${day}`, bundled };\n}\n\nconst branchExists = (branch: string, cwd: string) =>\n Effect.gen(function*() {\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const cmd = ChildProcess.make('git', ['rev-parse', '--verify', branch], { cwd });\n return yield* spawner.exitCode(cmd).pipe(\n Effect.map((code) => code === 0),\n Effect.orElseSucceed(() => false),\n );\n });\n\nexport const changedFilesVsMain = (branch: string, cwd: string, base = 'main') =>\n Effect.gen(function*() {\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const cmd = ChildProcess.make('git', ['diff', '--name-only', `${base}...${branch}`], { cwd });\n return yield* spawner.string(cmd).pipe(\n Effect.map((stdout) => new Set(stdout.trim().split('\\n').filter(Boolean))),\n Effect.orElseSucceed(() => new Set<string>()),\n );\n });\n\n/**\n * Verbs that mean \"at the merge gate\" (PAN-1736). Orchestrators have\n * legitimately emitted both 'shipping' and 'merging' for merge-ready issues;\n * filtering on one alone silently rendered an empty queue with five ready\n * items in RUN-18. Contract documented next to FlywheelPipelineVerb in\n * packages/contracts/src/flywheel.ts — extend BOTH places together.\n */\nexport const MERGE_GATE_VERBS: ReadonlySet<FlywheelPipelineItem['verb']> = new Set(['shipping', 'merging']);\n\nexport const MERGE_QUEUE_GIT_CONCURRENCY = 4;\n\nexport interface ComputeMergeQueueOptions {\n getPrUrl?: (item: FlywheelPipelineItem) => string | undefined;\n gitConcurrency?: number;\n /**\n * Authoritative merge eligibility per issue (PAN-1759). Defaults to the\n * review-status DB predicate; injectable for tests. The verb filter alone is\n * the orchestrator's INTENT — an LLM emission that has tagged mid-review\n * issues as merge-bound. Only verb ∩ eligibility enters the queue.\n */\n eligibility?: (issueId: string) => MergeGateEligibility;\n /** Called for each verb-tagged item the eligibility gate rejects. */\n onIneligible?: (issueId: string, reason: string) => void;\n /** Files treated as common hotspots and excluded from predicted-conflict math. */\n hotspots?: string[];\n}\n\n/** Default eligibility: the issue's review-status record, read synchronously. */\nexport function reviewRecordEligibility(issueId: string): MergeGateEligibility {\n return mergeGateEligibility(getReviewStatusSync(issueId.toUpperCase()));\n}\n\n/**\n * Server-side PR URL resolution for merge-queue items: prefer the review\n * status record, fall back to the GitHub repo + PR number — the browser never\n * guesses repo slugs.\n */\nexport function resolveMergeQueuePrUrl(item: { issueId: string; pr?: number }): string | undefined {\n const issueId = item.issueId.toUpperCase();\n const reviewStatus = getReviewStatusSync(issueId);\n if (reviewStatus?.prUrl) return reviewStatus.prUrl;\n\n const prNumber = reviewStatus?.prNumber ?? item.pr;\n if (prNumber === undefined) return undefined;\n\n const githubIssue = resolveGitHubIssueSync(issueId);\n if (!githubIssue.isGitHub) return undefined;\n return `https://github.com/${githubIssue.owner}/${githubIssue.repo}/pull/${prNumber}`;\n}\n\nexport const computeMergeQueue = (\n items: ReadonlyArray<FlywheelPipelineItem>,\n projectRoot: string,\n options: ComputeMergeQueueOptions = {},\n) =>\n Effect.gen(function*() {\n const eligibility = options.eligibility ?? reviewRecordEligibility;\n const candidates = items.filter((item) => {\n if (!MERGE_GATE_VERBS.has(item.verb)) return false;\n const gate = eligibility(item.issueId);\n if (!gate.eligible) {\n options.onIneligible?.(item.issueId, gate.reason ?? 'not eligible');\n return false;\n }\n return true;\n });\n if (candidates.length === 0) return [] as MergeQueueItem[];\n const gitConcurrency = Math.max(1, Math.floor(options.gitConcurrency ?? MERGE_QUEUE_GIT_CONCURRENCY));\n\n const branches = candidates.map((item) => `feature/${item.issueId.toLowerCase()}`);\n\n const existsFlags = yield* Effect.all(\n branches.map((branch) => branchExists(branch, projectRoot)),\n { concurrency: gitConcurrency },\n );\n\n const existing = candidates\n .map((item, i) => ({ item, branch: branches[i]!, exists: existsFlags[i]! }))\n .filter(({ exists }) => exists);\n\n if (existing.length === 0) return [] as MergeQueueItem[];\n\n const fileSets = yield* Effect.all(\n existing.map(({ branch }) => changedFilesVsMain(branch, projectRoot)),\n { concurrency: gitConcurrency },\n );\n const hotspots = options.hotspots ?? getProjectSwarmHotspots(findProjectByPathSync(projectRoot));\n const conflictSignals = computePredictedConflictSignals(\n existing.map((e, i) => ({ issueId: e.item.issueId, source: 'actual' as const, files: fileSets[i]! })),\n { hotspots },\n );\n const signalByIssue = new Map(conflictSignals.map(signal => [signal.issueId, signal]));\n const conflictsMap = new Map(conflictSignals.map(signal => [signal.issueId, new Set(signal.conflictsWith)]));\n\n // PAN-1691: conflict-aware order. Disjoint (no-conflict) items go first so\n // they can batch through in a single verification pass; then conflicting\n // items broadest-file-footprint first, so the remaining cluster members\n // rebase once onto the worst offender instead of repeatedly. Issue number\n // is the stable tiebreak within each tier.\n const sorted = orderMergeCandidates(\n existing.map((e, i) => ({\n item: e.item,\n issueId: e.item.issueId,\n footprint: signalByIssue.get(e.item.issueId)?.footprint ?? fileSets[i]!.size,\n conflictCount: signalByIssue.get(e.item.issueId)?.conflictCount ?? 0,\n })),\n );\n\n return sorted.map(({ item, conflictCount }, idx) => ({\n issueId: item.issueId,\n title: item.title,\n branchName: `feature/${item.issueId.toLowerCase()}`,\n pr: item.pr,\n prUrl: options.getPrUrl?.(item),\n mergeOrder: idx + 1,\n conflictsWith: [...(conflictsMap.get(item.issueId) ?? [])],\n batchGroup: (conflictCount === 0 ? 'batch' : 'serialize') as 'batch' | 'serialize',\n }));\n });\n\nexport interface SequencePickResult {\n issueId: string;\n rank: number;\n gate: string;\n planning: string;\n predictedConflictCount?: number;\n predictedConflictsWith?: string[];\n}\n\n\n/**\n * PAN-1866: Pick the highest-ranked eligible issue from a sequence node list.\n *\n * Eligibility rules:\n * - gate must not be 'blocked' (the `vetoed` pickup state)\n * - no `vetoed` label — an absolute operator hard-stop (PAN-2006)\n * - not in-pipeline (active review/work/test)\n * - no parked labels (`parked`; legacy `needs-design`/`needs-discussion`)\n * - not in the optional exclusion set (e.g. already running agents)\n * - FR-14: must have a vBRIEF spec (ready) or a PRD draft (hasPrd)\n *\n * Returns null when no eligible issue is found.\n */\nexport function pickFromSequence(\n nodes: ReadonlyArray<SequenceNode>,\n opts?: {\n excludeIssueIds?: ReadonlySet<string>;\n issueLabels?: (issueId: string) => ReadonlyArray<string>;\n /** Flywheel author/assignee safety gate. Return false to skip an issue. When\n * absent every issue passes (backward-compatible default). */\n isAuthorizedIssue?: (issueId: string) => boolean;\n /** FR-14 eligibility gate. Return true if the issue has a vBRIEF spec (ready)\n * or a PRD draft (hasPrd). When absent every issue passes (backward-compatible\n * default). */\n isReadyOrHasPrd?: (issueId: string) => boolean;\n /** Supplement the built-in review-status inPipeline check with live workspace/agent\n * state. Return true to treat an issue as in-pipeline and skip it. When absent only\n * review_status is checked (backward-compatible default). */\n isInPipeline?: (issueId: string) => boolean;\n /** PAN-2006 Definition of Ready: when true, only issues carrying the `ready`\n * label are eligible (the hard entry gate). The live Flywheel passes true;\n * legacy callers omit it and keep their pre-DoR behavior. */\n requireReady?: boolean;\n /** PAN-2059 + vision.mdx blanket release: when auto-pickup is ON the toggle\n * satisfies the per-issue `released` gate for the whole backlog. The live Flywheel\n * passes its auto_pickup_backlog setting; legacy callers omit it (default OFF). */\n autoPickupBacklog?: boolean;\n /**\n * Advisory pre-branch conflict signal. When present, lower predicted-conflict\n * counts sort first among otherwise pickable issues; no issue is filtered out.\n */\n predictedConflictSignals?: ReadonlyArray<PredictedConflictSignal>;\n },\n): SequencePickResult | null {\n // Single source of truth: the same classifier the Forecast UI uses (PAN-2006).\n // `isReadyOrHasPrd` maps to the module's `planned` gate; review_status + the\n // optional callback feed the `inPipeline` gate; vetoed / parked / gate-blocked are\n // derived from labels + the node's gate inside classifyIssue.\n const lookups: ClassifyLookups = {\n labels: opts?.issueLabels ?? (() => []),\n isPlanned: opts?.isReadyOrHasPrd ?? (() => true),\n isInPipeline: (issueId) => {\n const reviewStatus = getReviewStatusSync(issueId.toUpperCase());\n return (reviewStatus !== null && reviewStatus.reviewStatus !== 'pending') ||\n (opts?.isInPipeline?.(issueId) ?? false);\n },\n };\n\n const signalByIssue = new Map((opts?.predictedConflictSignals ?? []).map(signal => [signal.issueId.toUpperCase(), signal]));\n const eligible = [...nodes]\n .sort((a, b) => a.rank - b.rank)\n .filter((node) => {\n const state = classifyIssue(node, lookups);\n // DoR is conditional: when not required, treat readiness as satisfied so the\n // remaining gates (planned / parked / vetoed / in-pipeline) still apply.\n if (!isAutoPickable(opts?.requireReady ? state : { ...state, ready: true }, opts?.autoPickupBacklog ?? false)) return false;\n if (opts?.excludeIssueIds?.has(node.issue)) return false;\n if (opts?.isAuthorizedIssue && !opts.isAuthorizedIssue(node.issue)) return false;\n return true;\n });\n\n if (signalByIssue.size > 0) {\n eligible.sort((a, b) => {\n const aSignal = signalByIssue.get(a.issue.toUpperCase());\n const bSignal = signalByIssue.get(b.issue.toUpperCase());\n const aConflicts = aSignal?.conflictCount ?? 0;\n const bConflicts = bSignal?.conflictCount ?? 0;\n if (aConflicts !== bConflicts) return aConflicts - bConflicts;\n return a.rank - b.rank;\n });\n }\n\n for (const node of eligible) {\n const state = classifyIssue(node, lookups);\n const signal = signalByIssue.get(node.issue.toUpperCase());\n return {\n issueId: node.issue,\n rank: node.rank,\n gate: node.gate,\n planning: node.planning,\n predictedConflictCount: signal?.conflictCount,\n predictedConflictsWith: signal?.conflictsWith,\n };\n }\n return null;\n}\n"],"mappings":"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"}
|
package/dist/dashboard/{flywheel-merge-order-B6YDSElh.js → flywheel-merge-order-CCwF60b-.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { c as resolveMergeQueuePrUrl, l as reviewRecordEligibility, r as computeMergeQueue } from "./flywheel-merge-order-
|
|
1
|
+
import { c as resolveMergeQueuePrUrl, l as reviewRecordEligibility, r as computeMergeQueue } from "./flywheel-merge-order-B7BH8mZ3.js";
|
|
2
2
|
export { computeMergeQueue, resolveMergeQueuePrUrl, reviewRecordEligibility };
|
|
@@ -4,7 +4,7 @@ import { exec } from "child_process";
|
|
|
4
4
|
import { promisify } from "util";
|
|
5
5
|
//#region ../../lib/harness-resolve.ts
|
|
6
6
|
async function getProviderAuthModeForModel(model) {
|
|
7
|
-
const { getProviderAuthMode } = await import("./agents-
|
|
7
|
+
const { getProviderAuthMode } = await import("./agents-C-vqmKDi.js");
|
|
8
8
|
return getProviderAuthMode(model);
|
|
9
9
|
}
|
|
10
10
|
async function hasHarnessBinary(harness) {
|
|
@@ -69,4 +69,4 @@ var init_harness_resolve = __esmMin((() => {
|
|
|
69
69
|
//#endregion
|
|
70
70
|
export { resolveHarness as n, init_harness_resolve as t };
|
|
71
71
|
|
|
72
|
-
//# sourceMappingURL=harness-resolve-
|
|
72
|
+
//# sourceMappingURL=harness-resolve-B3f9G1Kp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-resolve-
|
|
1
|
+
{"version":3,"file":"harness-resolve-B3f9G1Kp.js","names":["loadYamlConfig"],"sources":["../../src/lib/harness-resolve.ts"],"sourcesContent":["import { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { canUseHarnessSync, canUseModelWithAuthSync } from './harness-policy.js';\nimport { getBuiltInDefaultHarness, getProviderForModelSync } from './providers.js';\nimport type { RuntimeName } from './runtimes/types.js';\nimport type { Role } from './agents.js';\nimport { loadConfigSync as loadYamlConfig } from './config-yaml.js';\n\nconst execAsync = promisify(exec);\nconst BINARY_BY_HARNESS: Partial<Record<RuntimeName, string>> = {\n ohmypi: 'omp',\n codex: 'codex',\n};\nconst harnessAvailabilityCache = new Map<RuntimeName, Promise<boolean>>();\nconst builtInDefaultNoticeProviders = new Set<string>();\n\nexport function resetHarnessResolveCachesForTests(): void {\n if (process.env.NODE_ENV !== 'test') return;\n harnessAvailabilityCache.clear();\n builtInDefaultNoticeProviders.clear();\n}\n\nexport type ResolveHarnessInput = {\n explicit?: RuntimeName;\n role?: Role;\n model: string;\n};\n\nexport class HarnessResolutionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'HarnessResolutionError';\n }\n}\n\nasync function getProviderAuthModeForModel(model: string) {\n const { getProviderAuthMode } = await import('./agents.js');\n return getProviderAuthMode(model);\n}\n\nasync function hasHarnessBinary(harness: RuntimeName): Promise<boolean> {\n const binary = BINARY_BY_HARNESS[harness];\n if (!binary) return true;\n\n const cached = harnessAvailabilityCache.get(harness);\n if (cached) return cached;\n\n const check = execAsync(`command -v ${binary}`).then(\n () => true,\n () => false\n );\n harnessAvailabilityCache.set(harness, check);\n return check;\n}\n\nfunction logBuiltInDefaultNotice(provider: string, harness: RuntimeName): void {\n if (builtInDefaultNoticeProviders.has(provider)) return;\n builtInDefaultNoticeProviders.add(provider);\n console.info(`harness ${harness} chosen by provider default — override in Settings → Providers`);\n}\n\nexport async function resolveHarness(input: ResolveHarnessInput): Promise<RuntimeName> {\n const provider = getProviderForModelSync(input.model).name;\n const { config } = loadYamlConfig();\n // Harness is PROVIDER-DEFAULT-ONLY (PAN-1984). It is derived solely from the model's\n // provider — the per-provider default (Settings → Providers) else the built-in default.\n // We no longer honor a per-spawn explicit override or a per-role harness:\n // `input.explicit` / `input.role` are accepted for signature compatibility but\n // INTENTIONALLY IGNORED. This removes the entire class of harness↔model mismatch bugs\n // (e.g. Pi+GPT-5.5 selected when Codex is GPT-5.5's real provider default) and the\n // stale-harness-on-resume bug — a provider/config change now always flows through\n // instead of a frozen state.json harness winning forever.\n const providerHarness = config.providerHarnesses?.[provider];\n const builtInHarness = getBuiltInDefaultHarness(provider);\n\n const winner = providerHarness ?? builtInHarness ?? 'claude-code';\n\n if (!providerHarness) {\n logBuiltInDefaultNotice(provider, winner);\n }\n\n const authMode = await getProviderAuthModeForModel(input.model);\n const modelDecision = canUseModelWithAuthSync(input.model, authMode);\n if (!modelDecision.allowed) {\n throw new HarnessResolutionError(modelDecision.reason ?? `Model ${input.model} is not allowed with the current auth mode`);\n }\n\n const decision = canUseHarnessSync(winner, input.model, authMode);\n if (!decision.allowed) {\n // PAN-1871 — only fall back to claude-code when it is the model's NATIVE\n // harness (Anthropic). For CLIProxy-routed models (kimi, gpt-5.5, …) the\n // provider default is pi/codex; claude-code would route them through\n // CLIProxy and hit the 200k-window-illusion deadlock. Silently degrading\n // there is worse than failing, so refuse it loudly.\n if (builtInHarness && builtInHarness !== 'claude-code') {\n throw new HarnessResolutionError(\n `Harness ${winner} denied for ${input.model} (${decision.reason ?? 'policy denied'}); ${input.model} is not native to claude-code (provider default is ${builtInHarness}), so refusing to silently fall back to claude-code/CLIProxy. Resolve ${winner} availability/auth and retry.`,\n );\n }\n\n const fallbackDecision = canUseHarnessSync('claude-code', input.model, authMode);\n if (!fallbackDecision.allowed) {\n throw new HarnessResolutionError(decision.reason ?? fallbackDecision.reason ?? `Harness ${winner} is not allowed for ${input.model}`);\n }\n\n console.warn(`harness ${winner} denied for ${provider}: ${decision.reason ?? 'policy denied'} — falling back to native claude-code`);\n return 'claude-code';\n }\n\n if (!(await hasHarnessBinary(winner))) {\n const binary = BINARY_BY_HARNESS[winner];\n // PAN-1871 — never silently fall back to claude-code from a non-native\n // (CLIProxy) model whose own binary is missing at spawn. Silently routing\n // kimi onto claude-code is what leaked PAN-1845. Fail loudly so the cause is\n // visible and recoverable.\n if (builtInHarness && builtInHarness !== 'claude-code') {\n throw new HarnessResolutionError(\n `Harness ${winner} (provider default for ${input.model}) has no installed ${binary} binary at spawn — refusing to silently fall back to claude-code. Install ${binary} (check its PATH) and retry.`,\n );\n }\n console.warn(`harness ${winner} requested for ${provider}, but ${binary} is not installed — falling back to native claude-code`);\n return 'claude-code';\n }\n\n return winner;\n}\n"],"mappings":";;;;;AAmCA,eAAe,4BAA4B,OAAe;CACxD,MAAM,EAAE,wBAAwB,MAAM,OAAO;AAC7C,QAAO,oBAAoB,MAAM;;AAGnC,eAAe,iBAAiB,SAAwC;CACtE,MAAM,SAAS,kBAAkB;AACjC,KAAI,CAAC,OAAQ,QAAO;CAEpB,MAAM,SAAS,yBAAyB,IAAI,QAAQ;AACpD,KAAI,OAAQ,QAAO;CAEnB,MAAM,QAAQ,UAAU,cAAc,SAAS,CAAC,WACxC,YACA,MACP;AACD,0BAAyB,IAAI,SAAS,MAAM;AAC5C,QAAO;;AAGT,SAAS,wBAAwB,UAAkB,SAA4B;AAC7E,KAAI,8BAA8B,IAAI,SAAS,CAAE;AACjD,+BAA8B,IAAI,SAAS;AAC3C,SAAQ,KAAK,WAAW,QAAQ,gEAAgE;;AAGlG,eAAsB,eAAe,OAAkD;CACrF,MAAM,WAAW,wBAAwB,MAAM,MAAM,CAAC;CACtD,MAAM,EAAE,WAAWA,gBAAgB;CASnC,MAAM,kBAAkB,OAAO,oBAAoB;CACnD,MAAM,iBAAiB,yBAAyB,SAAS;CAEzD,MAAM,SAAS,mBAAmB,kBAAkB;AAEpD,KAAI,CAAC,gBACH,yBAAwB,UAAU,OAAO;CAG3C,MAAM,WAAW,MAAM,4BAA4B,MAAM,MAAM;CAC/D,MAAM,gBAAgB,wBAAwB,MAAM,OAAO,SAAS;AACpE,KAAI,CAAC,cAAc,QACjB,OAAM,IAAI,uBAAuB,cAAc,UAAU,SAAS,MAAM,MAAM,4CAA4C;CAG5H,MAAM,WAAW,kBAAkB,QAAQ,MAAM,OAAO,SAAS;AACjE,KAAI,CAAC,SAAS,SAAS;AAMrB,MAAI,kBAAkB,mBAAmB,cACvC,OAAM,IAAI,uBACR,WAAW,OAAO,cAAc,MAAM,MAAM,IAAI,SAAS,UAAU,gBAAgB,KAAK,MAAM,MAAM,qDAAqD,eAAe,wEAAwE,OAAO,+BACxP;EAGH,MAAM,mBAAmB,kBAAkB,eAAe,MAAM,OAAO,SAAS;AAChF,MAAI,CAAC,iBAAiB,QACpB,OAAM,IAAI,uBAAuB,SAAS,UAAU,iBAAiB,UAAU,WAAW,OAAO,sBAAsB,MAAM,QAAQ;AAGvI,UAAQ,KAAK,WAAW,OAAO,cAAc,SAAS,IAAI,SAAS,UAAU,gBAAgB,uCAAuC;AACpI,SAAO;;AAGT,KAAI,CAAE,MAAM,iBAAiB,OAAO,EAAG;EACrC,MAAM,SAAS,kBAAkB;AAKjC,MAAI,kBAAkB,mBAAmB,cACvC,OAAM,IAAI,uBACR,WAAW,OAAO,yBAAyB,MAAM,MAAM,qBAAqB,OAAO,4EAA4E,OAAO,8BACvK;AAEH,UAAQ,KAAK,WAAW,OAAO,iBAAiB,SAAS,QAAQ,OAAO,wDAAwD;AAChI,SAAO;;AAGT,QAAO;;;;sBA1HwE;iBACE;mBAGf;AAE9D,aAAY,UAAU,KAAK;AAC3B,qBAA0D;EAC9D,QAAQ;EACR,OAAO;EACR;AACK,4CAA2B,IAAI,KAAoC;AACnE,iDAAgC,IAAI,KAAa;AAc1C,0BAAb,cAA4C,MAAM;EAChD,YAAY,SAAiB;AAC3B,SAAM,QAAQ;AACd,QAAK,OAAO"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { C as resolveTieredExecutionEnabled, L as getProviderForModelSync, N as clearCredentialFileAuthSync, R as init_providers, S as init_tier_table, V as setupCredentialFileAuthSync, X as resolveModel, f as loadConfigSync, lr as GitError, mr as ProcessSpawnError, t as init_config_yaml, vr as init_errors } from "./config-yaml-DPAieL_G.js";
|
|
2
2
|
import { L as sessionExists, o as createSession, w as killSession, x as init_tmux } from "./tmux-ePNhMIMU.js";
|
|
3
|
-
import { c as init_review_status, y as setReviewStatusSync } from "./review-status-
|
|
4
|
-
import { c as init_tier_supervisor, d as spawnTierSupervisor, f as supervisorAgentId, gr as saveAgentRuntimeState, l as loadPrdDraft, ln as getProviderEnvForModel, qn as saveAgentState, s as deliverCommitForReview, t as init_agents } from "./agents-
|
|
3
|
+
import { c as init_review_status, y as setReviewStatusSync } from "./review-status-B5ZASOo4.js";
|
|
4
|
+
import { c as init_tier_supervisor, d as spawnTierSupervisor, f as supervisorAgentId, gr as saveAgentRuntimeState, l as loadPrdDraft, ln as getProviderEnvForModel, qn as saveAgentState, s as deliverCommitForReview, t as init_agents } from "./agents-C7V4odQ6.js";
|
|
5
5
|
import { m as readWorkspacePlanSync, o as init_io } from "./io-Wy8YpFeP.js";
|
|
6
6
|
import { a as init_launcher_generator, i as generateLauncherScriptSync } from "./state-home-Bgj7i-C7.js";
|
|
7
7
|
import { a as init_claude_permissions, i as getClaudePermissionFlagsSync } from "./claude-permissions-DrRAs_0F.js";
|
|
@@ -367,4 +367,4 @@ function onInspectComplete(projectKey, issueId, beadId, status, workspacePath) {
|
|
|
367
367
|
//#endregion
|
|
368
368
|
export { onInspectComplete as n, spawnInspectAgent as r, buildInspectPrompt as t };
|
|
369
369
|
|
|
370
|
-
//# sourceMappingURL=inspect-agent-
|
|
370
|
+
//# sourceMappingURL=inspect-agent-CKF5-SJr.js.map
|