@overdeck/core 0.50.3 → 0.50.4
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/agents-DNMRPC8n.js.map +1 -1
- package/dist/{build-info-BJuEVWOh.js → build-info-ioRdOmqT.js} +3 -3
- package/dist/{build-info-BJuEVWOh.js.map → build-info-ioRdOmqT.js.map} +1 -1
- package/dist/build-info-kMMitDOP.js +2 -0
- package/dist/cli/index.js +4 -4
- package/dist/conversation-compaction-GFfO5UZL.js.map +1 -1
- package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js → advancing-selfheal-Dp5_Gai3.js} +3 -3
- package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js.map → advancing-selfheal-Dp5_Gai3.js.map} +1 -1
- package/dist/dashboard/{agent-gc-Coz8zQt_.js → agent-gc-JzoSs5Pi.js} +3 -3
- package/dist/dashboard/{agent-gc-Coz8zQt_.js.map → agent-gc-JzoSs5Pi.js.map} +1 -1
- package/dist/dashboard/{agent-idle-CAwB-K6M.js → agent-idle-D-Qy5BN_.js} +2 -2
- package/dist/dashboard/{agent-idle-CAwB-K6M.js.map → agent-idle-D-Qy5BN_.js.map} +1 -1
- package/dist/dashboard/{agent-state-Ci6PhjOE.js → agent-state-B196fj1h.js} +38 -16
- package/dist/dashboard/agent-state-B196fj1h.js.map +1 -0
- package/dist/dashboard/{agent-state-CbUEwUEQ.js → agent-state-IpWnw8lv.js} +1 -1
- package/dist/dashboard/{agent-state-service-D6IVrX3B.js → agent-state-service-CCo5-9DR.js} +3 -3
- package/dist/dashboard/{agent-state-service-D6IVrX3B.js.map → agent-state-service-CCo5-9DR.js.map} +1 -1
- package/dist/dashboard/{agent-state-service-DcN1j6eK.js → agent-state-service-D_F3VZ23.js} +1 -1
- package/dist/dashboard/{agents-BVvL4o_4.js → agents-CkGhnAmu.js} +1 -1
- package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js → backlog-auto-trigger-B5Z2zBtC.js} +2 -2
- package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js.map → backlog-auto-trigger-B5Z2zBtC.js.map} +1 -1
- package/dist/dashboard/build-info-BTjOB0Xr.js +2 -0
- package/dist/dashboard/{build-info-Cj3Y0K87.js → build-info-D0EJNj6C.js} +3 -3
- package/dist/dashboard/{build-info-Cj3Y0K87.js.map → build-info-D0EJNj6C.js.map} +1 -1
- package/dist/dashboard/cli-CIznFrcE.js +2 -0
- package/dist/dashboard/{cli-DIhQieif.js → cli-DCbVrSsg.js} +2 -2
- package/dist/dashboard/{cli-DIhQieif.js.map → cli-DCbVrSsg.js.map} +1 -1
- package/dist/dashboard/{close-issue-CRlmrE5g.js → close-issue-Bmu9aOIx.js} +2 -2
- package/dist/dashboard/{close-issue-CRlmrE5g.js.map → close-issue-Bmu9aOIx.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js → closed-issue-reaper-COxfTtLz.js} +5 -5
- package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js.map → closed-issue-reaper-COxfTtLz.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DWlw2EtL.js → closed-issue-reaper-DwMwG_Pz.js} +1 -1
- package/dist/dashboard/{compaction-continuation-DhYgsXmF.js → compaction-continuation-Bqc212Zm.js} +2 -2
- package/dist/dashboard/{compaction-continuation-DhYgsXmF.js.map → compaction-continuation-Bqc212Zm.js.map} +1 -1
- package/dist/dashboard/{conflict-gate-vczgdVTa.js → conflict-gate-9mPVg7-5.js} +1 -1
- package/dist/dashboard/{conflict-gate-Bt-lLgYf.js → conflict-gate-CGQTcR0I.js} +3 -3
- package/dist/dashboard/{conflict-gate-Bt-lLgYf.js.map → conflict-gate-CGQTcR0I.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-CsdCbcQB.js → conversation-compaction-C7P6V8Iw.js} +5 -5
- package/dist/dashboard/{conversation-compaction-CsdCbcQB.js.map → conversation-compaction-C7P6V8Iw.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-D0zgWM1f.js → conversation-compaction-Cck5oi-V.js} +1 -1
- package/dist/dashboard/{conversation-service-6LaymUV-.js → conversation-service-CsOpwI9Y.js} +37 -5
- package/dist/dashboard/{conversation-service-6LaymUV-.js.map → conversation-service-CsOpwI9Y.js.map} +1 -1
- package/dist/dashboard/conversation-service-plOFkoMo.js +3 -0
- package/dist/dashboard/dashboard-db-worker.js +1 -1
- package/dist/dashboard/{deacon-CU47dU7U.js → deacon-B8oMMIgN.js} +62 -62
- package/dist/dashboard/{deacon-CU47dU7U.js.map → deacon-B8oMMIgN.js.map} +1 -1
- package/dist/dashboard/{deacon-DYsOa_ss.js → deacon-b7J57Svt.js} +2 -2
- package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js → deacon-pending-lifecycle-3NIJ7ew5.js} +2 -2
- package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js.map → deacon-pending-lifecycle-3NIJ7ew5.js.map} +1 -1
- package/dist/dashboard/deacon.js +5 -5
- package/dist/dashboard/{delivery-DMYxIE0J.js → delivery-lnI-gQKo.js} +1 -1
- package/dist/dashboard/{deploy-patrol-C4eXco1S.js → deploy-patrol-u3v2krg9.js} +2 -2
- package/dist/dashboard/{deploy-patrol-C4eXco1S.js.map → deploy-patrol-u3v2krg9.js.map} +1 -1
- package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js → feedback-stuck-retirement-CoFNqjCc.js} +2 -2
- package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js.map → feedback-stuck-retirement-CoFNqjCc.js.map} +1 -1
- package/dist/dashboard/{feedback-target-BzdJ2wIR.js → feedback-target-BBCh3RRq.js} +1 -1
- package/dist/dashboard/{feedback-target-BcsA_GRV.js → feedback-target-CeCq6EhW.js} +5 -5
- package/dist/dashboard/{feedback-target-BcsA_GRV.js.map → feedback-target-CeCq6EhW.js.map} +1 -1
- package/dist/dashboard/{flywheel-Is3w2lcT.js → flywheel-DKldI63J.js} +7 -7
- package/dist/dashboard/{flywheel-Is3w2lcT.js.map → flywheel-DKldI63J.js.map} +1 -1
- package/dist/dashboard/{flywheel-D0guNqkh.js → flywheel-DpRPLErh.js} +7 -7
- package/dist/dashboard/{flywheel-D0guNqkh.js.map → flywheel-DpRPLErh.js.map} +1 -1
- package/dist/dashboard/{flywheel-B34cdfbd.js → flywheel-jeZFYvm-.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-6HiPR2WG.js → flywheel-merge-order-0OnoosaO.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js → flywheel-merge-order-PG5op4j7.js} +2 -2
- package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js.map → flywheel-merge-order-PG5op4j7.js.map} +1 -1
- package/dist/dashboard/{harness-resolve-CzTFVZ5l.js → harness-resolve-C0HWj3H9.js} +2 -2
- package/dist/dashboard/{harness-resolve-CzTFVZ5l.js.map → harness-resolve-C0HWj3H9.js.map} +1 -1
- package/dist/dashboard/{identity-Dc7rQLln.js → identity-Cot1Hg4G.js} +2 -2
- package/dist/dashboard/{identity-Dc7rQLln.js.map → identity-Cot1Hg4G.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-CP7Bu47O.js → inspect-agent-BDmLWbGx.js} +3 -3
- package/dist/dashboard/{inspect-agent-CP7Bu47O.js.map → inspect-agent-BDmLWbGx.js.map} +1 -1
- package/dist/dashboard/inspect-agent-BcOkA-hC.js +2 -0
- package/dist/dashboard/{issue-lifecycle-BPbczKyy.js → issue-lifecycle-1B_v8Rf5.js} +6 -6
- package/dist/dashboard/{issue-lifecycle-BPbczKyy.js.map → issue-lifecycle-1B_v8Rf5.js.map} +1 -1
- package/dist/dashboard/{issue-lifecycle-FaugTi39.js → issue-lifecycle-CIrfbIbj.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-Dsad9NTt.js → issue-service-singleton-BVwPzjwF.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js → issue-service-singleton-KcRrOiFD.js} +2 -2
- package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js.map → issue-service-singleton-KcRrOiFD.js.map} +1 -1
- package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js → jsonl-resolver-jxbif7MD.js} +3 -3
- package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js.map → jsonl-resolver-jxbif7MD.js.map} +1 -1
- package/dist/dashboard/{kimi-code-Cx5YUJpS.js → kimi-code-BZh3NgcN.js} +1 -1
- package/dist/dashboard/{lifecycle-DZFHmpGI.js → lifecycle-CvcZ4_fA.js} +4 -4
- package/dist/dashboard/{lifecycle-DZFHmpGI.js.map → lifecycle-CvcZ4_fA.js.map} +1 -1
- package/dist/dashboard/{linear-mcp-auth-DylPJABZ.js → linear-mcp-auth-CZoj_-d7.js} +1 -1
- package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js → linear-mcp-auth-Dx74yCAY.js} +2 -2
- package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js.map → linear-mcp-auth-Dx74yCAY.js.map} +1 -1
- package/dist/dashboard/{memory-governor-B7uDWRo9.js → memory-governor-Bv3LAqk3.js} +5 -5
- package/dist/dashboard/{memory-governor-B7uDWRo9.js.map → memory-governor-Bv3LAqk3.js.map} +1 -1
- package/dist/dashboard/{merge-agent-ddEcZDZD.js → merge-agent-1zVLl6k_.js} +1 -1
- package/dist/dashboard/{merge-agent-ttRY053V.js → merge-agent-8zrQu48t.js} +8 -8
- package/dist/dashboard/{merge-agent-ttRY053V.js.map → merge-agent-8zrQu48t.js.map} +1 -1
- package/dist/dashboard/merge-ops-CFJaMPLu.js +2 -0
- package/dist/dashboard/{merge-train-f_un38UJ.js → merge-train-BYH_cF7G.js} +2 -2
- package/dist/dashboard/{merge-train-f_un38UJ.js.map → merge-train-BYH_cF7G.js.map} +1 -1
- package/dist/dashboard/{merge-train-deps-GPlHOjK6.js → merge-train-deps-BTfXM5AR.js} +2 -2
- package/dist/dashboard/{merge-train-deps-GPlHOjK6.js.map → merge-train-deps-BTfXM5AR.js.map} +1 -1
- package/dist/dashboard/{messaging-BasbftDu.js → messaging-Bk6vGsMw.js} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js → orphan-proposed-reconciler-B898fk26.js} +6 -6
- package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js.map → orphan-proposed-reconciler-B898fk26.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-DzAxrM99.js → orphan-proposed-reconciler-Bx_aWS4B.js} +1 -1
- package/dist/dashboard/{parked-residue-DMtbxJml.js → parked-residue-q2kzrYSm.js} +9 -9
- package/dist/dashboard/{parked-residue-DMtbxJml.js.map → parked-residue-q2kzrYSm.js.map} +1 -1
- package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js → post-merge-lifecycle-worker-Cl7-uf4g.js} +2 -2
- package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js.map → post-merge-lifecycle-worker-Cl7-uf4g.js.map} +1 -1
- package/dist/dashboard/{project-resource-refresh-queue-cqOeJ0fB.js → project-resource-refresh-queue-O_dRfPHn.js} +1 -1
- package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js → project-resource-refresh-queue-se3T9IYP.js} +4 -4
- package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js.map → project-resource-refresh-queue-se3T9IYP.js.map} +1 -1
- package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js → reconstruct-cache-XpWC9L0F.js} +2 -2
- package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js.map → reconstruct-cache-XpWC9L0F.js.map} +1 -1
- package/dist/dashboard/{release-engine-78iCajIc.js → release-engine-BuzxOamf.js} +2 -2
- package/dist/dashboard/{release-engine-78iCajIc.js.map → release-engine-BuzxOamf.js.map} +1 -1
- package/dist/dashboard/{remote-completion-D5wzZCte.js → remote-completion-BCZgeKPS.js} +3 -3
- package/dist/dashboard/{remote-completion-D5wzZCte.js.map → remote-completion-BCZgeKPS.js.map} +1 -1
- package/dist/dashboard/{resolver-BGZh3lXA.js → resolver-BeNrqNou.js} +5 -5
- package/dist/dashboard/{resolver-BGZh3lXA.js.map → resolver-BeNrqNou.js.map} +1 -1
- package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js → resource-pressure-patrol-Ccg_aCgO.js} +3 -3
- package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js.map → resource-pressure-patrol-Ccg_aCgO.js.map} +1 -1
- package/dist/dashboard/{resume-BKyB4hA1.js → resume-KGCrn0DV.js} +1 -1
- package/dist/dashboard/{review-agent-ZdP-HmwY.js → review-agent-BmM6YMZW.js} +1 -1
- package/dist/dashboard/{review-agent-DHs-JngN.js → review-agent-DRWVU8fP.js} +13 -13
- package/dist/dashboard/{review-agent-DHs-JngN.js.map → review-agent-DRWVU8fP.js.map} +1 -1
- package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js → review-convoy-liveness-EvCDv8Q_.js} +3 -3
- package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js.map → review-convoy-liveness-EvCDv8Q_.js.map} +1 -1
- package/dist/dashboard/{review-run-recovery-BYalQzbh.js → review-run-recovery-Ctm48Snt.js} +1 -1
- package/dist/dashboard/{review-run-recovery-BvVG3oUr.js → review-run-recovery-DNDI58cM.js} +2 -2
- package/dist/dashboard/{review-run-recovery-BvVG3oUr.js.map → review-run-recovery-DNDI58cM.js.map} +1 -1
- package/dist/dashboard/{review-status-DXTFzCmR.js → review-status-FnlvQuPI.js} +1 -1
- package/dist/dashboard/{review-status-emit-C9YW6SQL.js → review-status-emit-CeMS31DX.js} +2 -2
- package/dist/dashboard/{review-status-emit-C9YW6SQL.js.map → review-status-emit-CeMS31DX.js.map} +1 -1
- package/dist/dashboard/{review-status-BegAr86G.js → review-status-yY5qt7Rl.js} +5 -5
- package/dist/dashboard/{review-status-BegAr86G.js.map → review-status-yY5qt7Rl.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js → review-verdict-feedback-BNgRj1e3.js} +4 -4
- package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js.map → review-verdict-feedback-BNgRj1e3.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-Dp139Lmv.js → review-verdict-feedback-D39cuSV1.js} +1 -1
- package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js → review-verdict-writer-DvFfKAn-.js} +2 -2
- package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js.map → review-verdict-writer-DvFfKAn-.js.map} +1 -1
- package/dist/dashboard/{review-verdict-writer-BVco3WoI.js → review-verdict-writer-PcGBkvJ4.js} +1 -1
- package/dist/dashboard/{sequence-io-iljWlslY.js → sequence-io-DZyschsf.js} +2 -2
- package/dist/dashboard/{sequence-io-iljWlslY.js.map → sequence-io-DZyschsf.js.map} +1 -1
- package/dist/dashboard/{sequencer-agent-BGGVKxtw.js → sequencer-agent-BXDw5cNx.js} +5 -5
- package/dist/dashboard/{sequencer-agent-BGGVKxtw.js.map → sequencer-agent-BXDw5cNx.js.map} +1 -1
- package/dist/dashboard/server.js +79 -72
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{service-kzhN0DLg.js → service-DP50Omvn.js} +33 -33
- package/dist/dashboard/{service-kzhN0DLg.js.map → service-DP50Omvn.js.map} +1 -1
- package/dist/dashboard/{smart-compaction-owKHJDbL.js → smart-compaction-944GL8PN.js} +1 -1
- package/dist/dashboard/{smart-compaction-C96F2aFQ.js → smart-compaction-BzDfF6yb.js} +2 -2
- package/dist/dashboard/{smart-compaction-C96F2aFQ.js.map → smart-compaction-BzDfF6yb.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-CiJdOHTD.js → spawn-planning-session-BcVnHJjm.js} +1 -1
- package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js → spawn-planning-session-CH8IYli7.js} +3 -3
- package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js.map → spawn-planning-session-CH8IYli7.js.map} +1 -1
- package/dist/dashboard/{specialist-context-_ootmzn2.js → specialist-context-DhDTXVht.js} +3 -3
- package/dist/dashboard/{specialist-context-_ootmzn2.js.map → specialist-context-DhDTXVht.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-BbAX221-.js → specialist-logs-C1bcnZ-G.js} +2 -2
- package/dist/dashboard/{specialist-logs-BbAX221-.js.map → specialist-logs-C1bcnZ-G.js.map} +1 -1
- package/dist/dashboard/{specialists-COZoYaiz.js → specialists-BKmReEWT.js} +53 -53
- package/dist/dashboard/{specialists-COZoYaiz.js.map → specialists-BKmReEWT.js.map} +1 -1
- package/dist/dashboard/{specialists-RPLJnYc1.js → specialists-Bvrvt0br.js} +12 -12
- package/dist/dashboard/{specialists-RPLJnYc1.js.map → specialists-Bvrvt0br.js.map} +1 -1
- package/dist/dashboard/{specialists-DTAOVZpU.js → specialists-CD6GEKXk.js} +1 -1
- package/dist/dashboard/{specialists-D8sYZnPb.js → specialists-eFWpJRBc.js} +1 -1
- package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js → stall-sweeper-D3fZvg7O.js} +3 -3
- package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js.map → stall-sweeper-D3fZvg7O.js.map} +1 -1
- package/dist/dashboard/{state-migrate-J3tFJnbd.js → state-migrate-KZ2goscQ.js} +2 -2
- package/dist/dashboard/{state-migrate-J3tFJnbd.js.map → state-migrate-KZ2goscQ.js.map} +1 -1
- package/dist/dashboard/{stuck-remediation-BvTK_NYN.js → stuck-remediation-Cb2V1roj.js} +1 -1
- package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js → stuck-remediation-CnoV0kcv.js} +5 -5
- package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js.map → stuck-remediation-CnoV0kcv.js.map} +1 -1
- package/dist/dashboard/{summary-fork-PLcckTRE.js → summary-fork-CxuNBM0F.js} +2 -2
- package/dist/dashboard/{summary-fork-D-cylknJ.js → summary-fork-lC2vMXly.js} +4 -4
- package/dist/dashboard/{summary-fork-D-cylknJ.js.map → summary-fork-lC2vMXly.js.map} +1 -1
- package/dist/dashboard/{teardown-workspace-CTL16rnG.js → teardown-workspace-C-9murBw.js} +4 -4
- package/dist/dashboard/{teardown-workspace-CTL16rnG.js.map → teardown-workspace-C-9murBw.js.map} +1 -1
- package/dist/dashboard/{test-agent-queue-CMgYZiR_.js → test-agent-queue-YCBRzaAB.js} +3 -3
- package/dist/dashboard/{test-agent-queue-CMgYZiR_.js.map → test-agent-queue-YCBRzaAB.js.map} +1 -1
- package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js → uat-failure-feedback-B-1mL8Zp.js} +3 -3
- package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js.map → uat-failure-feedback-B-1mL8Zp.js.map} +1 -1
- package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js → uat-promote-verification-BEBPcpPg.js} +2 -2
- package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js.map → uat-promote-verification-BEBPcpPg.js.map} +1 -1
- package/dist/dashboard/{uat-train-BoA9r6_I.js → uat-train-BFo3OQUB.js} +1 -1
- package/dist/dashboard/{uat-train-Cd8CJeNe.js → uat-train-COgoJomz.js} +8 -8
- package/dist/dashboard/{uat-train-Cd8CJeNe.js.map → uat-train-COgoJomz.js.map} +1 -1
- package/dist/dashboard/{validate-BdmvTIym.js → validate-069_N1e2.js} +2 -2
- package/dist/dashboard/{validate-BdmvTIym.js.map → validate-069_N1e2.js.map} +1 -1
- package/dist/dashboard/{verification-runner-BO0K3xNA.js → verification-runner-CQnZyC5A.js} +1 -1
- package/dist/dashboard/{verification-runner-fEY1Qj-l.js → verification-runner-fy3wNrWn.js} +4 -4
- package/dist/dashboard/{verification-runner-fEY1Qj-l.js.map → verification-runner-fy3wNrWn.js.map} +1 -1
- package/dist/dashboard/workflows-Bk6Kt0ZQ.js +2 -0
- package/dist/dashboard/{workflows-CxVnygYU.js → workflows-N4QMkDzN.js} +10 -10
- package/dist/dashboard/{workflows-CxVnygYU.js.map → workflows-N4QMkDzN.js.map} +1 -1
- package/dist/dashboard/{workspaces-Cpz0xJWQ.js → workspaces-DmoyfHoU.js} +33 -33
- package/dist/dashboard/{workspaces-Cpz0xJWQ.js.map → workspaces-DmoyfHoU.js.map} +1 -1
- package/dist/{lifecycle-C6k0HWEO.js → lifecycle-DCFcjg7y.js} +1 -1
- package/dist/{lifecycle-D5oRpvvB.js → lifecycle-GkaHQJNK.js} +2 -2
- package/dist/{lifecycle-D5oRpvvB.js.map → lifecycle-GkaHQJNK.js.map} +1 -1
- package/dist/{merge-agent-Drufc7tU.js → merge-agent-CogNFasY.js} +2 -2
- package/dist/{merge-agent-Drufc7tU.js.map → merge-agent-CogNFasY.js.map} +1 -1
- package/dist/{merge-agent-CBOVY_eC.js → merge-agent-eteKQ9Rb.js} +1 -1
- package/package.json +1 -1
- package/dist/build-info-BgrwLUy6.js +0 -2
- package/dist/dashboard/agent-state-Ci6PhjOE.js.map +0 -1
- package/dist/dashboard/build-info-CYlxvZSQ.js +0 -2
- package/dist/dashboard/cli-ZAS502uC.js +0 -2
- package/dist/dashboard/conversation-service-Dlks_hSS.js +0 -3
- package/dist/dashboard/inspect-agent-DTuSg95i.js +0 -2
- package/dist/dashboard/merge-ops-Bgc-EScO.js +0 -2
- package/dist/dashboard/workflows-CgGNyl1z.js +0 -2
package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js → review-verdict-feedback-BNgRj1e3.js}
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { On as resolveProjectFromIssueSync, fn as init_projects } from "./config-yaml-Dce9VUd6.js";
|
|
2
|
-
import { Gt as messageAgent, Wt as init_messaging, _ as init_agent_state, g as getAgentStateSync } from "./agent-state-
|
|
2
|
+
import { Gt as messageAgent, Wt as init_messaging, _ as init_agent_state, g as getAgentStateSync } from "./agent-state-B196fj1h.js";
|
|
3
3
|
import { a as PAN_DIRNAME, m as init_types } from "./types-pLpbsNto.js";
|
|
4
|
-
import { d as getReviewStatusSync, p as init_review_status, r as clearFeedbackDeliveryStuck } from "./review-status-
|
|
4
|
+
import { d as getReviewStatusSync, p as init_review_status, r as clearFeedbackDeliveryStuck } from "./review-status-yY5qt7Rl.js";
|
|
5
5
|
import { d as init_review_verdict_report, l as findVerdictReport } from "./synthesis-verdict-BtJAwMS3.js";
|
|
6
6
|
import { a as writeFeedbackFile, i as init_feedback_writer } from "./feedback-writer-lbXBFycU.js";
|
|
7
|
-
import { n as resolveIssueFeedbackTarget, r as surfaceIssueFeedbackNeedsYou, t as init_feedback_target } from "./feedback-target-
|
|
7
|
+
import { n as resolveIssueFeedbackTarget, r as surfaceIssueFeedbackNeedsYou, t as init_feedback_target } from "./feedback-target-CeCq6EhW.js";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { Effect } from "effect";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
@@ -228,4 +228,4 @@ async function deliverReviewVerdictFeedbackFromStatus(issueId, status) {
|
|
|
228
228
|
//#endregion
|
|
229
229
|
export { deliverReviewVerdictFeedbackFromStatus as n, postPrComment as r, deliverReviewVerdictFeedback as t };
|
|
230
230
|
|
|
231
|
-
//# sourceMappingURL=review-verdict-feedback-
|
|
231
|
+
//# sourceMappingURL=review-verdict-feedback-BNgRj1e3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-verdict-feedback-qlVBi19-.js","names":[],"sources":["../../src/lib/cloister/review-verdict-feedback.ts"],"sourcesContent":["/**\n * Delivers durable review-verdict feedback to the work agent. Agent messages\n * use a key derived from the issue and review run so one review cycle is\n * model-visible at most once; callers without a run ID fall back to the\n * reviewed anchor, or deliver unkeyed if neither identity exists. ACP and\n * Channels targets fall back to unkeyed delivery because those transports\n * cannot enforce the key.\n * Repeated keyed suppressions surface a needs-you escalation before duplicate\n * delivery attempts consume the work agent's context window.\n */\nimport { execFile } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect } from 'effect';\n\nimport { messageAgent } from '../agents/messaging.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { clearFeedbackDeliveryStuck, getReviewStatusSync } from '../review-status.js';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { writeFeedbackFile } from './feedback-writer.js';\nimport { resolveIssueFeedbackTarget, surfaceIssueFeedbackNeedsYou } from './feedback-target.js';\nimport { findVerdictReport } from './review-verdict-report.js';\n\nconst execFileAsync = promisify(execFile);\n\n// PAN-1837: bounded same-key retries for ambiguous keyed deliveries. The\n// common ambiguity source is a delivery racing an agent resume, which\n// resolves within seconds once the supervisor finishes binding its socket.\nconst AMBIGUOUS_DELIVERY_RETRIES = 3;\nconst AMBIGUOUS_DELIVERY_RETRY_MS = 2_000;\n\ntype ReviewVerdict = 'blocked' | 'failed';\n\nexport interface DeliverReviewVerdictFeedbackOptions {\n issueId: string;\n verdict: ReviewVerdict;\n notes?: string;\n workspacePath?: string;\n prUrl?: string;\n slotItemId?: string;\n runId?: string;\n}\n\nexport interface DeliverReviewVerdictFeedbackResult {\n feedbackPath?: string;\n synthesisPath?: string;\n prCommentPosted: boolean;\n agentMessageSent: boolean;\n}\n\nexport interface ReviewVerdictFeedbackStatus {\n reviewStatus: string;\n reviewNotes?: string;\n prUrl?: string;\n}\n\nasync function findLatestSynthesis(workspacePath: string): Promise<{ path: string; body: string } | null> {\n const reviewRoot = join(workspacePath, PAN_DIRNAME, 'review');\n if (!existsSync(reviewRoot)) return null;\n\n let latest: { path: string; mtimeMs: number } | null = null;\n const entries = await readdir(reviewRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const report = findVerdictReport(join(reviewRoot, entry.name));\n if (!report) continue;\n const fileStat = await stat(report.path);\n if (!latest || fileStat.mtimeMs > latest.mtimeMs) {\n latest = { path: report.path, mtimeMs: fileStat.mtimeMs };\n }\n }\n\n if (!latest) return null;\n return { path: latest.path, body: await readFile(latest.path, 'utf-8') };\n}\n\nfunction parseGitHubPrUrl(prUrl: string | undefined): { owner: string; repo: string; number: string } | null {\n const match = prUrl?.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) return null;\n return { owner: match[1]!, repo: match[2]!, number: match[3]! };\n}\n\nfunction buildReviewFeedbackBody(opts: {\n issueId: string;\n verdict: ReviewVerdict;\n notes?: string;\n synthesisBody?: string;\n synthesisPath?: string;\n}): string {\n const verdictLabel = opts.verdict === 'blocked' ? 'CHANGES REQUESTED' : 'FAILED';\n const synthesis = opts.synthesisBody?.trim() || opts.notes?.trim() || 'Review did not provide a synthesis summary.';\n const sourceLine = opts.synthesisPath ? `\\n\\nSource: ${opts.synthesisPath}` : '';\n\n return `# Review ${verdictLabel} for ${opts.issueId}\\n\\n${synthesis}${sourceLine}\\n\\n## Required action\\n\\nFix every blocking review finding, commit the fixes, then re-request review with:\\n\\n\\`pan review request ${opts.issueId} -m \"Fixed review issues\"\\``;\n}\n\n// PAN-2518: the PR-comment POST is advisory — the review verdict is already\n// durable (setReviewStatusSync + writeFeedbackFile) before we get here. A `gh\n// api` call that STALLS (not rejects) on a network hiccup must never block the\n// caller: `pan admin specialists done` is shelled out from the review agent's\n// own session, so a hung POST leaves that agent waiting on a never-returning\n// command and the issue stalls in-review. A bounded timeout turns a stall into\n// a rejection the caller already swallows.\nconst PR_COMMENT_TIMEOUT_MS = 15_000;\nconst suppressedReviewFeedbackDeliveries = new Map<string, number>();\nconst REPEATED_DELIVERY_LOOP_MESSAGE =\n 'Review feedback for this verdict was already delivered to the agent; the pipeline re-triggered delivery 3+ times — possible stuck loop. Investigate before the agent context burns.';\n\nexport async function postPrComment(prUrl: string | undefined, body: string): Promise<boolean> {\n const parsed = parseGitHubPrUrl(prUrl);\n if (!parsed) return false;\n\n await execFileAsync(\n 'gh',\n ['api', `repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments`, '--field', `body=${body}`],\n { encoding: 'utf-8', timeout: PR_COMMENT_TIMEOUT_MS, killSignal: 'SIGKILL' },\n );\n return true;\n}\n\nasync function deliverReviewVerdictFeedbackPromise(\n opts: DeliverReviewVerdictFeedbackOptions,\n): Promise<DeliverReviewVerdictFeedbackResult> {\n const issueId = opts.issueId.toUpperCase();\n const resolved = resolveProjectFromIssueSync(issueId);\n const workspacePath = opts.workspacePath\n ?? (resolved ? join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`) : undefined);\n const existingStatus = getReviewStatusSync(issueId);\n\n // PAN-3151: check if review loop is stuck in non-converging state\n const isReviewNotConverging = existingStatus?.stuckReason === 'review-not-converging';\n\n const deliveryIdentity = opts.runId\n ? `run:${opts.runId}`\n : existingStatus?.reviewedAtCommit\n ? `anchor:${existingStatus.reviewedAtCommit}`\n : undefined;\n const dedupKey = deliveryIdentity\n ? `review-feedback:${issueId.toLowerCase()}:${createHash('sha256')\n .update(deliveryIdentity)\n .digest('hex')\n .slice(0, 16)}`\n : undefined;\n const synthesis = workspacePath && existsSync(workspacePath)\n ? await findLatestSynthesis(workspacePath)\n : null;\n const markdownBody = buildReviewFeedbackBody({\n issueId,\n verdict: opts.verdict,\n notes: opts.notes,\n synthesisBody: synthesis?.body,\n synthesisPath: synthesis?.path,\n });\n\n let prCommentPosted = false;\n try {\n prCommentPosted = await postPrComment(opts.prUrl ?? existingStatus?.prUrl, markdownBody);\n } catch (err) {\n console.warn(`[review-verdict-feedback] Failed to post PR comment for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n const fileResult = await Effect.runPromise(writeFeedbackFile({\n issueId,\n workspacePath,\n specialist: 'review-agent',\n outcome: opts.verdict === 'blocked' ? 'changes-requested' : 'failed',\n summary: `Review ${opts.verdict.toUpperCase()}: ${(opts.notes ?? synthesis?.body ?? '').slice(0, 80)}`,\n markdownBody,\n }));\n\n let agentMessageSent = false;\n if (fileResult.success && fileResult.filePath) {\n if (isReviewNotConverging) {\n // PAN-3151: review loop not converging — suppress agent re-drive, surface needs-you instead\n const countSeries = existingStatus?.reviewCycleHistory\n ?.map(e => e.blockingCount)\n .join(' → ') ?? 'unknown';\n const needsYouMessage = `Review loop not converging (cycle counts: ${countSeries}). ` +\n `Consider decomposing the remaining work into sibling issues, or unstick to continue rework.`;\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, needsYouMessage, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n slotItemId: opts.slotItemId,\n });\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not surface convergence gate for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n agentMessageSent = false;\n } else {\n const message = `SPECIALIST FEEDBACK: review-agent reported ${opts.verdict.toUpperCase()} for ${issueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then fix ALL review findings. Do NOT stop at the prompt.`;\n try {\n // PAN-2209/PAN-2461: resolveIssueFeedbackTarget's built-in resurrection ladder\n // (unpause pipeline pauses, clear troubled gates, resume stopped agents) handles\n // every non-live target; no local reviver override.\n const target = await resolveIssueFeedbackTarget(issueId, {\n itemId: opts.slotItemId,\n });\n if ('agentId' in target) {\n try {\n // PAN-2668: a blocked/failed review verdict owes rework — re-drive a\n // stopped-by-user agent with a completed handoff instead of queueing.\n let deliveryOutcome;\n let ambiguousRetries = 0;\n for (;;) {\n try {\n deliveryOutcome = await messageAgent(target.agentId, message, 'internal', {\n owesRework: true,\n ...(dedupKey ? { dedupKey } : {}),\n });\n break;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n // An ambiguous keyed outcome means the supervisor may or may not\n // have completed the injection; its contract (delivery.ts,\n // AmbiguousKeyedDeliveryError) is to retry the SAME key at the\n // SAME tier — the supervisor's dedup store absorbs a duplicate.\n // Without this retry a delivery that raced an agent resume\n // stranded rework behind feedback_delivery_needs_you (PAN-1837).\n if (\n err instanceof Error\n && err.name === 'AmbiguousKeyedDeliveryError'\n && ambiguousRetries < AMBIGUOUS_DELIVERY_RETRIES\n ) {\n ambiguousRetries += 1;\n console.warn(\n `[review-verdict-feedback] ambiguous keyed delivery to ${target.agentId} — retrying the same key (${ambiguousRetries}/${AMBIGUOUS_DELIVERY_RETRIES}): ${reason}`,\n );\n await new Promise((resolve) => setTimeout(resolve, AMBIGUOUS_DELIVERY_RETRY_MS));\n continue;\n }\n if (!reason.includes('cannot enforce a dedup key')) throw err;\n // ACP and explicit Channels targets cannot enforce keyed delivery.\n // Delivery still wins over deduplication for those transports.\n console.warn(\n `[review-verdict-feedback] ${target.agentId} cannot enforce keyed delivery; retrying unkeyed`,\n );\n deliveryOutcome = await messageAgent(\n target.agentId,\n message,\n 'internal',\n { owesRework: true },\n );\n break;\n }\n }\n agentMessageSent = true;\n let repeatedDeliveryLoop = false;\n if (deliveryOutcome.deduplicated && dedupKey) {\n const suppressedCount = (suppressedReviewFeedbackDeliveries.get(dedupKey) ?? 0) + 1;\n suppressedReviewFeedbackDeliveries.set(dedupKey, suppressedCount);\n repeatedDeliveryLoop = suppressedCount >= 2;\n if (suppressedCount === 2) {\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, REPEATED_DELIVERY_LOOP_MESSAGE, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n });\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not surface repeated delivery loop for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } else if (dedupKey) {\n suppressedReviewFeedbackDeliveries.delete(dedupKey);\n }\n // PAN-3074: a fresh delivery clears stale feedback-delivery state. Once\n // repeated suppressions surface a loop, preserve that operator-visible\n // state until a non-deduplicated delivery proves the loop ended.\n if (!repeatedDeliveryLoop) clearFeedbackDeliveryStuck(issueId);\n } catch (err) {\n // PAN-2228: a resolved-but-unreachable target is a real delivery failure,\n // not a shrug. Surface it as needs-you so the stall is visible instead of\n // the issue silently parking with an unread feedback file.\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(`[review-verdict-feedback] Could not message ${target.agentId}; feedback file remains available: ${reason}`);\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, `Feedback delivery to ${target.agentId} failed: ${reason}`, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n slotItemId: opts.slotItemId,\n });\n } catch { /* best-effort — the warn above still records the failure */ }\n }\n } else {\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, target.reason, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n slotItemId: opts.slotItemId,\n });\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not mark ${issueId} as needing human attention; feedback file remains available: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not resolve a feedback target for ${issueId}; feedback file remains available: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } else if (!fileResult.success) {\n console.error(`[review-verdict-feedback] Failed to write feedback file for ${issueId}: ${fileResult.error}`);\n }\n\n return {\n feedbackPath: fileResult.filePath,\n synthesisPath: synthesis?.path,\n prCommentPosted,\n agentMessageSent,\n };\n}\n\n// ─── Effect variant (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link deliverReviewVerdictFeedback}. The Promise version\n * already swallows recoverable errors (PR comment failures, agent messaging,\n * synthesis lookup), so the Effect form mirrors that contract: callers see a\n * successful Effect carrying the same result shape and inspect the flags to\n * decide what surfaced. The single non-recoverable boundary — feedback file\n * write — keeps its existing error reporting through {@link writeFeedbackFile}.\n */\nexport const deliverReviewVerdictFeedback = (\n opts: DeliverReviewVerdictFeedbackOptions,\n): Effect.Effect<DeliverReviewVerdictFeedbackResult> =>\n Effect.promise(() => deliverReviewVerdictFeedbackPromise(opts));\n\n/** Adapter for the review-status write door, registered by the dashboard composition root. */\nexport async function deliverReviewVerdictFeedbackFromStatus(\n issueId: string,\n status: ReviewVerdictFeedbackStatus,\n): Promise<void> {\n const runId = getAgentStateSync(`agent-${issueId.toLowerCase()}-review`)?.reviewRunId;\n const result = await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId,\n verdict: status.reviewStatus === 'failed' ? 'failed' : 'blocked',\n notes: status.reviewNotes,\n prUrl: status.prUrl,\n ...(runId ? { runId } : {}),\n }));\n if (result.agentMessageSent) {\n console.log(`[review-status] delivered review feedback to the work agent for ${issueId} (host-side)`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"review-verdict-feedback-BNgRj1e3.js","names":[],"sources":["../../src/lib/cloister/review-verdict-feedback.ts"],"sourcesContent":["/**\n * Delivers durable review-verdict feedback to the work agent. Agent messages\n * use a key derived from the issue and review run so one review cycle is\n * model-visible at most once; callers without a run ID fall back to the\n * reviewed anchor, or deliver unkeyed if neither identity exists. ACP and\n * Channels targets fall back to unkeyed delivery because those transports\n * cannot enforce the key.\n * Repeated keyed suppressions surface a needs-you escalation before duplicate\n * delivery attempts consume the work agent's context window.\n */\nimport { execFile } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect } from 'effect';\n\nimport { messageAgent } from '../agents/messaging.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { clearFeedbackDeliveryStuck, getReviewStatusSync } from '../review-status.js';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { writeFeedbackFile } from './feedback-writer.js';\nimport { resolveIssueFeedbackTarget, surfaceIssueFeedbackNeedsYou } from './feedback-target.js';\nimport { findVerdictReport } from './review-verdict-report.js';\n\nconst execFileAsync = promisify(execFile);\n\n// PAN-1837: bounded same-key retries for ambiguous keyed deliveries. The\n// common ambiguity source is a delivery racing an agent resume, which\n// resolves within seconds once the supervisor finishes binding its socket.\nconst AMBIGUOUS_DELIVERY_RETRIES = 3;\nconst AMBIGUOUS_DELIVERY_RETRY_MS = 2_000;\n\ntype ReviewVerdict = 'blocked' | 'failed';\n\nexport interface DeliverReviewVerdictFeedbackOptions {\n issueId: string;\n verdict: ReviewVerdict;\n notes?: string;\n workspacePath?: string;\n prUrl?: string;\n slotItemId?: string;\n runId?: string;\n}\n\nexport interface DeliverReviewVerdictFeedbackResult {\n feedbackPath?: string;\n synthesisPath?: string;\n prCommentPosted: boolean;\n agentMessageSent: boolean;\n}\n\nexport interface ReviewVerdictFeedbackStatus {\n reviewStatus: string;\n reviewNotes?: string;\n prUrl?: string;\n}\n\nasync function findLatestSynthesis(workspacePath: string): Promise<{ path: string; body: string } | null> {\n const reviewRoot = join(workspacePath, PAN_DIRNAME, 'review');\n if (!existsSync(reviewRoot)) return null;\n\n let latest: { path: string; mtimeMs: number } | null = null;\n const entries = await readdir(reviewRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const report = findVerdictReport(join(reviewRoot, entry.name));\n if (!report) continue;\n const fileStat = await stat(report.path);\n if (!latest || fileStat.mtimeMs > latest.mtimeMs) {\n latest = { path: report.path, mtimeMs: fileStat.mtimeMs };\n }\n }\n\n if (!latest) return null;\n return { path: latest.path, body: await readFile(latest.path, 'utf-8') };\n}\n\nfunction parseGitHubPrUrl(prUrl: string | undefined): { owner: string; repo: string; number: string } | null {\n const match = prUrl?.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) return null;\n return { owner: match[1]!, repo: match[2]!, number: match[3]! };\n}\n\nfunction buildReviewFeedbackBody(opts: {\n issueId: string;\n verdict: ReviewVerdict;\n notes?: string;\n synthesisBody?: string;\n synthesisPath?: string;\n}): string {\n const verdictLabel = opts.verdict === 'blocked' ? 'CHANGES REQUESTED' : 'FAILED';\n const synthesis = opts.synthesisBody?.trim() || opts.notes?.trim() || 'Review did not provide a synthesis summary.';\n const sourceLine = opts.synthesisPath ? `\\n\\nSource: ${opts.synthesisPath}` : '';\n\n return `# Review ${verdictLabel} for ${opts.issueId}\\n\\n${synthesis}${sourceLine}\\n\\n## Required action\\n\\nFix every blocking review finding, commit the fixes, then re-request review with:\\n\\n\\`pan review request ${opts.issueId} -m \"Fixed review issues\"\\``;\n}\n\n// PAN-2518: the PR-comment POST is advisory — the review verdict is already\n// durable (setReviewStatusSync + writeFeedbackFile) before we get here. A `gh\n// api` call that STALLS (not rejects) on a network hiccup must never block the\n// caller: `pan admin specialists done` is shelled out from the review agent's\n// own session, so a hung POST leaves that agent waiting on a never-returning\n// command and the issue stalls in-review. A bounded timeout turns a stall into\n// a rejection the caller already swallows.\nconst PR_COMMENT_TIMEOUT_MS = 15_000;\nconst suppressedReviewFeedbackDeliveries = new Map<string, number>();\nconst REPEATED_DELIVERY_LOOP_MESSAGE =\n 'Review feedback for this verdict was already delivered to the agent; the pipeline re-triggered delivery 3+ times — possible stuck loop. Investigate before the agent context burns.';\n\nexport async function postPrComment(prUrl: string | undefined, body: string): Promise<boolean> {\n const parsed = parseGitHubPrUrl(prUrl);\n if (!parsed) return false;\n\n await execFileAsync(\n 'gh',\n ['api', `repos/${parsed.owner}/${parsed.repo}/issues/${parsed.number}/comments`, '--field', `body=${body}`],\n { encoding: 'utf-8', timeout: PR_COMMENT_TIMEOUT_MS, killSignal: 'SIGKILL' },\n );\n return true;\n}\n\nasync function deliverReviewVerdictFeedbackPromise(\n opts: DeliverReviewVerdictFeedbackOptions,\n): Promise<DeliverReviewVerdictFeedbackResult> {\n const issueId = opts.issueId.toUpperCase();\n const resolved = resolveProjectFromIssueSync(issueId);\n const workspacePath = opts.workspacePath\n ?? (resolved ? join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`) : undefined);\n const existingStatus = getReviewStatusSync(issueId);\n\n // PAN-3151: check if review loop is stuck in non-converging state\n const isReviewNotConverging = existingStatus?.stuckReason === 'review-not-converging';\n\n const deliveryIdentity = opts.runId\n ? `run:${opts.runId}`\n : existingStatus?.reviewedAtCommit\n ? `anchor:${existingStatus.reviewedAtCommit}`\n : undefined;\n const dedupKey = deliveryIdentity\n ? `review-feedback:${issueId.toLowerCase()}:${createHash('sha256')\n .update(deliveryIdentity)\n .digest('hex')\n .slice(0, 16)}`\n : undefined;\n const synthesis = workspacePath && existsSync(workspacePath)\n ? await findLatestSynthesis(workspacePath)\n : null;\n const markdownBody = buildReviewFeedbackBody({\n issueId,\n verdict: opts.verdict,\n notes: opts.notes,\n synthesisBody: synthesis?.body,\n synthesisPath: synthesis?.path,\n });\n\n let prCommentPosted = false;\n try {\n prCommentPosted = await postPrComment(opts.prUrl ?? existingStatus?.prUrl, markdownBody);\n } catch (err) {\n console.warn(`[review-verdict-feedback] Failed to post PR comment for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n const fileResult = await Effect.runPromise(writeFeedbackFile({\n issueId,\n workspacePath,\n specialist: 'review-agent',\n outcome: opts.verdict === 'blocked' ? 'changes-requested' : 'failed',\n summary: `Review ${opts.verdict.toUpperCase()}: ${(opts.notes ?? synthesis?.body ?? '').slice(0, 80)}`,\n markdownBody,\n }));\n\n let agentMessageSent = false;\n if (fileResult.success && fileResult.filePath) {\n if (isReviewNotConverging) {\n // PAN-3151: review loop not converging — suppress agent re-drive, surface needs-you instead\n const countSeries = existingStatus?.reviewCycleHistory\n ?.map(e => e.blockingCount)\n .join(' → ') ?? 'unknown';\n const needsYouMessage = `Review loop not converging (cycle counts: ${countSeries}). ` +\n `Consider decomposing the remaining work into sibling issues, or unstick to continue rework.`;\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, needsYouMessage, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n slotItemId: opts.slotItemId,\n });\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not surface convergence gate for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n agentMessageSent = false;\n } else {\n const message = `SPECIALIST FEEDBACK: review-agent reported ${opts.verdict.toUpperCase()} for ${issueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then fix ALL review findings. Do NOT stop at the prompt.`;\n try {\n // PAN-2209/PAN-2461: resolveIssueFeedbackTarget's built-in resurrection ladder\n // (unpause pipeline pauses, clear troubled gates, resume stopped agents) handles\n // every non-live target; no local reviver override.\n const target = await resolveIssueFeedbackTarget(issueId, {\n itemId: opts.slotItemId,\n });\n if ('agentId' in target) {\n try {\n // PAN-2668: a blocked/failed review verdict owes rework — re-drive a\n // stopped-by-user agent with a completed handoff instead of queueing.\n let deliveryOutcome;\n let ambiguousRetries = 0;\n for (;;) {\n try {\n deliveryOutcome = await messageAgent(target.agentId, message, 'internal', {\n owesRework: true,\n ...(dedupKey ? { dedupKey } : {}),\n });\n break;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n // An ambiguous keyed outcome means the supervisor may or may not\n // have completed the injection; its contract (delivery.ts,\n // AmbiguousKeyedDeliveryError) is to retry the SAME key at the\n // SAME tier — the supervisor's dedup store absorbs a duplicate.\n // Without this retry a delivery that raced an agent resume\n // stranded rework behind feedback_delivery_needs_you (PAN-1837).\n if (\n err instanceof Error\n && err.name === 'AmbiguousKeyedDeliveryError'\n && ambiguousRetries < AMBIGUOUS_DELIVERY_RETRIES\n ) {\n ambiguousRetries += 1;\n console.warn(\n `[review-verdict-feedback] ambiguous keyed delivery to ${target.agentId} — retrying the same key (${ambiguousRetries}/${AMBIGUOUS_DELIVERY_RETRIES}): ${reason}`,\n );\n await new Promise((resolve) => setTimeout(resolve, AMBIGUOUS_DELIVERY_RETRY_MS));\n continue;\n }\n if (!reason.includes('cannot enforce a dedup key')) throw err;\n // ACP and explicit Channels targets cannot enforce keyed delivery.\n // Delivery still wins over deduplication for those transports.\n console.warn(\n `[review-verdict-feedback] ${target.agentId} cannot enforce keyed delivery; retrying unkeyed`,\n );\n deliveryOutcome = await messageAgent(\n target.agentId,\n message,\n 'internal',\n { owesRework: true },\n );\n break;\n }\n }\n agentMessageSent = true;\n let repeatedDeliveryLoop = false;\n if (deliveryOutcome.deduplicated && dedupKey) {\n const suppressedCount = (suppressedReviewFeedbackDeliveries.get(dedupKey) ?? 0) + 1;\n suppressedReviewFeedbackDeliveries.set(dedupKey, suppressedCount);\n repeatedDeliveryLoop = suppressedCount >= 2;\n if (suppressedCount === 2) {\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, REPEATED_DELIVERY_LOOP_MESSAGE, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n });\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not surface repeated delivery loop for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } else if (dedupKey) {\n suppressedReviewFeedbackDeliveries.delete(dedupKey);\n }\n // PAN-3074: a fresh delivery clears stale feedback-delivery state. Once\n // repeated suppressions surface a loop, preserve that operator-visible\n // state until a non-deduplicated delivery proves the loop ended.\n if (!repeatedDeliveryLoop) clearFeedbackDeliveryStuck(issueId);\n } catch (err) {\n // PAN-2228: a resolved-but-unreachable target is a real delivery failure,\n // not a shrug. Surface it as needs-you so the stall is visible instead of\n // the issue silently parking with an unread feedback file.\n const reason = err instanceof Error ? err.message : String(err);\n console.warn(`[review-verdict-feedback] Could not message ${target.agentId}; feedback file remains available: ${reason}`);\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, `Feedback delivery to ${target.agentId} failed: ${reason}`, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n slotItemId: opts.slotItemId,\n });\n } catch { /* best-effort — the warn above still records the failure */ }\n }\n } else {\n try {\n await surfaceIssueFeedbackNeedsYou(issueId, target.reason, {\n specialist: 'review-agent',\n feedbackPath: fileResult.filePath,\n slotItemId: opts.slotItemId,\n });\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not mark ${issueId} as needing human attention; feedback file remains available: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (err) {\n console.warn(`[review-verdict-feedback] Could not resolve a feedback target for ${issueId}; feedback file remains available: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } else if (!fileResult.success) {\n console.error(`[review-verdict-feedback] Failed to write feedback file for ${issueId}: ${fileResult.error}`);\n }\n\n return {\n feedbackPath: fileResult.filePath,\n synthesisPath: synthesis?.path,\n prCommentPosted,\n agentMessageSent,\n };\n}\n\n// ─── Effect variant (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link deliverReviewVerdictFeedback}. The Promise version\n * already swallows recoverable errors (PR comment failures, agent messaging,\n * synthesis lookup), so the Effect form mirrors that contract: callers see a\n * successful Effect carrying the same result shape and inspect the flags to\n * decide what surfaced. The single non-recoverable boundary — feedback file\n * write — keeps its existing error reporting through {@link writeFeedbackFile}.\n */\nexport const deliverReviewVerdictFeedback = (\n opts: DeliverReviewVerdictFeedbackOptions,\n): Effect.Effect<DeliverReviewVerdictFeedbackResult> =>\n Effect.promise(() => deliverReviewVerdictFeedbackPromise(opts));\n\n/** Adapter for the review-status write door, registered by the dashboard composition root. */\nexport async function deliverReviewVerdictFeedbackFromStatus(\n issueId: string,\n status: ReviewVerdictFeedbackStatus,\n): Promise<void> {\n const runId = getAgentStateSync(`agent-${issueId.toLowerCase()}-review`)?.reviewRunId;\n const result = await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId,\n verdict: status.reviewStatus === 'failed' ? 'failed' : 'blocked',\n notes: status.reviewNotes,\n prUrl: status.prUrl,\n ...(runId ? { runId } : {}),\n }));\n if (result.agentMessageSent) {\n console.log(`[review-status] delivered review feedback to the work agent for ${issueId} (host-side)`);\n }\n}\n"],"mappings":"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"}
|
package/dist/dashboard/{review-verdict-feedback-Dp139Lmv.js → review-verdict-feedback-D39cuSV1.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as deliverReviewVerdictFeedback } from "./review-verdict-feedback-
|
|
1
|
+
import { t as deliverReviewVerdictFeedback } from "./review-verdict-feedback-BNgRj1e3.js";
|
|
2
2
|
export { deliverReviewVerdictFeedback };
|
package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js → review-verdict-writer-DvFfKAn-.js}
RENAMED
|
@@ -2,7 +2,7 @@ import { On as resolveProjectFromIssueSync, fn as init_projects } from "./config
|
|
|
2
2
|
import { l as init_activity_logger, o as emitActivityEntrySync } from "./activity-logger-DNYDzAOU.js";
|
|
3
3
|
import { a as init_git_utils, n as formatAnchorShort, o as parseCompositeSnapshot } from "./git-utils-D96xW28f.js";
|
|
4
4
|
import { d as resolveWorkspaceRepoRootsSync, i as init_project_repos } from "./project-repos-J6jc42uD.js";
|
|
5
|
-
import { d as getReviewStatusSync, p as init_review_status, w as setReviewStatusSync } from "./review-status-
|
|
5
|
+
import { d as getReviewStatusSync, p as init_review_status, w as setReviewStatusSync } from "./review-status-yY5qt7Rl.js";
|
|
6
6
|
import { t as getCloisterEventStore } from "./event-store-provider-B2PMoaap.js";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { execFile } from "child_process";
|
|
@@ -205,4 +205,4 @@ async function recordReviewVerdict(issueId, input) {
|
|
|
205
205
|
//#endregion
|
|
206
206
|
export { recordReviewVerdict as t };
|
|
207
207
|
|
|
208
|
-
//# sourceMappingURL=review-verdict-writer-
|
|
208
|
+
//# sourceMappingURL=review-verdict-writer-DvFfKAn-.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-verdict-writer-CJr_Ob0Y.js","names":[],"sources":["../../src/lib/cloister/review-verdict-writer.ts"],"sourcesContent":["/**\n * The verdict write door — recordReviewVerdict + dispatch-not-drop + fallback kill-conditional\n *\n * Single source of truth for terminal review verdict writes. Every call site\n * that writes a final verdict (passed/blocked) routes through this door.\n *\n * Dispatch-not-drop rule: a terminal review verdict whose evidence head disagrees\n * with the row's lastVerifiedCommit is never silently discarded. Provably-stale\n * evidence is rejected WITH a review.verdict_rejected domain event and an\n * activity entry; anything else lands and re-gates.\n */\nimport { join } from 'path';\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { parseCompositeSnapshot, formatAnchorShort, type HeadAnchor } from '../git-utils.js';\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getCloisterEventStore } from './event-store-provider.js';\nimport { getReviewStatusSync, setReviewStatusSync, type ReviewStatus, type ReviewStatusUpdate } from '../review-status.js';\n\nconst execFileAsync = promisify(execFile);\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type VerdictWriter = 'coordinator' | 'fallback' | 'quick-signal' | 'orphan-restore' | 'sweeper-restore' | 'unsignaled-recovery' | 'infra-bypass' | 'dispatch-converge';\n\nexport type ReviewVerdict = 'passed' | 'blocked' | 'failed';\n\nexport interface VerdictInput {\n verdict: ReviewVerdict;\n notes?: string;\n evidenceHead?: HeadAnchor;\n extra?: Record<string, unknown>;\n runId?: string;\n writer: VerdictWriter;\n}\n\nexport type VerdictOutcome = { landed: true; classification: 'no-evidence' | 'anchor-match' | 'dispatched' } | { landed: false; reason: string };\n\n// ─── Private: Head Classification ─────────────────────────────────────────────\n\ntype EvidenceClassification = 'no-evidence' | 'anchor-match' | 'stale' | 'fresh' | 'indeterminate';\n\nfunction isNonAncestorExit(error: unknown): boolean {\n return typeof error === 'object'\n && error !== null\n && 'code' in error\n && error.code === 1;\n}\n\nasync function classifyEvidenceAgainstAnchor(\n issueId: string,\n workspacePath: string,\n evidenceHead: HeadAnchor | string,\n rowHead: HeadAnchor | string,\n): Promise<EvidenceClassification> {\n const evidenceMap = parseCompositeSnapshot(evidenceHead);\n const rowMap = parseCompositeSnapshot(rowHead);\n const evidenceIsComposite = evidenceMap.size > 0;\n const rowIsComposite = rowMap.size > 0;\n\n if (evidenceIsComposite !== rowIsComposite) {\n return 'indeterminate';\n }\n\n let repoRoots: Array<{ repoKey: string; dir: string }>;\n try {\n repoRoots = resolveWorkspaceRepoRootsSync(issueId, workspacePath);\n } catch {\n return 'indeterminate';\n }\n\n let comparisons: Array<{ dir: string; evidenceSha: string; rowSha: string }>;\n if (!evidenceIsComposite) {\n if (repoRoots.length !== 1) {\n return 'indeterminate';\n }\n comparisons = [{\n dir: repoRoots[0]!.dir,\n evidenceSha: evidenceHead,\n rowSha: rowHead,\n }];\n } else {\n const rootMap = new Map(repoRoots.map(root => [root.repoKey, root.dir]));\n if (evidenceMap.size !== rowMap.size || ![...evidenceMap.keys()].every(key => rowMap.has(key))) {\n return 'indeterminate';\n }\n\n comparisons = [];\n for (const [repoKey, evidenceSha] of evidenceMap) {\n const rowSha = rowMap.get(repoKey);\n const dir = rootMap.get(repoKey);\n if (!rowSha || !dir) {\n return 'indeterminate';\n }\n comparisons.push({ dir, evidenceSha, rowSha });\n }\n }\n\n let allStale = true;\n for (const comparison of comparisons) {\n try {\n await execFileAsync(\n 'git',\n ['merge-base', '--is-ancestor', comparison.evidenceSha, comparison.rowSha],\n { cwd: comparison.dir, timeout: 10_000 },\n );\n } catch (error: unknown) {\n if (isNonAncestorExit(error)) {\n allStale = false;\n } else {\n return 'indeterminate';\n }\n }\n }\n\n return allStale ? 'stale' : 'fresh';\n}\n\n// ─── Public: Verdict Write Door ───────────────────────────────────────────────\n\n/**\n * Record a terminal review verdict through the single write door.\n *\n * Returns {landed: true} if the verdict was written to the row, or\n * {landed: false; reason} if the verdict was rejected (stale evidence only).\n */\nexport async function recordReviewVerdict(issueId: string, input: VerdictInput): Promise<VerdictOutcome> {\n const status = getReviewStatusSync(issueId);\n if (!status) {\n setReviewStatusSync(issueId, {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n ...(input.extra ? { ...input.extra } : {}),\n });\n return { landed: true, classification: 'no-evidence' };\n }\n\n // Resolve workspace path\n const workspacePath = (() => {\n const resolved = resolveProjectFromIssueSync(issueId);\n return resolved ? join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`) : null;\n })();\n\n // No evidence head or row head absent: take the no-evidence path\n if (!input.evidenceHead || !status.lastVerifiedCommit) {\n const update: ReviewStatusUpdate = {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n ...(input.extra ? { ...input.extra } : {}),\n };\n setReviewStatusSync(issueId, update, status);\n return { landed: true, classification: 'no-evidence' };\n }\n\n // Evidence heads equal: land the verdict without re-gating the same test result.\n if (input.evidenceHead === status.lastVerifiedCommit) {\n const update: ReviewStatusUpdate = {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n reviewedAtCommit: input.evidenceHead as HeadAnchor,\n ...(input.extra ? { ...input.extra } : {}),\n };\n setReviewStatusSync(issueId, update, status);\n\n const eventStore = getCloisterEventStore();\n if (eventStore) {\n eventStore.append({\n type: 'review.verdict_dispatched' as const,\n timestamp: new Date().toISOString(),\n payload: {\n issueId,\n workspaceId: workspacePath ? workspacePath.split('/').pop() : undefined,\n writer: input.writer,\n verdict: input.verdict,\n evidenceHead: input.evidenceHead as string,\n rowHead: status.lastVerifiedCommit as string,\n classification: 'anchor-match',\n testGateReset: false,\n },\n });\n }\n\n return { landed: true, classification: 'anchor-match' };\n }\n\n // Evidence heads differ: classify and decide\n if (!workspacePath) {\n return { landed: false, reason: 'workspace-not-resolvable' };\n }\n\n // At this point we know evidenceHead and lastVerifiedCommit both exist (checked above)\n const classification = await classifyEvidenceAgainstAnchor(\n issueId,\n workspacePath,\n input.evidenceHead!,\n status.lastVerifiedCommit!,\n );\n\n if (classification === 'stale') {\n // Reject with event and activity entry\n const eventStore = getCloisterEventStore();\n if (eventStore) {\n eventStore.append({\n type: 'review.verdict_rejected' as const,\n timestamp: new Date().toISOString(),\n payload: {\n issueId,\n workspaceId: workspacePath ? workspacePath.split('/').pop() : undefined,\n writer: input.writer,\n verdict: input.verdict,\n evidenceHead: input.evidenceHead as string,\n rowHead: status.lastVerifiedCommit as string,\n reason: 'stale-evidence-head',\n },\n });\n }\n\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `[review-verdict-writer] Rejected stale evidence: writer=${input.writer} verdict=${input.verdict} evidence=${formatAnchorShort(input.evidenceHead)} row=${formatAnchorShort(status.lastVerifiedCommit)}`,\n });\n\n return { landed: false, reason: 'stale-evidence-head' };\n }\n\n // Fresh or indeterminate: land the verdict.\n // The re-gate keys off the test status this write would actually PERSIST, not\n // just the one already on the row. Orphan restore forwards a historical\n // terminal test result through `extra`, so a row sitting at 'pending' would\n // otherwise let that stale 'passed'/'skipped' land against fresh evidence and\n // advance without a new test run.\n const incomingTestStatus = input.extra?.['testStatus'];\n const effectiveTestStatus = incomingTestStatus ?? status.testStatus;\n const testGateReset = (effectiveTestStatus === 'passed' || effectiveTestStatus === 'skipped')\n && input.evidenceHead !== status.lastVerifiedCommit;\n const update: ReviewStatusUpdate = {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n ...(input.evidenceHead ? { reviewedAtCommit: input.evidenceHead as HeadAnchor } : {}),\n ...(input.extra ? { ...input.extra } : {}),\n // The re-gate lands LAST so no caller extra can overwrite it. Orphan restore\n // forwards the restored snapshot's historical testStatus/testNotes; letting\n // those win would make a newer reviewed head look verified with no new test\n // run — the \"admit a verdict that should not advance\" failure this door exists to prevent.\n ...(testGateReset ? { testStatus: 'pending', testNotes: `Verdict re-gated: evidence=${formatAnchorShort(input.evidenceHead)} row=${formatAnchorShort(status.lastVerifiedCommit)} writer=${input.writer}` } : {}),\n };\n\n setReviewStatusSync(issueId, update, status);\n\n const eventStore = getCloisterEventStore();\n if (eventStore) {\n eventStore.append({\n type: 'review.verdict_dispatched' as const,\n timestamp: new Date().toISOString(),\n payload: {\n issueId,\n workspaceId: workspacePath ? workspacePath.split('/').pop() : undefined,\n writer: input.writer,\n verdict: input.verdict,\n evidenceHead: input.evidenceHead as string,\n rowHead: status.lastVerifiedCommit as string,\n classification,\n testGateReset,\n },\n });\n }\n\n return { landed: true, classification: 'dispatched' };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"review-verdict-writer-DvFfKAn-.js","names":[],"sources":["../../src/lib/cloister/review-verdict-writer.ts"],"sourcesContent":["/**\n * The verdict write door — recordReviewVerdict + dispatch-not-drop + fallback kill-conditional\n *\n * Single source of truth for terminal review verdict writes. Every call site\n * that writes a final verdict (passed/blocked) routes through this door.\n *\n * Dispatch-not-drop rule: a terminal review verdict whose evidence head disagrees\n * with the row's lastVerifiedCommit is never silently discarded. Provably-stale\n * evidence is rejected WITH a review.verdict_rejected domain event and an\n * activity entry; anything else lands and re-gates.\n */\nimport { join } from 'path';\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { parseCompositeSnapshot, formatAnchorShort, type HeadAnchor } from '../git-utils.js';\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getCloisterEventStore } from './event-store-provider.js';\nimport { getReviewStatusSync, setReviewStatusSync, type ReviewStatus, type ReviewStatusUpdate } from '../review-status.js';\n\nconst execFileAsync = promisify(execFile);\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type VerdictWriter = 'coordinator' | 'fallback' | 'quick-signal' | 'orphan-restore' | 'sweeper-restore' | 'unsignaled-recovery' | 'infra-bypass' | 'dispatch-converge';\n\nexport type ReviewVerdict = 'passed' | 'blocked' | 'failed';\n\nexport interface VerdictInput {\n verdict: ReviewVerdict;\n notes?: string;\n evidenceHead?: HeadAnchor;\n extra?: Record<string, unknown>;\n runId?: string;\n writer: VerdictWriter;\n}\n\nexport type VerdictOutcome = { landed: true; classification: 'no-evidence' | 'anchor-match' | 'dispatched' } | { landed: false; reason: string };\n\n// ─── Private: Head Classification ─────────────────────────────────────────────\n\ntype EvidenceClassification = 'no-evidence' | 'anchor-match' | 'stale' | 'fresh' | 'indeterminate';\n\nfunction isNonAncestorExit(error: unknown): boolean {\n return typeof error === 'object'\n && error !== null\n && 'code' in error\n && error.code === 1;\n}\n\nasync function classifyEvidenceAgainstAnchor(\n issueId: string,\n workspacePath: string,\n evidenceHead: HeadAnchor | string,\n rowHead: HeadAnchor | string,\n): Promise<EvidenceClassification> {\n const evidenceMap = parseCompositeSnapshot(evidenceHead);\n const rowMap = parseCompositeSnapshot(rowHead);\n const evidenceIsComposite = evidenceMap.size > 0;\n const rowIsComposite = rowMap.size > 0;\n\n if (evidenceIsComposite !== rowIsComposite) {\n return 'indeterminate';\n }\n\n let repoRoots: Array<{ repoKey: string; dir: string }>;\n try {\n repoRoots = resolveWorkspaceRepoRootsSync(issueId, workspacePath);\n } catch {\n return 'indeterminate';\n }\n\n let comparisons: Array<{ dir: string; evidenceSha: string; rowSha: string }>;\n if (!evidenceIsComposite) {\n if (repoRoots.length !== 1) {\n return 'indeterminate';\n }\n comparisons = [{\n dir: repoRoots[0]!.dir,\n evidenceSha: evidenceHead,\n rowSha: rowHead,\n }];\n } else {\n const rootMap = new Map(repoRoots.map(root => [root.repoKey, root.dir]));\n if (evidenceMap.size !== rowMap.size || ![...evidenceMap.keys()].every(key => rowMap.has(key))) {\n return 'indeterminate';\n }\n\n comparisons = [];\n for (const [repoKey, evidenceSha] of evidenceMap) {\n const rowSha = rowMap.get(repoKey);\n const dir = rootMap.get(repoKey);\n if (!rowSha || !dir) {\n return 'indeterminate';\n }\n comparisons.push({ dir, evidenceSha, rowSha });\n }\n }\n\n let allStale = true;\n for (const comparison of comparisons) {\n try {\n await execFileAsync(\n 'git',\n ['merge-base', '--is-ancestor', comparison.evidenceSha, comparison.rowSha],\n { cwd: comparison.dir, timeout: 10_000 },\n );\n } catch (error: unknown) {\n if (isNonAncestorExit(error)) {\n allStale = false;\n } else {\n return 'indeterminate';\n }\n }\n }\n\n return allStale ? 'stale' : 'fresh';\n}\n\n// ─── Public: Verdict Write Door ───────────────────────────────────────────────\n\n/**\n * Record a terminal review verdict through the single write door.\n *\n * Returns {landed: true} if the verdict was written to the row, or\n * {landed: false; reason} if the verdict was rejected (stale evidence only).\n */\nexport async function recordReviewVerdict(issueId: string, input: VerdictInput): Promise<VerdictOutcome> {\n const status = getReviewStatusSync(issueId);\n if (!status) {\n setReviewStatusSync(issueId, {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n ...(input.extra ? { ...input.extra } : {}),\n });\n return { landed: true, classification: 'no-evidence' };\n }\n\n // Resolve workspace path\n const workspacePath = (() => {\n const resolved = resolveProjectFromIssueSync(issueId);\n return resolved ? join(resolved.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`) : null;\n })();\n\n // No evidence head or row head absent: take the no-evidence path\n if (!input.evidenceHead || !status.lastVerifiedCommit) {\n const update: ReviewStatusUpdate = {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n ...(input.extra ? { ...input.extra } : {}),\n };\n setReviewStatusSync(issueId, update, status);\n return { landed: true, classification: 'no-evidence' };\n }\n\n // Evidence heads equal: land the verdict without re-gating the same test result.\n if (input.evidenceHead === status.lastVerifiedCommit) {\n const update: ReviewStatusUpdate = {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n reviewedAtCommit: input.evidenceHead as HeadAnchor,\n ...(input.extra ? { ...input.extra } : {}),\n };\n setReviewStatusSync(issueId, update, status);\n\n const eventStore = getCloisterEventStore();\n if (eventStore) {\n eventStore.append({\n type: 'review.verdict_dispatched' as const,\n timestamp: new Date().toISOString(),\n payload: {\n issueId,\n workspaceId: workspacePath ? workspacePath.split('/').pop() : undefined,\n writer: input.writer,\n verdict: input.verdict,\n evidenceHead: input.evidenceHead as string,\n rowHead: status.lastVerifiedCommit as string,\n classification: 'anchor-match',\n testGateReset: false,\n },\n });\n }\n\n return { landed: true, classification: 'anchor-match' };\n }\n\n // Evidence heads differ: classify and decide\n if (!workspacePath) {\n return { landed: false, reason: 'workspace-not-resolvable' };\n }\n\n // At this point we know evidenceHead and lastVerifiedCommit both exist (checked above)\n const classification = await classifyEvidenceAgainstAnchor(\n issueId,\n workspacePath,\n input.evidenceHead!,\n status.lastVerifiedCommit!,\n );\n\n if (classification === 'stale') {\n // Reject with event and activity entry\n const eventStore = getCloisterEventStore();\n if (eventStore) {\n eventStore.append({\n type: 'review.verdict_rejected' as const,\n timestamp: new Date().toISOString(),\n payload: {\n issueId,\n workspaceId: workspacePath ? workspacePath.split('/').pop() : undefined,\n writer: input.writer,\n verdict: input.verdict,\n evidenceHead: input.evidenceHead as string,\n rowHead: status.lastVerifiedCommit as string,\n reason: 'stale-evidence-head',\n },\n });\n }\n\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `[review-verdict-writer] Rejected stale evidence: writer=${input.writer} verdict=${input.verdict} evidence=${formatAnchorShort(input.evidenceHead)} row=${formatAnchorShort(status.lastVerifiedCommit)}`,\n });\n\n return { landed: false, reason: 'stale-evidence-head' };\n }\n\n // Fresh or indeterminate: land the verdict.\n // The re-gate keys off the test status this write would actually PERSIST, not\n // just the one already on the row. Orphan restore forwards a historical\n // terminal test result through `extra`, so a row sitting at 'pending' would\n // otherwise let that stale 'passed'/'skipped' land against fresh evidence and\n // advance without a new test run.\n const incomingTestStatus = input.extra?.['testStatus'];\n const effectiveTestStatus = incomingTestStatus ?? status.testStatus;\n const testGateReset = (effectiveTestStatus === 'passed' || effectiveTestStatus === 'skipped')\n && input.evidenceHead !== status.lastVerifiedCommit;\n const update: ReviewStatusUpdate = {\n reviewStatus: input.verdict,\n reviewNotes: input.notes,\n ...(input.evidenceHead ? { reviewedAtCommit: input.evidenceHead as HeadAnchor } : {}),\n ...(input.extra ? { ...input.extra } : {}),\n // The re-gate lands LAST so no caller extra can overwrite it. Orphan restore\n // forwards the restored snapshot's historical testStatus/testNotes; letting\n // those win would make a newer reviewed head look verified with no new test\n // run — the \"admit a verdict that should not advance\" failure this door exists to prevent.\n ...(testGateReset ? { testStatus: 'pending', testNotes: `Verdict re-gated: evidence=${formatAnchorShort(input.evidenceHead)} row=${formatAnchorShort(status.lastVerifiedCommit)} writer=${input.writer}` } : {}),\n };\n\n setReviewStatusSync(issueId, update, status);\n\n const eventStore = getCloisterEventStore();\n if (eventStore) {\n eventStore.append({\n type: 'review.verdict_dispatched' as const,\n timestamp: new Date().toISOString(),\n payload: {\n issueId,\n workspaceId: workspacePath ? workspacePath.split('/').pop() : undefined,\n writer: input.writer,\n verdict: input.verdict,\n evidenceHead: input.evidenceHead as string,\n rowHead: status.lastVerifiedCommit as string,\n classification,\n testGateReset,\n },\n });\n }\n\n return { landed: true, classification: 'dispatched' };\n}\n"],"mappings":"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"}
|
package/dist/dashboard/{review-verdict-writer-BVco3WoI.js → review-verdict-writer-PcGBkvJ4.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as recordReviewVerdict } from "./review-verdict-writer-
|
|
1
|
+
import { t as recordReviewVerdict } from "./review-verdict-writer-DvFfKAn-.js";
|
|
2
2
|
export { recordReviewVerdict };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Dt as queueAutoCommit, Tt as init_auto_commit } from "./config-yaml-Dce9VUd6.js";
|
|
2
2
|
import { C as init_specs, N as getProjectPanPaths } from "./io-Cm4Sf_6P.js";
|
|
3
|
-
import { d as getReviewStatusSync, p as init_review_status } from "./review-status-
|
|
3
|
+
import { d as getReviewStatusSync, p as init_review_status } from "./review-status-yY5qt7Rl.js";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
//#region ../../lib/backlog/types.ts
|
|
@@ -267,4 +267,4 @@ function writeSequenceMd(projectRoot, doc, opts) {
|
|
|
267
267
|
//#endregion
|
|
268
268
|
export { writeSequenceMd as n, parseSequenceMd as t };
|
|
269
269
|
|
|
270
|
-
//# sourceMappingURL=sequence-io-
|
|
270
|
+
//# sourceMappingURL=sequence-io-DZyschsf.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequence-io-iljWlslY.js","names":[],"sources":["../../src/lib/backlog/types.ts","../../src/lib/backlog/sequence-io.ts"],"sourcesContent":["export type NodeSize = 'XS' | 'S' | 'M' | 'L' | 'XL';\nexport type NodeImportance = 'critical' | 'high' | 'medium' | 'low';\nexport type NodeCondition = 'ok' | 'needs-refinement' | 'stale';\nexport type NodeGate = 'auto' | 'ready' | 'blocked';\nexport type NodePlanning = 'skip' | 'auto' | 'interactive';\nexport type EdgeType = 'unblocks' | 'informs' | 'contains';\nexport type EdgeSource = 'github-ref' | 'operator' | 'ai-inferred';\nexport type PassMode = 'creation' | 'incremental' | 'review';\n\nexport type SequenceNode = {\n issue: string;\n rank: number;\n size: NodeSize;\n importance: NodeImportance;\n score: number;\n condition: NodeCondition;\n dependsOn: string[];\n why: string;\n rationale?: string;\n gate: NodeGate;\n planning: NodePlanning;\n /**\n * True when this issue is an epic (a container of child issues), not directly\n * workable. Epics are excluded from the auto-pickable queue; their children\n * carry the work. Membership is expressed via `contains` edges (epic → child).\n */\n isEpic?: boolean;\n};\n\nexport type SequenceEdge = {\n from: string;\n to: string;\n type: EdgeType;\n source: EdgeSource;\n confidence: number;\n};\n\nexport type SequenceDoc = {\n version: number;\n project: string;\n generatedAt: string;\n model: string;\n pass: PassMode;\n lastReviewPass?: string;\n openCount: number;\n nodes: SequenceNode[];\n edges: SequenceEdge[];\n};\n\nexport type SequenceParseError = { ok: false; error: string };\nexport type SequenceParseResult = { ok: true; doc: SequenceDoc } | SequenceParseError;\n\nconst NODE_SIZES = new Set<string>(['XS', 'S', 'M', 'L', 'XL']);\nconst NODE_IMPORTANCES = new Set<string>(['critical', 'high', 'medium', 'low']);\nconst NODE_CONDITIONS = new Set<string>(['ok', 'needs-refinement', 'stale']);\nconst NODE_GATES = new Set<string>(['auto', 'ready', 'blocked']);\nconst NODE_PLANNINGS = new Set<string>(['skip', 'auto', 'interactive']);\nconst EDGE_TYPES = new Set<string>(['unblocks', 'informs', 'contains']);\nconst EDGE_SOURCES = new Set<string>(['github-ref', 'operator', 'ai-inferred']);\nconst PASS_MODES = new Set<string>(['creation', 'incremental', 'review']);\n\nfunction err(msg: string): SequenceParseError {\n return { ok: false, error: msg };\n}\n\nfunction validateNode(n: Record<string, unknown>, i: number): { ok: true; node: SequenceNode } | SequenceParseError {\n if (typeof n.issue !== 'string' || !n.issue) return err(`nodes[${i}].issue missing`);\n if (typeof n.rank !== 'number') return err(`nodes[${i}].rank missing`);\n if (typeof n.size !== 'string' || !NODE_SIZES.has(n.size)) return err(`nodes[${i}].size invalid: ${n.size}`);\n if (typeof n.importance !== 'string' || !NODE_IMPORTANCES.has(n.importance)) return err(`nodes[${i}].importance invalid: ${n.importance}`);\n if (typeof n.score !== 'number' || n.score < 0 || n.score > 100) return err(`nodes[${i}].score must be 0-100: ${n.score}`);\n if (typeof n.condition !== 'string' || !NODE_CONDITIONS.has(n.condition)) return err(`nodes[${i}].condition invalid: ${n.condition}`);\n if (!Array.isArray(n.dependsOn) || !n.dependsOn.every((d) => typeof d === 'string')) return err(`nodes[${i}].dependsOn must be string[]`);\n if (typeof n.why !== 'string') return err(`nodes[${i}].why missing`);\n if (n.why.length > 140) return err(`nodes[${i}].why exceeds 140 chars`);\n if (typeof n.gate !== 'string' || !NODE_GATES.has(n.gate)) return err(`nodes[${i}].gate invalid: ${n.gate}`);\n if (typeof n.planning !== 'string' || !NODE_PLANNINGS.has(n.planning)) return err(`nodes[${i}].planning invalid: ${n.planning}`);\n if (n.rationale !== undefined && typeof n.rationale !== 'string') return err(`nodes[${i}].rationale must be string`);\n if (n.isEpic !== undefined && typeof n.isEpic !== 'boolean') return err(`nodes[${i}].isEpic must be boolean`);\n return {\n ok: true,\n node: {\n issue: n.issue,\n rank: n.rank,\n size: n.size as NodeSize,\n importance: n.importance as NodeImportance,\n score: n.score,\n condition: n.condition as NodeCondition,\n dependsOn: n.dependsOn as string[],\n why: n.why,\n rationale: n.rationale as string | undefined,\n gate: n.gate as NodeGate,\n planning: n.planning as NodePlanning,\n isEpic: typeof n.isEpic === 'boolean' ? n.isEpic : undefined,\n },\n };\n}\n\nfunction validateEdge(e: Record<string, unknown>, i: number): { ok: true; edge: SequenceEdge } | SequenceParseError {\n if (typeof e.from !== 'string') return err(`edges[${i}].from missing`);\n if (typeof e.to !== 'string') return err(`edges[${i}].to missing`);\n if (typeof e.type !== 'string' || !EDGE_TYPES.has(e.type)) return err(`edges[${i}].type invalid: ${e.type}`);\n if (typeof e.source !== 'string' || !EDGE_SOURCES.has(e.source)) return err(`edges[${i}].source invalid: ${e.source}`);\n if (typeof e.confidence !== 'number' || e.confidence < 0 || e.confidence > 1) return err(`edges[${i}].confidence must be 0-1: ${e.confidence}`);\n return { ok: true, edge: { from: e.from, to: e.to, type: e.type as EdgeType, source: e.source as EdgeSource, confidence: e.confidence } };\n}\n\nexport function parseSequenceJson(value: unknown): SequenceParseResult {\n if (typeof value !== 'object' || value === null) return err('value must be an object');\n const v = value as Record<string, unknown>;\n\n if (typeof v.version !== 'number') return err('version missing');\n if (typeof v.project !== 'string') return err('project missing');\n if (typeof v.generatedAt !== 'string') return err('generatedAt missing');\n if (typeof v.model !== 'string') return err('model missing');\n if (typeof v.pass !== 'string' || !PASS_MODES.has(v.pass)) return err(`pass invalid: ${v.pass}`);\n if (typeof v.openCount !== 'number') return err('openCount missing');\n if (!Array.isArray(v.nodes)) return err('nodes must be an array');\n if (!Array.isArray(v.edges)) return err('edges must be an array');\n\n const nodes: SequenceNode[] = [];\n for (let i = 0; i < v.nodes.length; i++) {\n const result = validateNode(v.nodes[i] as Record<string, unknown>, i);\n if (!result.ok) return result;\n nodes.push(result.node);\n }\n\n const edges: SequenceEdge[] = [];\n for (let i = 0; i < v.edges.length; i++) {\n const result = validateEdge(v.edges[i] as Record<string, unknown>, i);\n if (!result.ok) return result;\n edges.push(result.edge);\n }\n\n return {\n ok: true,\n doc: {\n version: v.version,\n project: v.project,\n generatedAt: v.generatedAt,\n model: v.model,\n pass: v.pass as PassMode,\n lastReviewPass: typeof v.lastReviewPass === 'string' ? v.lastReviewPass : undefined,\n openCount: v.openCount,\n nodes,\n edges,\n },\n };\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { parseSequenceJson } from './types.js';\nimport type { SequenceDoc, SequenceEdge, SequenceNode, SequenceParseError } from './types.js';\nimport { queueAutoCommit } from '../pan-dir/auto-commit.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { getProjectPanPaths } from '../pan-dir/specs.js';\n\nconst MACHINE_MARKER = '<!-- machine-readable; do not hand-edit below this line -->';\nconst TOP_TIER_SIZE = 80;\nfunction sequencePath(projectRoot: string): string {\n return join(getProjectPanPaths(projectRoot).panDir, 'backlog', 'sequence.md');\n}\n\nexport type ParseSequenceMdResult = { ok: true; doc: SequenceDoc } | SequenceParseError;\n\nexport function parseSequenceMd(markdown: string): ParseSequenceMdResult {\n const markerIdx = markdown.indexOf(MACHINE_MARKER);\n const searchText = markerIdx >= 0 ? markdown.slice(markerIdx) : markdown;\n\n const fenceMatch = searchText.match(/```json\\s*\\n([\\s\\S]*?)```/);\n if (!fenceMatch) {\n return { ok: false, error: 'No fenced JSON block found in sequence.md' };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fenceMatch[1]);\n } catch (e) {\n return { ok: false, error: `JSON parse failed: ${e instanceof Error ? e.message : String(e)}` };\n }\n\n return parseSequenceJson(parsed);\n}\n\nfunction renderTable(nodes: SequenceNode[]): string {\n const header = '| rank | issue | size | importance | condition | epic | depends-on | why |';\n const sep = '|------|-------|------|------------|-----------|------|------------|-----|';\n const rows = nodes.map((n) =>\n `| ${n.rank} | ${n.issue} | ${n.size} | ${n.importance} | ${n.condition} | ${n.isEpic ? '✓' : ''} | ${n.dependsOn.join(', ')} | ${n.why} |`\n );\n return [header, sep, ...rows].join('\\n');\n}\n\nfunction renderRationaleSection(nodes: SequenceNode[]): string {\n const topTier = nodes.slice(0, TOP_TIER_SIZE).filter((n) => n.rationale);\n if (topTier.length === 0) return '';\n const lines = ['## Rationale detail', ''];\n for (const n of topTier) {\n lines.push(`### ${n.issue} (rank ${n.rank})`, '', n.rationale!, '');\n }\n return lines.join('\\n');\n}\n\nexport interface WriteSequenceMdOpts {\n /**\n * Set to true when the caller is an operator-facing route that has already\n * applied the intended field value to the doc. Skips the prior-file\n * preservation step so operators can reset gate/planning back to 'auto'.\n *\n * When false/absent (AI resequence path), prior non-'auto' operator fields\n * and in-pipeline ranks are carried forward from the on-disk file (FR-13,\n * FR-15, FR-16, FR-17).\n */\n operatorEdit?: boolean;\n}\n\n/**\n * Write sequence.md, merging operator-owned state from the prior file.\n *\n * Merge rules for AI resequence (operatorEdit absent/false):\n * - gate !== 'auto' in prior → carry forward (operator-set)\n * - planning !== 'auto' in prior → carry forward (operator-set)\n * - in-pipeline issues (live workspace or non-pending review) → carry forward rank/why/rationale\n * - edges with source === 'operator' in prior → always preserved verbatim\n *\n * For explicit operator edits (operatorEdit: true), the doc already contains\n * the correct operator-intended value; prior-preservation is skipped entirely.\n */\nexport function writeSequenceMd(projectRoot: string, doc: SequenceDoc, opts?: WriteSequenceMdOpts): void {\n const outPath = sequencePath(projectRoot);\n\n // Load prior sequence for merge-preservation (AI resequence path only)\n let priorNodeMap = new Map<string, SequenceNode>();\n let priorOperatorEdges: SequenceEdge[] = [];\n if (!opts?.operatorEdit && existsSync(outPath)) {\n const priorText = readFileSync(outPath, 'utf-8');\n const prior = parseSequenceMd(priorText);\n if (prior.ok) {\n priorNodeMap = new Map(prior.doc.nodes.map((n) => [n.issue, n]));\n priorOperatorEdges = prior.doc.edges.filter((e) => e.source === 'operator');\n }\n }\n\n // Detect in-pipeline (pinned) issues: live workspace dir OR non-pending review status\n const workspacesDir = join(projectRoot, 'workspaces');\n const isPinned = (issueId: string): boolean => {\n const rs = getReviewStatusSync(issueId.toUpperCase());\n if (rs && rs.reviewStatus !== 'pending') return true;\n return existsSync(join(workspacesDir, `feature-${issueId.toLowerCase()}`));\n };\n\n // Merge operator-owned fields into each node\n const mergedNodes: SequenceNode[] = doc.nodes.map((node) => {\n const prior = priorNodeMap.get(node.issue);\n if (!prior) return node;\n // Preserve operator-set gate and planning\n const gate = prior.gate !== 'auto' ? prior.gate : node.gate;\n const planning = prior.planning !== 'auto' ? prior.planning : node.planning;\n // Preserve rank/why/rationale for pinned (in-pipeline) issues\n if (isPinned(node.issue)) {\n return { ...node, gate, planning, rank: prior.rank, why: prior.why, rationale: prior.rationale };\n }\n return { ...node, gate, planning };\n });\n\n // Merge operator edges: union of prior operator edges and current doc's operator edges\n // (prior ones are kept even if the sequencer dropped them; new ones from doc are added).\n const currentOperatorEdges = doc.edges.filter((e) => e.source === 'operator');\n const currentNonOperatorEdges = doc.edges.filter((e) => e.source !== 'operator');\n const operatorEdgeKeys = new Set<string>();\n const mergedOperatorEdges: SequenceEdge[] = [];\n for (const edge of [...priorOperatorEdges, ...currentOperatorEdges]) {\n const key = `${edge.from}→${edge.to}→${edge.type}`;\n if (!operatorEdgeKeys.has(key)) {\n operatorEdgeKeys.add(key);\n mergedOperatorEdges.push(edge);\n }\n }\n const mergedEdges: SequenceEdge[] = [...mergedOperatorEdges, ...currentNonOperatorEdges];\n\n const mergedDoc: SequenceDoc = { ...doc, nodes: mergedNodes, edges: mergedEdges };\n const sortedNodes = [...mergedDoc.nodes].sort((a, b) => a.rank - b.rank);\n const ts = mergedDoc.generatedAt;\n const header = `# Backlog Sequence\\n\\n_Last sequenced: ${ts} · model: ${mergedDoc.model} · open: ${mergedDoc.openCount}_\\n`;\n const table = renderTable(sortedNodes);\n const rationale = renderRationaleSection(sortedNodes);\n const machineBlock = `${MACHINE_MARKER}\\n\\n\\`\\`\\`json\\n${JSON.stringify(mergedDoc, null, 2)}\\n\\`\\`\\`\\n`;\n\n const parts = [header, table];\n if (rationale) parts.push(rationale);\n parts.push(machineBlock);\n\n const content = parts.join('\\n\\n');\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, content, 'utf-8');\n\n queueAutoCommit({\n projectRoot,\n paths: [outPath],\n subject: `chore(state): update backlog sequence (${doc.project})`,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sequence-io-DZyschsf.js","names":[],"sources":["../../src/lib/backlog/types.ts","../../src/lib/backlog/sequence-io.ts"],"sourcesContent":["export type NodeSize = 'XS' | 'S' | 'M' | 'L' | 'XL';\nexport type NodeImportance = 'critical' | 'high' | 'medium' | 'low';\nexport type NodeCondition = 'ok' | 'needs-refinement' | 'stale';\nexport type NodeGate = 'auto' | 'ready' | 'blocked';\nexport type NodePlanning = 'skip' | 'auto' | 'interactive';\nexport type EdgeType = 'unblocks' | 'informs' | 'contains';\nexport type EdgeSource = 'github-ref' | 'operator' | 'ai-inferred';\nexport type PassMode = 'creation' | 'incremental' | 'review';\n\nexport type SequenceNode = {\n issue: string;\n rank: number;\n size: NodeSize;\n importance: NodeImportance;\n score: number;\n condition: NodeCondition;\n dependsOn: string[];\n why: string;\n rationale?: string;\n gate: NodeGate;\n planning: NodePlanning;\n /**\n * True when this issue is an epic (a container of child issues), not directly\n * workable. Epics are excluded from the auto-pickable queue; their children\n * carry the work. Membership is expressed via `contains` edges (epic → child).\n */\n isEpic?: boolean;\n};\n\nexport type SequenceEdge = {\n from: string;\n to: string;\n type: EdgeType;\n source: EdgeSource;\n confidence: number;\n};\n\nexport type SequenceDoc = {\n version: number;\n project: string;\n generatedAt: string;\n model: string;\n pass: PassMode;\n lastReviewPass?: string;\n openCount: number;\n nodes: SequenceNode[];\n edges: SequenceEdge[];\n};\n\nexport type SequenceParseError = { ok: false; error: string };\nexport type SequenceParseResult = { ok: true; doc: SequenceDoc } | SequenceParseError;\n\nconst NODE_SIZES = new Set<string>(['XS', 'S', 'M', 'L', 'XL']);\nconst NODE_IMPORTANCES = new Set<string>(['critical', 'high', 'medium', 'low']);\nconst NODE_CONDITIONS = new Set<string>(['ok', 'needs-refinement', 'stale']);\nconst NODE_GATES = new Set<string>(['auto', 'ready', 'blocked']);\nconst NODE_PLANNINGS = new Set<string>(['skip', 'auto', 'interactive']);\nconst EDGE_TYPES = new Set<string>(['unblocks', 'informs', 'contains']);\nconst EDGE_SOURCES = new Set<string>(['github-ref', 'operator', 'ai-inferred']);\nconst PASS_MODES = new Set<string>(['creation', 'incremental', 'review']);\n\nfunction err(msg: string): SequenceParseError {\n return { ok: false, error: msg };\n}\n\nfunction validateNode(n: Record<string, unknown>, i: number): { ok: true; node: SequenceNode } | SequenceParseError {\n if (typeof n.issue !== 'string' || !n.issue) return err(`nodes[${i}].issue missing`);\n if (typeof n.rank !== 'number') return err(`nodes[${i}].rank missing`);\n if (typeof n.size !== 'string' || !NODE_SIZES.has(n.size)) return err(`nodes[${i}].size invalid: ${n.size}`);\n if (typeof n.importance !== 'string' || !NODE_IMPORTANCES.has(n.importance)) return err(`nodes[${i}].importance invalid: ${n.importance}`);\n if (typeof n.score !== 'number' || n.score < 0 || n.score > 100) return err(`nodes[${i}].score must be 0-100: ${n.score}`);\n if (typeof n.condition !== 'string' || !NODE_CONDITIONS.has(n.condition)) return err(`nodes[${i}].condition invalid: ${n.condition}`);\n if (!Array.isArray(n.dependsOn) || !n.dependsOn.every((d) => typeof d === 'string')) return err(`nodes[${i}].dependsOn must be string[]`);\n if (typeof n.why !== 'string') return err(`nodes[${i}].why missing`);\n if (n.why.length > 140) return err(`nodes[${i}].why exceeds 140 chars`);\n if (typeof n.gate !== 'string' || !NODE_GATES.has(n.gate)) return err(`nodes[${i}].gate invalid: ${n.gate}`);\n if (typeof n.planning !== 'string' || !NODE_PLANNINGS.has(n.planning)) return err(`nodes[${i}].planning invalid: ${n.planning}`);\n if (n.rationale !== undefined && typeof n.rationale !== 'string') return err(`nodes[${i}].rationale must be string`);\n if (n.isEpic !== undefined && typeof n.isEpic !== 'boolean') return err(`nodes[${i}].isEpic must be boolean`);\n return {\n ok: true,\n node: {\n issue: n.issue,\n rank: n.rank,\n size: n.size as NodeSize,\n importance: n.importance as NodeImportance,\n score: n.score,\n condition: n.condition as NodeCondition,\n dependsOn: n.dependsOn as string[],\n why: n.why,\n rationale: n.rationale as string | undefined,\n gate: n.gate as NodeGate,\n planning: n.planning as NodePlanning,\n isEpic: typeof n.isEpic === 'boolean' ? n.isEpic : undefined,\n },\n };\n}\n\nfunction validateEdge(e: Record<string, unknown>, i: number): { ok: true; edge: SequenceEdge } | SequenceParseError {\n if (typeof e.from !== 'string') return err(`edges[${i}].from missing`);\n if (typeof e.to !== 'string') return err(`edges[${i}].to missing`);\n if (typeof e.type !== 'string' || !EDGE_TYPES.has(e.type)) return err(`edges[${i}].type invalid: ${e.type}`);\n if (typeof e.source !== 'string' || !EDGE_SOURCES.has(e.source)) return err(`edges[${i}].source invalid: ${e.source}`);\n if (typeof e.confidence !== 'number' || e.confidence < 0 || e.confidence > 1) return err(`edges[${i}].confidence must be 0-1: ${e.confidence}`);\n return { ok: true, edge: { from: e.from, to: e.to, type: e.type as EdgeType, source: e.source as EdgeSource, confidence: e.confidence } };\n}\n\nexport function parseSequenceJson(value: unknown): SequenceParseResult {\n if (typeof value !== 'object' || value === null) return err('value must be an object');\n const v = value as Record<string, unknown>;\n\n if (typeof v.version !== 'number') return err('version missing');\n if (typeof v.project !== 'string') return err('project missing');\n if (typeof v.generatedAt !== 'string') return err('generatedAt missing');\n if (typeof v.model !== 'string') return err('model missing');\n if (typeof v.pass !== 'string' || !PASS_MODES.has(v.pass)) return err(`pass invalid: ${v.pass}`);\n if (typeof v.openCount !== 'number') return err('openCount missing');\n if (!Array.isArray(v.nodes)) return err('nodes must be an array');\n if (!Array.isArray(v.edges)) return err('edges must be an array');\n\n const nodes: SequenceNode[] = [];\n for (let i = 0; i < v.nodes.length; i++) {\n const result = validateNode(v.nodes[i] as Record<string, unknown>, i);\n if (!result.ok) return result;\n nodes.push(result.node);\n }\n\n const edges: SequenceEdge[] = [];\n for (let i = 0; i < v.edges.length; i++) {\n const result = validateEdge(v.edges[i] as Record<string, unknown>, i);\n if (!result.ok) return result;\n edges.push(result.edge);\n }\n\n return {\n ok: true,\n doc: {\n version: v.version,\n project: v.project,\n generatedAt: v.generatedAt,\n model: v.model,\n pass: v.pass as PassMode,\n lastReviewPass: typeof v.lastReviewPass === 'string' ? v.lastReviewPass : undefined,\n openCount: v.openCount,\n nodes,\n edges,\n },\n };\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { parseSequenceJson } from './types.js';\nimport type { SequenceDoc, SequenceEdge, SequenceNode, SequenceParseError } from './types.js';\nimport { queueAutoCommit } from '../pan-dir/auto-commit.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { getProjectPanPaths } from '../pan-dir/specs.js';\n\nconst MACHINE_MARKER = '<!-- machine-readable; do not hand-edit below this line -->';\nconst TOP_TIER_SIZE = 80;\nfunction sequencePath(projectRoot: string): string {\n return join(getProjectPanPaths(projectRoot).panDir, 'backlog', 'sequence.md');\n}\n\nexport type ParseSequenceMdResult = { ok: true; doc: SequenceDoc } | SequenceParseError;\n\nexport function parseSequenceMd(markdown: string): ParseSequenceMdResult {\n const markerIdx = markdown.indexOf(MACHINE_MARKER);\n const searchText = markerIdx >= 0 ? markdown.slice(markerIdx) : markdown;\n\n const fenceMatch = searchText.match(/```json\\s*\\n([\\s\\S]*?)```/);\n if (!fenceMatch) {\n return { ok: false, error: 'No fenced JSON block found in sequence.md' };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fenceMatch[1]);\n } catch (e) {\n return { ok: false, error: `JSON parse failed: ${e instanceof Error ? e.message : String(e)}` };\n }\n\n return parseSequenceJson(parsed);\n}\n\nfunction renderTable(nodes: SequenceNode[]): string {\n const header = '| rank | issue | size | importance | condition | epic | depends-on | why |';\n const sep = '|------|-------|------|------------|-----------|------|------------|-----|';\n const rows = nodes.map((n) =>\n `| ${n.rank} | ${n.issue} | ${n.size} | ${n.importance} | ${n.condition} | ${n.isEpic ? '✓' : ''} | ${n.dependsOn.join(', ')} | ${n.why} |`\n );\n return [header, sep, ...rows].join('\\n');\n}\n\nfunction renderRationaleSection(nodes: SequenceNode[]): string {\n const topTier = nodes.slice(0, TOP_TIER_SIZE).filter((n) => n.rationale);\n if (topTier.length === 0) return '';\n const lines = ['## Rationale detail', ''];\n for (const n of topTier) {\n lines.push(`### ${n.issue} (rank ${n.rank})`, '', n.rationale!, '');\n }\n return lines.join('\\n');\n}\n\nexport interface WriteSequenceMdOpts {\n /**\n * Set to true when the caller is an operator-facing route that has already\n * applied the intended field value to the doc. Skips the prior-file\n * preservation step so operators can reset gate/planning back to 'auto'.\n *\n * When false/absent (AI resequence path), prior non-'auto' operator fields\n * and in-pipeline ranks are carried forward from the on-disk file (FR-13,\n * FR-15, FR-16, FR-17).\n */\n operatorEdit?: boolean;\n}\n\n/**\n * Write sequence.md, merging operator-owned state from the prior file.\n *\n * Merge rules for AI resequence (operatorEdit absent/false):\n * - gate !== 'auto' in prior → carry forward (operator-set)\n * - planning !== 'auto' in prior → carry forward (operator-set)\n * - in-pipeline issues (live workspace or non-pending review) → carry forward rank/why/rationale\n * - edges with source === 'operator' in prior → always preserved verbatim\n *\n * For explicit operator edits (operatorEdit: true), the doc already contains\n * the correct operator-intended value; prior-preservation is skipped entirely.\n */\nexport function writeSequenceMd(projectRoot: string, doc: SequenceDoc, opts?: WriteSequenceMdOpts): void {\n const outPath = sequencePath(projectRoot);\n\n // Load prior sequence for merge-preservation (AI resequence path only)\n let priorNodeMap = new Map<string, SequenceNode>();\n let priorOperatorEdges: SequenceEdge[] = [];\n if (!opts?.operatorEdit && existsSync(outPath)) {\n const priorText = readFileSync(outPath, 'utf-8');\n const prior = parseSequenceMd(priorText);\n if (prior.ok) {\n priorNodeMap = new Map(prior.doc.nodes.map((n) => [n.issue, n]));\n priorOperatorEdges = prior.doc.edges.filter((e) => e.source === 'operator');\n }\n }\n\n // Detect in-pipeline (pinned) issues: live workspace dir OR non-pending review status\n const workspacesDir = join(projectRoot, 'workspaces');\n const isPinned = (issueId: string): boolean => {\n const rs = getReviewStatusSync(issueId.toUpperCase());\n if (rs && rs.reviewStatus !== 'pending') return true;\n return existsSync(join(workspacesDir, `feature-${issueId.toLowerCase()}`));\n };\n\n // Merge operator-owned fields into each node\n const mergedNodes: SequenceNode[] = doc.nodes.map((node) => {\n const prior = priorNodeMap.get(node.issue);\n if (!prior) return node;\n // Preserve operator-set gate and planning\n const gate = prior.gate !== 'auto' ? prior.gate : node.gate;\n const planning = prior.planning !== 'auto' ? prior.planning : node.planning;\n // Preserve rank/why/rationale for pinned (in-pipeline) issues\n if (isPinned(node.issue)) {\n return { ...node, gate, planning, rank: prior.rank, why: prior.why, rationale: prior.rationale };\n }\n return { ...node, gate, planning };\n });\n\n // Merge operator edges: union of prior operator edges and current doc's operator edges\n // (prior ones are kept even if the sequencer dropped them; new ones from doc are added).\n const currentOperatorEdges = doc.edges.filter((e) => e.source === 'operator');\n const currentNonOperatorEdges = doc.edges.filter((e) => e.source !== 'operator');\n const operatorEdgeKeys = new Set<string>();\n const mergedOperatorEdges: SequenceEdge[] = [];\n for (const edge of [...priorOperatorEdges, ...currentOperatorEdges]) {\n const key = `${edge.from}→${edge.to}→${edge.type}`;\n if (!operatorEdgeKeys.has(key)) {\n operatorEdgeKeys.add(key);\n mergedOperatorEdges.push(edge);\n }\n }\n const mergedEdges: SequenceEdge[] = [...mergedOperatorEdges, ...currentNonOperatorEdges];\n\n const mergedDoc: SequenceDoc = { ...doc, nodes: mergedNodes, edges: mergedEdges };\n const sortedNodes = [...mergedDoc.nodes].sort((a, b) => a.rank - b.rank);\n const ts = mergedDoc.generatedAt;\n const header = `# Backlog Sequence\\n\\n_Last sequenced: ${ts} · model: ${mergedDoc.model} · open: ${mergedDoc.openCount}_\\n`;\n const table = renderTable(sortedNodes);\n const rationale = renderRationaleSection(sortedNodes);\n const machineBlock = `${MACHINE_MARKER}\\n\\n\\`\\`\\`json\\n${JSON.stringify(mergedDoc, null, 2)}\\n\\`\\`\\`\\n`;\n\n const parts = [header, table];\n if (rationale) parts.push(rationale);\n parts.push(machineBlock);\n\n const content = parts.join('\\n\\n');\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, content, 'utf-8');\n\n queueAutoCommit({\n projectRoot,\n paths: [outPath],\n subject: `chore(state): update backlog sequence (${doc.project})`,\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { a as __toCommonJS } from "./rolldown-runtime-DNCzzKnI.js";
|
|
2
|
-
import { Mn as stopAgent, Rn as listRunningAgentsSync, W as getAgentRuntimeStateSync, g as getAgentStateSync, ln as spawnRun, q as init_agents
|
|
2
|
+
import { Mn as stopAgent, Rn as listRunningAgentsSync, W as getAgentRuntimeStateSync, Yi as determineModel, g as getAgentStateSync, ln as spawnRun, q as init_agents } from "./agent-state-B196fj1h.js";
|
|
3
3
|
import { N as getProjectPanPaths, P as init_paths } from "./io-Cm4Sf_6P.js";
|
|
4
|
-
import { i as issue_service_singleton_exports, n as init_issue_service_singleton } from "./issue-service-singleton-
|
|
5
|
-
import { d as getReviewStatusSync, p as init_review_status } from "./review-status-
|
|
6
|
-
import { t as parseSequenceMd } from "./sequence-io-
|
|
4
|
+
import { i as issue_service_singleton_exports, n as init_issue_service_singleton } from "./issue-service-singleton-KcRrOiFD.js";
|
|
5
|
+
import { d as getReviewStatusSync, p as init_review_status } from "./review-status-yY5qt7Rl.js";
|
|
6
|
+
import { t as parseSequenceMd } from "./sequence-io-DZyschsf.js";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { Effect } from "effect";
|
|
9
9
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
@@ -327,4 +327,4 @@ ${commonRules}`;
|
|
|
327
327
|
//#endregion
|
|
328
328
|
export { spawnSequencerAgent as i, clearFinishedSequencerRun as n, getSequencerRunStatus as r, SEQUENCER_AGENT_ID as t };
|
|
329
329
|
|
|
330
|
-
//# sourceMappingURL=sequencer-agent-
|
|
330
|
+
//# sourceMappingURL=sequencer-agent-BXDw5cNx.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequencer-agent-BGGVKxtw.js","names":[],"sources":["../../src/lib/backlog/backlog-input.ts","../../src/lib/backlog/sequencer-agent.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { getProjectPanPaths } from '../pan-dir/paths.js';\nimport type { Issue, IssueState, TrackerType } from '../tracker/interface.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { parseSequenceMd } from './sequence-io.js';\nimport type { SequenceDoc } from './types.js';\n\nexport type BacklogManifestEntry = {\n id: string;\n title: string;\n labels: string[];\n priority: number | undefined;\n ageMs: number;\n inPipeline: boolean;\n hasPrd: boolean;\n ready: boolean;\n updatedAt: string;\n /** Epic container (`[EPIC]` title prefix or `epic` label) — not directly workable. */\n isEpic: boolean;\n /** Epic this issue declares membership in via \"Part of #N\" in its body, if any. */\n partOf?: string;\n};\n\nexport type BatchedBodyAccessor = {\n count: number;\n getBatch(batchIndex: number, batchSize: number): Array<{ id: string; body: string }>;\n};\n\nexport type CollectOpenBacklogResult = {\n manifest: BacklogManifestEntry[];\n bodies: BatchedBodyAccessor;\n priorSequence: SequenceDoc | null;\n};\n\nconst SEQUENCE_MD_PATH = '.pan/backlog/sequence.md';\nconst CLOSED_STATES = new Set(['closed', 'cancelled']);\n\n/**\n * Map a dashboard canonical status (or a raw tracker status) onto the\n * tracker-library {@link IssueState} union that {@link collectOpenBacklog}\n * expects. Dashboard read-model issues carry `canonicalStatus`/`state` values\n * like `done`/`canceled`/`verifying_on_main`; the backlog library only knows\n * `open`/`in_progress`/`in_review`/`closed`.\n */\nfunction canonicalToIssueState(raw: string): IssueState {\n const c = raw.toLowerCase();\n if (['done', 'closed', 'completed', 'canceled', 'cancelled', 'duplicate', 'wontfix'].includes(c)) return 'closed';\n if (['in_review', 'in review', 'review', 'qa', 'testing'].includes(c)) return 'in_review';\n if (['in_progress', 'in progress', 'started', 'active', 'verifying', 'verifying_on_main', 'verifying on main'].includes(c)) return 'in_progress';\n return 'open'; // backlog, todo, ready, triage, unknown, ''\n}\n\n/**\n * Adapt the dashboard read-model issue objects returned by\n * `getSharedIssueService().getIssues()` into tracker-library {@link Issue}\n * objects. The two shapes diverge: the dashboard keys the human ref as\n * `identifier` (e.g. \"PAN-302\") with no `ref` field, and its `state` is a\n * canonical dashboard status, not an {@link IssueState}. Feeding the raw\n * dashboard objects straight into {@link collectOpenBacklog} made\n * `getReviewStatusSync(issue.ref)` receive `undefined` and crash on\n * `undefined.toUpperCase()` — so every sequencer pass died before spawning.\n *\n * Issues without a usable human ref are dropped (they cannot be ranked).\n */\nexport function normalizeBacklogIssues(\n raw: ReadonlyArray<Record<string, unknown>>,\n): Issue[] {\n const out: Issue[] = [];\n for (const r of raw) {\n const ref = String(r['identifier'] ?? r['ref'] ?? '').trim();\n if (!ref) continue;\n const stateRaw = String(r['canonicalStatus'] ?? r['state'] ?? r['status'] ?? '');\n out.push({\n id: String(r['id'] ?? ref),\n ref,\n title: String(r['title'] ?? ''),\n description: String(r['description'] ?? ''),\n state: canonicalToIssueState(stateRaw),\n labels: Array.isArray(r['labels']) ? (r['labels'] as string[]) : [],\n url: String(r['url'] ?? ''),\n tracker: (typeof r['source'] === 'string' ? r['source'] : 'github') as TrackerType,\n priority: typeof r['priority'] === 'number' ? r['priority'] : undefined,\n createdAt: String(r['createdAt'] ?? ''),\n updatedAt: String(r['updatedAt'] ?? ''),\n });\n }\n return out;\n}\n\nconst EPIC_TITLE_RE = /^\\s*\\[EPIC\\]/i;\n\n/** An issue is an epic if its title is prefixed `[EPIC]` or it carries the `epic` label. */\nexport function detectIsEpic(title: string, labels: readonly string[]): boolean {\n return EPIC_TITLE_RE.test(title) || labels.some((l) => l.toLowerCase() === 'epic');\n}\n\n/**\n * Parse an explicit epic-membership declaration (\"Part of #N\") from an issue body.\n * Returns the epic's ref using the child's own prefix (PAN-2076 body \"Part of #2075\"\n * → \"PAN-2075\"), or undefined if none is declared or it points at the issue itself.\n */\nexport function detectPartOf(childRef: string, body: string): string | undefined {\n const m = /\\bpart of\\s+#(\\d+)\\b/i.exec(body);\n if (!m) return undefined;\n const prefix = childRef.split('-')[0];\n if (!prefix) return undefined;\n const epicRef = `${prefix}-${m[1]}`;\n return epicRef === childRef.toUpperCase() ? undefined : epicRef;\n}\n\nexport async function collectOpenBacklog(\n projectRoot: string,\n issues: Issue[],\n opts?: {\n hasPrdFn?: (issueId: string) => boolean;\n hasSpecFn?: (issueId: string) => boolean;\n },\n): Promise<CollectOpenBacklogResult> {\n const now = Date.now();\n\n const openIssues = issues.filter((issue) => !CLOSED_STATES.has(issue.state));\n\n // Build per-issue lookup sets once (filesystem scans are cheap when batched).\n const paths = getProjectPanPaths(projectRoot);\n const specsDir = paths.specsDir;\n const workspacesDir = join(projectRoot, 'workspaces');\n const issuesWithSpecs = new Set<string>();\n if (!opts?.hasSpecFn) {\n if (existsSync(specsDir)) {\n for (const f of readdirSync(specsDir)) {\n const match = /^[\\d-]+-([A-Z]+-\\d+)-/i.exec(f);\n if (match) issuesWithSpecs.add(match[1]!.toUpperCase());\n }\n }\n }\n\n const manifest: BacklogManifestEntry[] = openIssues.map((issue) => {\n const reviewStatus = getReviewStatusSync(issue.ref);\n const inPipeline =\n (reviewStatus !== null && reviewStatus.reviewStatus !== 'pending') ||\n existsSync(join(workspacesDir, `feature-${issue.ref.toLowerCase()}`));\n\n const hasPrd = opts?.hasPrdFn\n ? opts.hasPrdFn(issue.ref)\n : existsSync(join(paths.draftsDir, `${issue.ref.toLowerCase()}.md`)) ||\n existsSync(join(paths.draftsDir, `${issue.ref.toUpperCase()}.md`));\n\n const ready = opts?.hasSpecFn\n ? opts.hasSpecFn(issue.ref)\n : issuesWithSpecs.has(issue.ref.toUpperCase());\n\n const createdMs = issue.createdAt ? new Date(issue.createdAt).getTime() : now;\n\n return {\n id: issue.ref,\n title: issue.title,\n labels: issue.labels,\n priority: issue.priority,\n ageMs: now - createdMs,\n inPipeline,\n hasPrd,\n ready,\n updatedAt: issue.updatedAt,\n isEpic: detectIsEpic(issue.title, issue.labels),\n partOf: detectPartOf(issue.ref, issue.description),\n };\n });\n\n const bodies: BatchedBodyAccessor = {\n count: openIssues.length,\n getBatch(batchIndex: number, batchSize: number) {\n const start = batchIndex * batchSize;\n const batch = openIssues.slice(start, start + batchSize);\n return batch.map((issue) => ({ id: issue.ref, body: issue.description }));\n },\n };\n\n const sequencePath = join(projectRoot, SEQUENCE_MD_PATH);\n let priorSequence: SequenceDoc | null = null;\n if (existsSync(sequencePath)) {\n const md = readFileSync(sequencePath, 'utf-8');\n const result = parseSequenceMd(md);\n if (result.ok) {\n priorSequence = result.doc;\n }\n }\n\n return { manifest, bodies, priorSequence };\n}\n","import { existsSync, statSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport type { AgentState } from '../agents.js';\nimport { Effect } from 'effect';\nimport {\n spawnRun,\n determineModel,\n listRunningAgentsSync,\n getAgentStateSync,\n getAgentRuntimeStateSync,\n stopAgent,\n} from '../agents.js';\nimport { collectOpenBacklog, normalizeBacklogIssues } from './backlog-input.js';\nimport type { PassMode } from './types.js';\nimport type { CollectOpenBacklogResult } from './backlog-input.js';\n\nexport const SEQUENCER_AGENT_ID = 'sequencer-runner';\n\nexport type SequencerRunStatus = {\n alive: boolean;\n running: boolean;\n done: boolean;\n startedAt: string | null;\n doneReason: 'fresh-sequence' | 'idle' | null;\n};\n\nexport type SpawnSequencerOptions = {\n projectRoot?: string;\n projectKey?: string;\n model?: string;\n workspace?: string;\n batchSize?: number;\n /**\n * Raw dashboard read-model issues (from `getSharedIssueService().getIssues()`).\n * Normalized to tracker `Issue` objects via {@link normalizeBacklogIssues}\n * before ranking — callers must NOT pre-cast to `Issue[]` (the shapes differ).\n */\n issues?: ReadonlyArray<Record<string, unknown>>;\n};\n\nexport function getSequencerRunStatus(projectRoot: string): SequencerRunStatus {\n const alive = listRunningAgentsSync().some((a) => a.id === SEQUENCER_AGENT_ID && a.tmuxActive);\n const startedAt = alive ? (getAgentStateSync(SEQUENCER_AGENT_ID)?.startedAt ?? null) : null;\n const seqPath = join(projectRoot, '.pan', 'backlog', 'sequence.md');\n\n let freshSequence = false;\n if (alive && startedAt && existsSync(seqPath)) {\n try {\n freshSequence = statSync(seqPath).mtimeMs >= new Date(startedAt).getTime();\n } catch {\n /* stat failed */\n }\n }\n\n const runtimeState = alive ? getAgentRuntimeStateSync(SEQUENCER_AGENT_ID)?.state ?? null : null;\n const idle = runtimeState === 'idle' || runtimeState === 'stopped' || runtimeState === 'suspended';\n const doneReason = freshSequence ? 'fresh-sequence' : idle ? 'idle' : null;\n const done = alive && doneReason !== null;\n\n return {\n alive,\n running: alive && !done,\n done,\n startedAt,\n doneReason,\n };\n}\n\nexport async function clearFinishedSequencerRun(\n projectRoot: string,\n stop: () => Promise<void> = () => Effect.runPromise(stopAgent(SEQUENCER_AGENT_ID)),\n): Promise<SequencerRunStatus> {\n const status = getSequencerRunStatus(projectRoot);\n if (status.done) {\n await stop();\n }\n return status;\n}\n\nexport async function spawnSequencerAgent(\n pass: PassMode | 'auto',\n opts: SpawnSequencerOptions = {},\n): Promise<AgentState> {\n const assertUnpaused = () => {\n if (getAgentStateSync(SEQUENCER_AGENT_ID)?.paused) {\n throw new Error('Sequencer is paused. Run pan unpause sequencer-runner before starting it again.');\n }\n };\n assertUnpaused();\n const projectRoot = opts.projectRoot ?? process.cwd();\n const projectKey = opts.projectKey ?? 'overdeck';\n const batchSize = opts.batchSize ?? 20;\n\n const seqPath = join(projectRoot, '.pan', 'backlog', 'sequence.md');\n const hasPriorSequence = existsSync(seqPath);\n // An incremental pass without a prior sequence.md is meaningless (there is\n // nothing to preserve ranks against) — downgrade it to a creation pass. The\n // auto-trigger always requests 'incremental', so this is the path a project's\n // first-ever autonomous pass takes.\n const resolvedPass: PassMode =\n pass === 'auto'\n ? hasPriorSequence\n ? 'incremental'\n : 'creation'\n : pass === 'incremental' && !hasPriorSequence\n ? 'creation'\n : pass;\n\n let rawIssues = opts.issues;\n if (!rawIssues) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { getSharedIssueService } = require('../../dashboard/server/services/issue-service-singleton.js') as\n typeof import('../../dashboard/server/services/issue-service-singleton.js');\n rawIssues = getSharedIssueService().getIssues() as Array<Record<string, unknown>>;\n } catch (err) {\n // Do not swallow the evidence — an unavailable issue read model here is\n // the difference between \"empty backlog\" and \"broken input\" downstream.\n console.warn('[sequencer] issue read model unavailable, falling back to empty issue list:', err);\n rawIssues = [];\n }\n }\n // Dashboard read-model issues key the human ref as `identifier` (not `ref`)\n // and carry canonical statuses; normalize to tracker `Issue` objects so the\n // backlog ranking pipeline can read `issue.ref`/`issue.state` (PAN-1866 fix).\n const allIssues = normalizeBacklogIssues(rawIssues);\n // Scope to Panopticon/overdeck issues only for now. The sequencer is a single\n // global runner; ranking every connected project's open issues (MIN/KRUX/TIN/…)\n // bloats the manifest and isn't meaningful for an overdeck-only sequence.\n // PAN-1999 generalizes this to one sequencer per project — drop the filter then.\n const issues = allIssues.filter((issue) => issue.ref.toUpperCase().startsWith('PAN-'));\n const input = await collectOpenBacklog(projectRoot, issues);\n\n // Refuse to spawn a pass over an empty manifest. On 2026-07-29 a transient\n // empty/failed getIssues() produced a 0-issue manifest against a 750-issue\n // open backlog, and a full sequencer run was spawned to rank nothing. An\n // empty manifest almost always means the issue read model was unavailable\n // or empty at spawn time — and even for a genuinely empty backlog a\n // sequencer pass has nothing to do. Guarding BEFORE the manifest write also\n // preserves the previous good manifest on disk.\n if (input.manifest.length === 0) {\n throw new Error(\n `Sequencer refusing to spawn: backlog manifest is empty (0 open ${projectKey} issues from ` +\n `${rawIssues.length} raw read-model issues). An empty manifest usually means the issue ` +\n 'read model was unavailable or empty at spawn time, not an empty backlog.',\n );\n }\n\n // Write the manifest to a file and reference it from the prompt instead of\n // inlining hundreds of KB of JSON. Inlining produced a single multi-hundred-KB\n // chat message that froze the dashboard conversation panel and blew past\n // small-context models (PAN-1866). The agent reads the file with the Read tool.\n const manifestPath = join(projectRoot, '.pan', 'backlog', 'manifest.json');\n await mkdir(dirname(manifestPath), { recursive: true });\n await writeFile(manifestPath, JSON.stringify(input.manifest, null, 2), 'utf-8');\n\n const model = determineModel({ role: 'sequencer', model: opts.model, spawnKey: 'sequencer:global' });\n const prompt = buildSequencerPrompt(resolvedPass, { projectRoot, projectKey, input, batchSize });\n\n // Backlog collection and manifest writes yield; an operator may pause meanwhile.\n assertUnpaused();\n return spawnRun(SEQUENCER_AGENT_ID, 'sequencer', {\n agentId: SEQUENCER_AGENT_ID,\n workspace: opts.workspace ?? projectRoot,\n prompt,\n model,\n allowHost: true,\n registerConversation: true,\n startedBy: 'backlog-sequencer',\n });\n}\n\nexport type SequencerPromptInput = {\n projectRoot: string;\n projectKey: string;\n input: CollectOpenBacklogResult;\n batchSize?: number;\n};\n\nexport function buildSequencerPrompt(pass: PassMode, opts: SequencerPromptInput): string {\n const { projectRoot, projectKey, input, batchSize = 20 } = opts;\n const { manifest, bodies, priorSequence } = input;\n const hasPrior = priorSequence !== null;\n const priorSequencePath = `${projectRoot}/.pan/backlog/sequence.md`;\n\n const passInstructions = {\n creation: `\nThis is a CREATION pass — there is no prior sequence.md. You must rank the entire open backlog from scratch.\n\n1. Read all issue bodies in batches of ${batchSize}. NEVER request all bodies at once — do NOT inline the entire backlog in a single prompt or tool call. Read batch 0, update your running shortlist, then batch 1, and so on.\n2. Assign rank, size, importance, score, condition, gate, and planning to every issue.\n3. Derive the dependency DAG from GitHub cross-references and your analysis.\n4. Write the result via \\`pan backlog write-sequence\\` (see Output section below — do NOT write the file directly).\n`,\n incremental: `\nThis is an INCREMENTAL pass — a prior sequence.md exists at ${hasPrior ? priorSequencePath : '(not found)'}. Preserve existing ranks, scores, conditions, operator-owned fields, and operator-sourced edges VERBATIM unless a delta justifies a change.\n\nRules:\n- An issue changed since the prior run if its manifest \\`updatedAt\\` is LATER than the prior sequence \\`generatedAt\\` (${hasPrior ? priorSequence!.generatedAt : 'N/A'}). Read bodies ONLY for those issues. Batch these (${batchSize} at a time).\n- A delta justifies a rank change when: body changed materially, new cross-references appeared, a dependency was closed/merged, or condition changed.\n- For each changed issue: set a rationale field (one sentence) explaining why the rank changed.\n- Re-derive github-ref edges. Recompute ai-inferred edges as advisory. Preserve operator edges verbatim.\n- Preserve operator-owned gate and planning fields verbatim.\n`,\n review: `\nThis is a REVIEW pass — re-rank the full backlog on demand. A prior sequence.md is available at ${hasPrior ? priorSequencePath : '(not found)'} for reference.\n\n1. Re-read all issue bodies in batches of ${batchSize}. NEVER inline the entire backlog in one prompt. Do NOT concatenate all bodies — read them batch by batch against your running shortlist.\n2. Re-derive all ai-inferred and github-ref edges.\n3. Preserve operator edges verbatim.\n4. Never re-rank in-pipeline issues (inPipeline=true); pin them at rank 1 or their prior rank. Leave gate=auto — do NOT set gate=ready (the gate is operator-only; in-pipeline is detected automatically).\n`,\n };\n\n const commonRules = `\n## Ranking rules (all passes)\n\n- Rank by IMPACT toward shipping, not raw priority signal. GitHub priority and issue age are inputs, not determinants.\n- Substrate-hardening first: an issue labeled \\`substrate-improvement\\`, \\`architecture\\`, or \\`v1.0-required\\` is at least \\`high\\` importance (\\`critical\\` if it unblocks the pipeline or other substrate work) and ranks ahead of routine feature work of equal impact — a stable substrate is the prerequisite for everything else (vision.mdx). When a substrate epic ranks high, lift its CHILDREN's ranks together (the children are what get picked). Do not rank such an issue \\`low\\` just because it reads as cleanup.\n- NEVER re-rank in-pipeline issues (inPipeline: true in the manifest). Pin them at their rank; leave gate=auto (do NOT set gate=ready — gate is operator-only).\n- Assign condition to every issue: ok / needs-refinement / stale.\n- Every node's why field must be ≤ 140 characters (displayed in the ranked table).\n- Write full-paragraph rationale only for the active top tier (~top 80 nodes).\n- Preserve operator-owned gate and planning fields verbatim across all passes.\n- Keep operator-sourced edges verbatim. Re-derive github-ref edges. Recompute ai-inferred edges as advisory with a confidence value 0.0–1.0.\n- Stamp pass=\"${pass}\" and generatedAt=<current ISO timestamp> in the output JSON.\n\n## Epics & parent-child\n\n- An EPIC is a container of child issues, not directly workable. The manifest flags epics with \\`isEpic: true\\` (derived from an \\`[EPIC]\\` title prefix or an \\`epic\\` label). On an epic's node set \\`isEpic: true\\`, and rank/score it by the aggregate impact of its CHILDREN — never as standalone work. Never mark an epic ready/promoted; it must not be picked up.\n- Express membership with a \\`contains\\` edge from the epic to each child: \\`{ from: <epic>, to: <child>, type: \"contains\", source: \"github-ref\", confidence: 1 }\\`. Membership sources: a child's manifest \\`partOf\\` field (\"Part of #N\" in its body) AND the epic body's task-list of child issue numbers (read the epic body to confirm). Emit one \\`contains\\` edge per child.\n- \\`contains\\` edges are membership, NOT ordering. Keep the children's intended BUILD ORDER in their \\`dependsOn\\` (and \\`unblocks\\` edges) as usual — an epic body often states the order explicitly.\n\n## Reading issue bodies\n\nFetch issue bodies in batches of ${batchSize} using the GitHub CLI:\n gh issue view <NUMBER> --json number,title,body\n\nDerive NUMBER from the manifest id (e.g. PAN-42 → 42). Do NOT fetch all bodies at once; read one batch, update your running shortlist, then the next.\n\n## Output\n\nAfter completing your analysis, write the SequenceDoc JSON to a temp file and submit it via:\n\n pan backlog write-sequence /tmp/sequence-result.json\n\nThe \\`pan backlog write-sequence\\` command validates the JSON, writes the formatted\n\\`.pan/backlog/sequence.md\\`, and queues an auto-commit — so DO NOT write the file\ndirectly with the Write tool. Always go through this command.\n\nThe SequenceDoc JSON must conform to the schema:\n { version (number, e.g. 1), project, generatedAt (ISO), model, pass, openCount, nodes[], edges[] }\n\nwhere each node has: issue, rank, size (XS/S/M/L/XL), importance (critical/high/medium/low),\nscore (0-100), condition (ok/needs-refinement/stale), dependsOn (string[]), why (≤140 chars),\nrationale (optional), gate (auto/ready/blocked), planning (skip/auto/interactive),\nisEpic (optional boolean — true for epic containers).\n\nEach edge has: from, to, type (unblocks/informs/contains), source (github-ref/operator/ai-inferred),\nconfidence (0.0–1.0). Use type=\"contains\" only for epic→child membership.\n\nDo not ask for operator input. If an issue is ambiguous, assign condition=needs-refinement and move on.\n`;\n\n const priorContext = hasPrior\n ? `\\n## Prior sequence\\n\\nProject: ${priorSequence!.project}, pass: ${priorSequence!.pass}, ${priorSequence!.nodes.length} nodes, generated: ${priorSequence!.generatedAt}\\n`\n : '\\n## Prior sequence\\n\\nNo prior sequence.md found — this is the first run.\\n';\n\n return `# Sequencer run: ${pass.toUpperCase()} pass for ${projectKey}\n\nYou are the Overdeck Sequencer. Your job is to rank the open backlog for project \"${projectKey}\" and write .pan/backlog/sequence.md.\n\n## Pass\n${passInstructions[pass]}\n${priorContext}\n## Backlog manifest (${manifest.length} open issues, ${bodies.count} bodies available in ${Math.ceil(bodies.count / batchSize)} batches of ${batchSize})\n\nThe manifest is written to \\`.pan/backlog/manifest.json\\` (relative to the project root) — it is NOT inlined here. Read it FIRST (the Read tool, or \\`cat .pan/backlog/manifest.json\\`) to load every open issue's { id, title, labels, priority, ageMs, inPipeline, hasPrd, ready, updatedAt, isEpic, partOf }. Do NOT ask for it to be pasted into the prompt.\n${commonRules}`;\n}\n"],"mappings":";;;;;;;;;;;;;;AAmCA,MAAA,mBAAA;AACA,MAAA,gBAAA,IAAA,IAAA,CAAA,UAAA,YAAA,CAAA;;;;;;;;AASA,SAAA,sBAAA,KAAA;;AAEE,KAAA;;;;;;;;;AACA,KAAA;;;;;;;AACA,KAAA;;;;;;;;;AACA,QAAA;;;;;;;;;;;;;;AAeF,SAAA,uBAAA,KAAA;;AAIE,MAAA,MAAA,KAAA,KAAA;;AAEE,MAAA,CAAA,IAAA;;AAEA,MAAA,KAAA;;;;;;;;;;;;;;AAcF,QAAA;;AAGF,MAAA,gBAAA;;AAGA,SAAA,aAAA,OAAA,QAAA;AACE,QAAA,cAAA,KAAA,MAAA,IAAA,OAAA,MAAA,MAAA,EAAA,aAAA,KAAA,OAAA;;;;;;;AAQF,SAAA,aAAA,UAAA,MAAA;;AAEE,KAAA,CAAA,EAAA,QAAA,KAAA;;AAEA,KAAA,CAAA,OAAA,QAAA,KAAA;;AAEA,QAAA,YAAA,SAAA,aAAA,GAAA,KAAA,IAAA;;AAGF,eAAA,mBAAA,aAAA,QAAA,MAAA;;;;;;;AAiBE,KAAA,CAAA,MAAA;;;AAIM,OAAA,MAAA,iBAAA,IAAA,MAAA,GAAA,aAAA,CAAA;;;;;;;;;AAsBJ,SAAA;;;;;;;;;;;;;;;;;;AAoBE,UAAA,WAAA,MAAA,OAAA,QAAA,UAAA,CAAA,KAAA,WAAA;;;;;;;;AAMJ,KAAA,WAAA,aAAA,EAAA;;AAGE,MAAA,OAAA,GAAA,iBAAA,OAAA;;AAKF,QAAA;;;;;;;;AC3KF,MAAa,qBAAqB;AAwBlC,SAAgB,sBAAsB,aAAyC;CAC7E,MAAM,QAAQ,uBAAuB,CAAC,MAAM,MAAM,EAAE,OAAA,sBAA6B,EAAE,WAAW;CAC9F,MAAM,YAAY,QAAS,kBAAA,mBAAqC,EAAE,aAAa,OAAQ;CACvF,MAAM,UAAU,KAAK,aAAa,QAAQ,WAAW,cAAc;CAEnE,IAAI,gBAAgB;AACpB,KAAI,SAAS,aAAa,WAAW,QAAQ,CAC3C,KAAI;AACF,kBAAgB,SAAS,QAAQ,CAAC,WAAW,IAAI,KAAK,UAAU,CAAC,SAAS;SACpE;CAKV,MAAM,eAAe,QAAQ,yBAAA,mBAA4C,EAAE,SAAS,OAAO;CAE3F,MAAM,aAAa,gBAAgB,mBADtB,iBAAiB,UAAU,iBAAiB,aAAa,iBAAiB,cAC1B,SAAS;CACtE,MAAM,OAAO,SAAS,eAAe;AAErC,QAAO;EACL;EACA,SAAS,SAAS,CAAC;EACnB;EACA;EACA;EACD;;AAGH,eAAsB,0BACpB,aACA,aAAkC,OAAO,WAAW,UAAU,mBAAmB,CAAC,EACrD;CAC7B,MAAM,SAAS,sBAAsB,YAAY;AACjD,KAAI,OAAO,KACT,OAAM,MAAM;AAEd,QAAO;;AAGT,eAAsB,oBACpB,MACA,OAA8B,EAAE,EACX;CACrB,MAAM,uBAAuB;AAC3B,MAAI,kBAAA,mBAAqC,EAAE,OACzC,OAAM,IAAI,MAAM,kFAAkF;;AAGtG,iBAAgB;CAChB,MAAM,cAAc,KAAK,eAAe,QAAQ,KAAK;CACrD,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,YAAY,KAAK,aAAa;CAGpC,MAAM,mBAAmB,WADT,KAAK,aAAa,QAAQ,WAAW,cACV,CAAC;CAK5C,MAAM,eACJ,SAAS,SACL,mBACE,gBACA,aACF,SAAS,iBAAiB,CAAC,mBACzB,aACA;CAER,IAAI,YAAY,KAAK;AACrB,KAAI,CAAC,UACH,KAAI;EAEF,MAAM,EAAE,2BAAA,8BAAA,EAAA,aAAA,gCAAA;AAER,cAAY,uBAAuB,CAAC,WAAW;UACxC,KAAK;AAGZ,UAAQ,KAAK,+EAA+E,IAAI;AAChG,cAAY,EAAE;;CAYlB,MAAM,QAAQ,MAAM,mBAAmB,aANrB,uBAAuB,UAKjB,CAAC,QAAQ,UAAU,MAAM,IAAI,aAAa,CAAC,WAAW,OAAO,CAC3B,CAAC;AAS3D,KAAI,MAAM,SAAS,WAAW,EAC5B,OAAM,IAAI,MACR,kEAAkE,WAAW,eACxE,UAAU,OAAO,6IAEvB;CAOH,MAAM,eAAe,KAAK,aAAa,QAAQ,WAAW,gBAAgB;AAC1E,OAAM,MAAM,QAAQ,aAAa,EAAE,EAAE,WAAW,MAAM,CAAC;AACvD,OAAM,UAAU,cAAc,KAAK,UAAU,MAAM,UAAU,MAAM,EAAE,EAAE,QAAQ;CAE/E,MAAM,QAAQ,eAAe;EAAE,MAAM;EAAa,OAAO,KAAK;EAAO,UAAU;EAAoB,CAAC;CACpG,MAAM,SAAS,qBAAqB,cAAc;EAAE;EAAa;EAAY;EAAO;EAAW,CAAC;AAGhG,iBAAgB;AAChB,QAAO,SAAS,oBAAoB,aAAa;EAC/C,SAAS;EACT,WAAW,KAAK,aAAa;EAC7B;EACA;EACA,WAAW;EACX,sBAAsB;EACtB,WAAW;EACZ,CAAC;;AAUJ,SAAgB,qBAAqB,MAAgB,MAAoC;CACvF,MAAM,EAAE,aAAa,YAAY,OAAO,YAAY,OAAO;CAC3D,MAAM,EAAE,UAAU,QAAQ,kBAAkB;CAC5C,MAAM,WAAW,kBAAkB;CACnC,MAAM,oBAAoB,GAAG,YAAY;CAEzC,MAAM,mBAAmB;EACvB,UAAU;;;yCAG2B,UAAU;;;;;EAK/C,aAAa;8DAC6C,WAAW,oBAAoB,cAAc;;;yHAGc,WAAW,cAAe,cAAc,MAAM,qDAAqD,UAAU;;;;;;EAMlO,QAAQ;kGACsF,WAAW,oBAAoB,cAAc;;4CAEnG,UAAU;;;;;EAKnD;CAED,MAAM,cAAc;;;;;;;;;;;gBAWN,KAAK;;;;;;;;;;mCAUc,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B3C,MAAM,eAAe,WACjB,mCAAmC,cAAe,QAAQ,UAAU,cAAe,KAAK,IAAI,cAAe,MAAM,OAAO,qBAAqB,cAAe,YAAY,MACxK;AAEJ,QAAO,oBAAoB,KAAK,aAAa,CAAC,YAAY,WAAW;;oFAEa,WAAW;;;EAG7F,iBAAiB,MAAM;EACvB,aAAa;uBACQ,SAAS,OAAO,gBAAgB,OAAO,MAAM,uBAAuB,KAAK,KAAK,OAAO,QAAQ,UAAU,CAAC,cAAc,UAAU;;;EAGrJ"}
|
|
1
|
+
{"version":3,"file":"sequencer-agent-BXDw5cNx.js","names":[],"sources":["../../src/lib/backlog/backlog-input.ts","../../src/lib/backlog/sequencer-agent.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { getProjectPanPaths } from '../pan-dir/paths.js';\nimport type { Issue, IssueState, TrackerType } from '../tracker/interface.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { parseSequenceMd } from './sequence-io.js';\nimport type { SequenceDoc } from './types.js';\n\nexport type BacklogManifestEntry = {\n id: string;\n title: string;\n labels: string[];\n priority: number | undefined;\n ageMs: number;\n inPipeline: boolean;\n hasPrd: boolean;\n ready: boolean;\n updatedAt: string;\n /** Epic container (`[EPIC]` title prefix or `epic` label) — not directly workable. */\n isEpic: boolean;\n /** Epic this issue declares membership in via \"Part of #N\" in its body, if any. */\n partOf?: string;\n};\n\nexport type BatchedBodyAccessor = {\n count: number;\n getBatch(batchIndex: number, batchSize: number): Array<{ id: string; body: string }>;\n};\n\nexport type CollectOpenBacklogResult = {\n manifest: BacklogManifestEntry[];\n bodies: BatchedBodyAccessor;\n priorSequence: SequenceDoc | null;\n};\n\nconst SEQUENCE_MD_PATH = '.pan/backlog/sequence.md';\nconst CLOSED_STATES = new Set(['closed', 'cancelled']);\n\n/**\n * Map a dashboard canonical status (or a raw tracker status) onto the\n * tracker-library {@link IssueState} union that {@link collectOpenBacklog}\n * expects. Dashboard read-model issues carry `canonicalStatus`/`state` values\n * like `done`/`canceled`/`verifying_on_main`; the backlog library only knows\n * `open`/`in_progress`/`in_review`/`closed`.\n */\nfunction canonicalToIssueState(raw: string): IssueState {\n const c = raw.toLowerCase();\n if (['done', 'closed', 'completed', 'canceled', 'cancelled', 'duplicate', 'wontfix'].includes(c)) return 'closed';\n if (['in_review', 'in review', 'review', 'qa', 'testing'].includes(c)) return 'in_review';\n if (['in_progress', 'in progress', 'started', 'active', 'verifying', 'verifying_on_main', 'verifying on main'].includes(c)) return 'in_progress';\n return 'open'; // backlog, todo, ready, triage, unknown, ''\n}\n\n/**\n * Adapt the dashboard read-model issue objects returned by\n * `getSharedIssueService().getIssues()` into tracker-library {@link Issue}\n * objects. The two shapes diverge: the dashboard keys the human ref as\n * `identifier` (e.g. \"PAN-302\") with no `ref` field, and its `state` is a\n * canonical dashboard status, not an {@link IssueState}. Feeding the raw\n * dashboard objects straight into {@link collectOpenBacklog} made\n * `getReviewStatusSync(issue.ref)` receive `undefined` and crash on\n * `undefined.toUpperCase()` — so every sequencer pass died before spawning.\n *\n * Issues without a usable human ref are dropped (they cannot be ranked).\n */\nexport function normalizeBacklogIssues(\n raw: ReadonlyArray<Record<string, unknown>>,\n): Issue[] {\n const out: Issue[] = [];\n for (const r of raw) {\n const ref = String(r['identifier'] ?? r['ref'] ?? '').trim();\n if (!ref) continue;\n const stateRaw = String(r['canonicalStatus'] ?? r['state'] ?? r['status'] ?? '');\n out.push({\n id: String(r['id'] ?? ref),\n ref,\n title: String(r['title'] ?? ''),\n description: String(r['description'] ?? ''),\n state: canonicalToIssueState(stateRaw),\n labels: Array.isArray(r['labels']) ? (r['labels'] as string[]) : [],\n url: String(r['url'] ?? ''),\n tracker: (typeof r['source'] === 'string' ? r['source'] : 'github') as TrackerType,\n priority: typeof r['priority'] === 'number' ? r['priority'] : undefined,\n createdAt: String(r['createdAt'] ?? ''),\n updatedAt: String(r['updatedAt'] ?? ''),\n });\n }\n return out;\n}\n\nconst EPIC_TITLE_RE = /^\\s*\\[EPIC\\]/i;\n\n/** An issue is an epic if its title is prefixed `[EPIC]` or it carries the `epic` label. */\nexport function detectIsEpic(title: string, labels: readonly string[]): boolean {\n return EPIC_TITLE_RE.test(title) || labels.some((l) => l.toLowerCase() === 'epic');\n}\n\n/**\n * Parse an explicit epic-membership declaration (\"Part of #N\") from an issue body.\n * Returns the epic's ref using the child's own prefix (PAN-2076 body \"Part of #2075\"\n * → \"PAN-2075\"), or undefined if none is declared or it points at the issue itself.\n */\nexport function detectPartOf(childRef: string, body: string): string | undefined {\n const m = /\\bpart of\\s+#(\\d+)\\b/i.exec(body);\n if (!m) return undefined;\n const prefix = childRef.split('-')[0];\n if (!prefix) return undefined;\n const epicRef = `${prefix}-${m[1]}`;\n return epicRef === childRef.toUpperCase() ? undefined : epicRef;\n}\n\nexport async function collectOpenBacklog(\n projectRoot: string,\n issues: Issue[],\n opts?: {\n hasPrdFn?: (issueId: string) => boolean;\n hasSpecFn?: (issueId: string) => boolean;\n },\n): Promise<CollectOpenBacklogResult> {\n const now = Date.now();\n\n const openIssues = issues.filter((issue) => !CLOSED_STATES.has(issue.state));\n\n // Build per-issue lookup sets once (filesystem scans are cheap when batched).\n const paths = getProjectPanPaths(projectRoot);\n const specsDir = paths.specsDir;\n const workspacesDir = join(projectRoot, 'workspaces');\n const issuesWithSpecs = new Set<string>();\n if (!opts?.hasSpecFn) {\n if (existsSync(specsDir)) {\n for (const f of readdirSync(specsDir)) {\n const match = /^[\\d-]+-([A-Z]+-\\d+)-/i.exec(f);\n if (match) issuesWithSpecs.add(match[1]!.toUpperCase());\n }\n }\n }\n\n const manifest: BacklogManifestEntry[] = openIssues.map((issue) => {\n const reviewStatus = getReviewStatusSync(issue.ref);\n const inPipeline =\n (reviewStatus !== null && reviewStatus.reviewStatus !== 'pending') ||\n existsSync(join(workspacesDir, `feature-${issue.ref.toLowerCase()}`));\n\n const hasPrd = opts?.hasPrdFn\n ? opts.hasPrdFn(issue.ref)\n : existsSync(join(paths.draftsDir, `${issue.ref.toLowerCase()}.md`)) ||\n existsSync(join(paths.draftsDir, `${issue.ref.toUpperCase()}.md`));\n\n const ready = opts?.hasSpecFn\n ? opts.hasSpecFn(issue.ref)\n : issuesWithSpecs.has(issue.ref.toUpperCase());\n\n const createdMs = issue.createdAt ? new Date(issue.createdAt).getTime() : now;\n\n return {\n id: issue.ref,\n title: issue.title,\n labels: issue.labels,\n priority: issue.priority,\n ageMs: now - createdMs,\n inPipeline,\n hasPrd,\n ready,\n updatedAt: issue.updatedAt,\n isEpic: detectIsEpic(issue.title, issue.labels),\n partOf: detectPartOf(issue.ref, issue.description),\n };\n });\n\n const bodies: BatchedBodyAccessor = {\n count: openIssues.length,\n getBatch(batchIndex: number, batchSize: number) {\n const start = batchIndex * batchSize;\n const batch = openIssues.slice(start, start + batchSize);\n return batch.map((issue) => ({ id: issue.ref, body: issue.description }));\n },\n };\n\n const sequencePath = join(projectRoot, SEQUENCE_MD_PATH);\n let priorSequence: SequenceDoc | null = null;\n if (existsSync(sequencePath)) {\n const md = readFileSync(sequencePath, 'utf-8');\n const result = parseSequenceMd(md);\n if (result.ok) {\n priorSequence = result.doc;\n }\n }\n\n return { manifest, bodies, priorSequence };\n}\n","import { existsSync, statSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport type { AgentState } from '../agents.js';\nimport { Effect } from 'effect';\nimport {\n spawnRun,\n determineModel,\n listRunningAgentsSync,\n getAgentStateSync,\n getAgentRuntimeStateSync,\n stopAgent,\n} from '../agents.js';\nimport { collectOpenBacklog, normalizeBacklogIssues } from './backlog-input.js';\nimport type { PassMode } from './types.js';\nimport type { CollectOpenBacklogResult } from './backlog-input.js';\n\nexport const SEQUENCER_AGENT_ID = 'sequencer-runner';\n\nexport type SequencerRunStatus = {\n alive: boolean;\n running: boolean;\n done: boolean;\n startedAt: string | null;\n doneReason: 'fresh-sequence' | 'idle' | null;\n};\n\nexport type SpawnSequencerOptions = {\n projectRoot?: string;\n projectKey?: string;\n model?: string;\n workspace?: string;\n batchSize?: number;\n /**\n * Raw dashboard read-model issues (from `getSharedIssueService().getIssues()`).\n * Normalized to tracker `Issue` objects via {@link normalizeBacklogIssues}\n * before ranking — callers must NOT pre-cast to `Issue[]` (the shapes differ).\n */\n issues?: ReadonlyArray<Record<string, unknown>>;\n};\n\nexport function getSequencerRunStatus(projectRoot: string): SequencerRunStatus {\n const alive = listRunningAgentsSync().some((a) => a.id === SEQUENCER_AGENT_ID && a.tmuxActive);\n const startedAt = alive ? (getAgentStateSync(SEQUENCER_AGENT_ID)?.startedAt ?? null) : null;\n const seqPath = join(projectRoot, '.pan', 'backlog', 'sequence.md');\n\n let freshSequence = false;\n if (alive && startedAt && existsSync(seqPath)) {\n try {\n freshSequence = statSync(seqPath).mtimeMs >= new Date(startedAt).getTime();\n } catch {\n /* stat failed */\n }\n }\n\n const runtimeState = alive ? getAgentRuntimeStateSync(SEQUENCER_AGENT_ID)?.state ?? null : null;\n const idle = runtimeState === 'idle' || runtimeState === 'stopped' || runtimeState === 'suspended';\n const doneReason = freshSequence ? 'fresh-sequence' : idle ? 'idle' : null;\n const done = alive && doneReason !== null;\n\n return {\n alive,\n running: alive && !done,\n done,\n startedAt,\n doneReason,\n };\n}\n\nexport async function clearFinishedSequencerRun(\n projectRoot: string,\n stop: () => Promise<void> = () => Effect.runPromise(stopAgent(SEQUENCER_AGENT_ID)),\n): Promise<SequencerRunStatus> {\n const status = getSequencerRunStatus(projectRoot);\n if (status.done) {\n await stop();\n }\n return status;\n}\n\nexport async function spawnSequencerAgent(\n pass: PassMode | 'auto',\n opts: SpawnSequencerOptions = {},\n): Promise<AgentState> {\n const assertUnpaused = () => {\n if (getAgentStateSync(SEQUENCER_AGENT_ID)?.paused) {\n throw new Error('Sequencer is paused. Run pan unpause sequencer-runner before starting it again.');\n }\n };\n assertUnpaused();\n const projectRoot = opts.projectRoot ?? process.cwd();\n const projectKey = opts.projectKey ?? 'overdeck';\n const batchSize = opts.batchSize ?? 20;\n\n const seqPath = join(projectRoot, '.pan', 'backlog', 'sequence.md');\n const hasPriorSequence = existsSync(seqPath);\n // An incremental pass without a prior sequence.md is meaningless (there is\n // nothing to preserve ranks against) — downgrade it to a creation pass. The\n // auto-trigger always requests 'incremental', so this is the path a project's\n // first-ever autonomous pass takes.\n const resolvedPass: PassMode =\n pass === 'auto'\n ? hasPriorSequence\n ? 'incremental'\n : 'creation'\n : pass === 'incremental' && !hasPriorSequence\n ? 'creation'\n : pass;\n\n let rawIssues = opts.issues;\n if (!rawIssues) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { getSharedIssueService } = require('../../dashboard/server/services/issue-service-singleton.js') as\n typeof import('../../dashboard/server/services/issue-service-singleton.js');\n rawIssues = getSharedIssueService().getIssues() as Array<Record<string, unknown>>;\n } catch (err) {\n // Do not swallow the evidence — an unavailable issue read model here is\n // the difference between \"empty backlog\" and \"broken input\" downstream.\n console.warn('[sequencer] issue read model unavailable, falling back to empty issue list:', err);\n rawIssues = [];\n }\n }\n // Dashboard read-model issues key the human ref as `identifier` (not `ref`)\n // and carry canonical statuses; normalize to tracker `Issue` objects so the\n // backlog ranking pipeline can read `issue.ref`/`issue.state` (PAN-1866 fix).\n const allIssues = normalizeBacklogIssues(rawIssues);\n // Scope to Panopticon/overdeck issues only for now. The sequencer is a single\n // global runner; ranking every connected project's open issues (MIN/KRUX/TIN/…)\n // bloats the manifest and isn't meaningful for an overdeck-only sequence.\n // PAN-1999 generalizes this to one sequencer per project — drop the filter then.\n const issues = allIssues.filter((issue) => issue.ref.toUpperCase().startsWith('PAN-'));\n const input = await collectOpenBacklog(projectRoot, issues);\n\n // Refuse to spawn a pass over an empty manifest. On 2026-07-29 a transient\n // empty/failed getIssues() produced a 0-issue manifest against a 750-issue\n // open backlog, and a full sequencer run was spawned to rank nothing. An\n // empty manifest almost always means the issue read model was unavailable\n // or empty at spawn time — and even for a genuinely empty backlog a\n // sequencer pass has nothing to do. Guarding BEFORE the manifest write also\n // preserves the previous good manifest on disk.\n if (input.manifest.length === 0) {\n throw new Error(\n `Sequencer refusing to spawn: backlog manifest is empty (0 open ${projectKey} issues from ` +\n `${rawIssues.length} raw read-model issues). An empty manifest usually means the issue ` +\n 'read model was unavailable or empty at spawn time, not an empty backlog.',\n );\n }\n\n // Write the manifest to a file and reference it from the prompt instead of\n // inlining hundreds of KB of JSON. Inlining produced a single multi-hundred-KB\n // chat message that froze the dashboard conversation panel and blew past\n // small-context models (PAN-1866). The agent reads the file with the Read tool.\n const manifestPath = join(projectRoot, '.pan', 'backlog', 'manifest.json');\n await mkdir(dirname(manifestPath), { recursive: true });\n await writeFile(manifestPath, JSON.stringify(input.manifest, null, 2), 'utf-8');\n\n const model = determineModel({ role: 'sequencer', model: opts.model, spawnKey: 'sequencer:global' });\n const prompt = buildSequencerPrompt(resolvedPass, { projectRoot, projectKey, input, batchSize });\n\n // Backlog collection and manifest writes yield; an operator may pause meanwhile.\n assertUnpaused();\n return spawnRun(SEQUENCER_AGENT_ID, 'sequencer', {\n agentId: SEQUENCER_AGENT_ID,\n workspace: opts.workspace ?? projectRoot,\n prompt,\n model,\n allowHost: true,\n registerConversation: true,\n startedBy: 'backlog-sequencer',\n });\n}\n\nexport type SequencerPromptInput = {\n projectRoot: string;\n projectKey: string;\n input: CollectOpenBacklogResult;\n batchSize?: number;\n};\n\nexport function buildSequencerPrompt(pass: PassMode, opts: SequencerPromptInput): string {\n const { projectRoot, projectKey, input, batchSize = 20 } = opts;\n const { manifest, bodies, priorSequence } = input;\n const hasPrior = priorSequence !== null;\n const priorSequencePath = `${projectRoot}/.pan/backlog/sequence.md`;\n\n const passInstructions = {\n creation: `\nThis is a CREATION pass — there is no prior sequence.md. You must rank the entire open backlog from scratch.\n\n1. Read all issue bodies in batches of ${batchSize}. NEVER request all bodies at once — do NOT inline the entire backlog in a single prompt or tool call. Read batch 0, update your running shortlist, then batch 1, and so on.\n2. Assign rank, size, importance, score, condition, gate, and planning to every issue.\n3. Derive the dependency DAG from GitHub cross-references and your analysis.\n4. Write the result via \\`pan backlog write-sequence\\` (see Output section below — do NOT write the file directly).\n`,\n incremental: `\nThis is an INCREMENTAL pass — a prior sequence.md exists at ${hasPrior ? priorSequencePath : '(not found)'}. Preserve existing ranks, scores, conditions, operator-owned fields, and operator-sourced edges VERBATIM unless a delta justifies a change.\n\nRules:\n- An issue changed since the prior run if its manifest \\`updatedAt\\` is LATER than the prior sequence \\`generatedAt\\` (${hasPrior ? priorSequence!.generatedAt : 'N/A'}). Read bodies ONLY for those issues. Batch these (${batchSize} at a time).\n- A delta justifies a rank change when: body changed materially, new cross-references appeared, a dependency was closed/merged, or condition changed.\n- For each changed issue: set a rationale field (one sentence) explaining why the rank changed.\n- Re-derive github-ref edges. Recompute ai-inferred edges as advisory. Preserve operator edges verbatim.\n- Preserve operator-owned gate and planning fields verbatim.\n`,\n review: `\nThis is a REVIEW pass — re-rank the full backlog on demand. A prior sequence.md is available at ${hasPrior ? priorSequencePath : '(not found)'} for reference.\n\n1. Re-read all issue bodies in batches of ${batchSize}. NEVER inline the entire backlog in one prompt. Do NOT concatenate all bodies — read them batch by batch against your running shortlist.\n2. Re-derive all ai-inferred and github-ref edges.\n3. Preserve operator edges verbatim.\n4. Never re-rank in-pipeline issues (inPipeline=true); pin them at rank 1 or their prior rank. Leave gate=auto — do NOT set gate=ready (the gate is operator-only; in-pipeline is detected automatically).\n`,\n };\n\n const commonRules = `\n## Ranking rules (all passes)\n\n- Rank by IMPACT toward shipping, not raw priority signal. GitHub priority and issue age are inputs, not determinants.\n- Substrate-hardening first: an issue labeled \\`substrate-improvement\\`, \\`architecture\\`, or \\`v1.0-required\\` is at least \\`high\\` importance (\\`critical\\` if it unblocks the pipeline or other substrate work) and ranks ahead of routine feature work of equal impact — a stable substrate is the prerequisite for everything else (vision.mdx). When a substrate epic ranks high, lift its CHILDREN's ranks together (the children are what get picked). Do not rank such an issue \\`low\\` just because it reads as cleanup.\n- NEVER re-rank in-pipeline issues (inPipeline: true in the manifest). Pin them at their rank; leave gate=auto (do NOT set gate=ready — gate is operator-only).\n- Assign condition to every issue: ok / needs-refinement / stale.\n- Every node's why field must be ≤ 140 characters (displayed in the ranked table).\n- Write full-paragraph rationale only for the active top tier (~top 80 nodes).\n- Preserve operator-owned gate and planning fields verbatim across all passes.\n- Keep operator-sourced edges verbatim. Re-derive github-ref edges. Recompute ai-inferred edges as advisory with a confidence value 0.0–1.0.\n- Stamp pass=\"${pass}\" and generatedAt=<current ISO timestamp> in the output JSON.\n\n## Epics & parent-child\n\n- An EPIC is a container of child issues, not directly workable. The manifest flags epics with \\`isEpic: true\\` (derived from an \\`[EPIC]\\` title prefix or an \\`epic\\` label). On an epic's node set \\`isEpic: true\\`, and rank/score it by the aggregate impact of its CHILDREN — never as standalone work. Never mark an epic ready/promoted; it must not be picked up.\n- Express membership with a \\`contains\\` edge from the epic to each child: \\`{ from: <epic>, to: <child>, type: \"contains\", source: \"github-ref\", confidence: 1 }\\`. Membership sources: a child's manifest \\`partOf\\` field (\"Part of #N\" in its body) AND the epic body's task-list of child issue numbers (read the epic body to confirm). Emit one \\`contains\\` edge per child.\n- \\`contains\\` edges are membership, NOT ordering. Keep the children's intended BUILD ORDER in their \\`dependsOn\\` (and \\`unblocks\\` edges) as usual — an epic body often states the order explicitly.\n\n## Reading issue bodies\n\nFetch issue bodies in batches of ${batchSize} using the GitHub CLI:\n gh issue view <NUMBER> --json number,title,body\n\nDerive NUMBER from the manifest id (e.g. PAN-42 → 42). Do NOT fetch all bodies at once; read one batch, update your running shortlist, then the next.\n\n## Output\n\nAfter completing your analysis, write the SequenceDoc JSON to a temp file and submit it via:\n\n pan backlog write-sequence /tmp/sequence-result.json\n\nThe \\`pan backlog write-sequence\\` command validates the JSON, writes the formatted\n\\`.pan/backlog/sequence.md\\`, and queues an auto-commit — so DO NOT write the file\ndirectly with the Write tool. Always go through this command.\n\nThe SequenceDoc JSON must conform to the schema:\n { version (number, e.g. 1), project, generatedAt (ISO), model, pass, openCount, nodes[], edges[] }\n\nwhere each node has: issue, rank, size (XS/S/M/L/XL), importance (critical/high/medium/low),\nscore (0-100), condition (ok/needs-refinement/stale), dependsOn (string[]), why (≤140 chars),\nrationale (optional), gate (auto/ready/blocked), planning (skip/auto/interactive),\nisEpic (optional boolean — true for epic containers).\n\nEach edge has: from, to, type (unblocks/informs/contains), source (github-ref/operator/ai-inferred),\nconfidence (0.0–1.0). Use type=\"contains\" only for epic→child membership.\n\nDo not ask for operator input. If an issue is ambiguous, assign condition=needs-refinement and move on.\n`;\n\n const priorContext = hasPrior\n ? `\\n## Prior sequence\\n\\nProject: ${priorSequence!.project}, pass: ${priorSequence!.pass}, ${priorSequence!.nodes.length} nodes, generated: ${priorSequence!.generatedAt}\\n`\n : '\\n## Prior sequence\\n\\nNo prior sequence.md found — this is the first run.\\n';\n\n return `# Sequencer run: ${pass.toUpperCase()} pass for ${projectKey}\n\nYou are the Overdeck Sequencer. Your job is to rank the open backlog for project \"${projectKey}\" and write .pan/backlog/sequence.md.\n\n## Pass\n${passInstructions[pass]}\n${priorContext}\n## Backlog manifest (${manifest.length} open issues, ${bodies.count} bodies available in ${Math.ceil(bodies.count / batchSize)} batches of ${batchSize})\n\nThe manifest is written to \\`.pan/backlog/manifest.json\\` (relative to the project root) — it is NOT inlined here. Read it FIRST (the Read tool, or \\`cat .pan/backlog/manifest.json\\`) to load every open issue's { id, title, labels, priority, ageMs, inPipeline, hasPrd, ready, updatedAt, isEpic, partOf }. Do NOT ask for it to be pasted into the prompt.\n${commonRules}`;\n}\n"],"mappings":"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"}
|