@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-agent-DHs-JngN.js","names":["execAsync","promisify","exec","execAsync","execAsync","promisify","exec","join","join","loadYamlConfig","dirname","promisify","exec","join","getAgentStateFileSync","loadYamlConfig"],"sources":["../../src/lib/cloister/stub-ui-patterns.ts","../../src/lib/cloister/lint-stub-ui.ts","../../src/lib/cloister/coderabbit-ingestion.ts","../../src/lib/cloister/review-context.ts","../../src/lib/agents/removal.ts","../../src/lib/cloister/review-resume-decision.ts","../../src/lib/cloister/review-convoy.ts","../../src/lib/cloister/review-agent.ts"],"sourcesContent":["/**\n * Stub UI pattern catalog (PAN-1500 / PAN-1454 pattern 4).\n *\n * This module defines a deterministic, regex-based catalog of code shapes that\n * strongly suggest a PR is merging a UI stub: a new tab, mode, or routed view\n * whose data layer is faked, whose handlers no-op, or whose copy apologizes for\n * missing functionality. The catalog is best-effort, not exhaustive — extend it\n * when new stub shapes escape post-merge audits.\n *\n * The globs and patterns are consumed by `scanStubUi()` in\n * `src/lib/cloister/lint-stub-ui.ts`, which restricts inspection to added (+)\n * lines in frontend UI affordance files. Reviewers see the emitted findings on\n * the review context manifest and must block unless the diff also shows one of\n * the three valid mitigations: a feature flag guarding the affordance off,\n * removal of the affordance from the user-facing surface, or a non-stub\n * implementation calling real data.\n */\n\nexport type StubUiSeverity = 'block' | 'advisory';\n\nexport interface StubUiFinding {\n patternId: StubUiPatternId;\n patternLabel: string;\n filePath: string;\n lineNumber: number;\n addedLine: string;\n severity: StubUiSeverity;\n}\n\nexport interface StubUiPattern {\n id: StubUiPatternId;\n label: string;\n regex: RegExp;\n severity: StubUiSeverity;\n}\n\n/**\n * Micromatch-compatible globs naming the file types that can introduce\n * user-facing UI affordances. The scanner applies additional status logic:\n * - Added (A) files under `src/dashboard/frontend/src/components/` are in scope.\n * - Modified (M) files are in scope only when they match the `*Tab.tsx`,\n * `*Mode.tsx`, or `*View.tsx` suffix globs, because modifications to generic\n * components are unlikely to introduce brand-new affordances.\n */\nexport const STUB_UI_FILE_GLOBS = [\n 'src/dashboard/frontend/src/components/**/*Tab.tsx',\n 'src/dashboard/frontend/src/components/**/*Mode.tsx',\n 'src/dashboard/frontend/src/components/**/*View.tsx',\n 'src/dashboard/frontend/src/components/**/*.tsx',\n] as const;\n\n/**\n * Stable pattern identifiers. Downstream beads (scanner, prompt) can reference\n * these by name via `typeof STUB_UI_PATTERNS[number]['id']`.\n */\nexport type StubUiPatternId =\n | 'coming-soon-copy'\n | 'empty-array-return'\n | 'null-return'\n | 'use-state-false'\n | 'new-tab-or-route'\n | 'segmented-control-noop'\n | 'noop-handler';\n\n/**\n * Regex catalog of stub shapes. Each pattern is applied to individual added\n * lines, so patterns must not rely on multi-line context.\n */\nexport const STUB_UI_PATTERNS: StubUiPattern[] = [\n {\n id: 'coming-soon-copy',\n label: 'Copy suggests the feature is not implemented (\"coming soon\" / \"not yet implemented\")',\n // Ignore lines that begin with a // comment so TODO/notes don't drown out\n // real user-facing copy. JSX text and quoted strings are still caught.\n regex: /^(?!\\s*\\/\\/).*?\\b(?:[Cc]oming\\s+soon|[Nn]ot\\s+yet\\s+implemented)\\b/,\n severity: 'block',\n },\n {\n id: 'empty-array-return',\n label: 'Hook/function returns an empty array',\n regex: /^\\s*return\\s*\\[\\s*\\]\\s*;/,\n severity: 'block',\n },\n {\n id: 'null-return',\n label: 'Hook/function returns null',\n regex: /^\\s*return\\s+null\\s*;/,\n severity: 'block',\n },\n {\n id: 'use-state-false',\n label: 'Tab/mode state initialized to false with no other writer suspected',\n regex: /\\buseState\\s*\\(\\s*false\\s*\\)/,\n severity: 'advisory',\n },\n {\n id: 'new-tab-or-route',\n label: 'New Tab, TabPanel, or Route JSX entry added',\n regex: /<(?:Tab|TabPanel|Route)\\b/,\n severity: 'advisory',\n },\n {\n id: 'segmented-control-noop',\n label: 'Segmented-control or mode onChange handler is a no-op arrow function',\n // Matches both JSX prop form `onChange={() => {}}` and object form\n // `onChange: () => {},`. The outer braces are optional.\n regex: /onChange\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n {\n id: 'noop-handler',\n label: 'Explicit onClick/onSelect/onSubmit handler is a no-op arrow function',\n // Matches JSX prop form `onClick={() => {}}` and object form\n // `onClick: () => {},`. Outer braces are optional.\n regex: /on(?:Click|Select|Submit)\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n];\n\n/**\n * Convert a micromatch-style glob (supporting `*` and `**`) to a RegExp.\n * This is intentionally minimal: it covers the globs exported above and avoids\n * adding a dependency on `micromatch` for a small, stable set of patterns.\n */\nfunction globToRegex(glob: string): RegExp {\n let pattern = '';\n let i = 0;\n while (i < glob.length) {\n const c = glob[i];\n if (c === '*' && glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n pattern += '(?:.*/)?';\n i += 3;\n continue;\n }\n pattern += '.*';\n i += 2;\n continue;\n }\n if (c === '*') {\n pattern += '[^/]*';\n i += 1;\n continue;\n }\n if (/[.+^${}()|[\\]\\\\]/.test(c)) {\n pattern += `\\\\${c}`;\n i += 1;\n continue;\n }\n pattern += c;\n i += 1;\n }\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * Test whether a repository-relative file path matches any of the stub-UI\n * file globs. This is the public entry point used by the scanner.\n */\nexport function matchesStubUiFileGlob(filePath: string): boolean {\n for (const glob of STUB_UI_FILE_GLOBS) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n\n/**\n * Status-aware scope check. Added component files are in scope because they may\n * introduce brand-new affordances; modified files are inspected only when their\n * basename suggests they are UI affordance files (*Tab/*Mode/*View).\n */\nexport function isStubUiFileInScope(filePath: string, status: 'A' | 'M' | string): boolean {\n if (status === 'A') {\n return matchesStubUiFileGlob(filePath);\n }\n // Modified files: only the explicit affordance suffixes.\n const affordanceGlobs = STUB_UI_FILE_GLOBS.slice(0, 3);\n for (const glob of affordanceGlobs) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n","/**\n * Stub-UI scanner (PAN-1500).\n *\n * Reads the PR diff for a workspace and emits structured findings for added\n * (+) lines that match the STUB_UI_PATTERNS catalog. The scanner restricts\n * inspection to frontend UI affordance files and degrades gracefully: any\n * internal failure returns an empty array and logs a non-fatal warning.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n STUB_UI_PATTERNS,\n isStubUiFileInScope,\n type StubUiFinding,\n} from './stub-ui-patterns.js';\n\nexport type { StubUiFinding };\n\nconst execAsync = promisify(exec);\n\ninterface ChangedFileEntry {\n path: string;\n status: string;\n}\n\nasync function listChangedFiles(workspace: string, diffBase: string): Promise<ChangedFileEntry[]> {\n const { stdout } = await execAsync(\n `git diff --name-status \"${diffBase}\"...HEAD`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n const entries: ChangedFileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line.trim()) continue;\n const parts = line.split('\\t');\n const status = parts[0]?.[0] ?? 'M';\n const path = parts[parts.length - 1] ?? '';\n if (!path) continue;\n entries.push({ path, status });\n }\n return entries;\n}\n\n/**\n * Parse a unified-diff hunk header and return the destination starting line.\n * Headers look like `@@ -3,5 +8,9 @@` or `@@ -1 +1 @@`.\n */\nfunction parseHunkHeader(line: string): number | null {\n const match = line.match(/@@\\s+-\\d+(?:,\\d+)?\\s+\\+(\\d+)(?:,\\d+)?\\s+@@/);\n if (!match) return null;\n return parseInt(match[1], 10);\n}\n\n/**\n * Read the diff for a single file and yield every added line with its\n * destination line number. Uses `-U0` so there are no context lines to skip.\n */\nasync function* addedLinesForFile(\n workspace: string,\n diffBase: string,\n filePath: string,\n): AsyncGenerator<{ lineNumber: number; content: string }> {\n const { stdout } = await execAsync(\n `git diff -U0 \"${diffBase}\"...HEAD -- \"${filePath}\"`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n let currentLine = 0;\n\n for (const rawLine of stdout.split('\\n')) {\n if (rawLine.startsWith('@@')) {\n const parsed = parseHunkHeader(rawLine);\n if (parsed !== null) {\n currentLine = parsed;\n }\n continue;\n }\n\n if (rawLine.startsWith('+++')) continue;\n if (rawLine.startsWith('---')) continue;\n\n if (rawLine.startsWith('+')) {\n const content = rawLine.slice(1);\n yield { lineNumber: currentLine, content };\n currentLine += 1;\n continue;\n }\n\n if (rawLine.startsWith(' ')) {\n currentLine += 1;\n continue;\n }\n\n // Deleted lines do not advance the destination line counter.\n }\n}\n\n/**\n * Scan the PR diff in `workspace` against `diffBase` for stub-UI patterns.\n *\n * Returns a list of findings; never throws. Internal failures (git errors,\n * malformed hunks, missing workspace) log a warning and return [].\n */\nexport async function scanStubUi(workspace: string, diffBase: string): Promise<StubUiFinding[]> {\n try {\n const changedFiles = await listChangedFiles(workspace, diffBase);\n const findings: StubUiFinding[] = [];\n\n for (const { path, status } of changedFiles) {\n if (!isStubUiFileInScope(path, status)) continue;\n\n for await (const { lineNumber, content } of addedLinesForFile(workspace, diffBase, path)) {\n for (const pattern of STUB_UI_PATTERNS) {\n if (pattern.regex.test(content)) {\n findings.push({\n patternId: pattern.id,\n patternLabel: pattern.label,\n filePath: path,\n lineNumber,\n addedLine: content.trimEnd(),\n severity: pattern.severity,\n });\n }\n }\n }\n }\n\n return findings;\n } catch (err) {\n console.warn(\n `[scanStubUi] Failed to scan workspace ${workspace} against ${diffBase}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return [];\n }\n}\n","/**\n * CodeRabbit review ingestion (PAN-2374).\n *\n * Quota invariant: CodeRabbit reviews PRs only sometimes. An empty finding\n * array means any of: no findings, quota miss, CodeRabbit absent, or fetch\n * error. Callers MUST treat all four identically — this module is advisory\n * and must never gate anything.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { lookupPullRequestNumberForBranch } from '../github-pr-lookup.js';\nimport { postPrComment } from './review-verdict-feedback.js';\n\nconst execAsync = promisify(exec);\n\nconst CODERABBIT_BOT_LOGIN = 'coderabbitai[bot]';\n\nexport interface CodeRabbitFinding {\n path?: string;\n line?: number;\n body: string;\n url?: string;\n}\n\n/**\n * Canonical reply template when a CodeRabbit finding has been addressed.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'addressed', finding.body)`\n */\nexport const CODERABBIT_REPLY_ADDRESSED = (finding: string): string =>\n `Thanks for the finding — addressed: ${finding}`;\n\n/**\n * Canonical reply template when a CodeRabbit finding is rejected.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'rejected', finding.body, rationale)`\n */\nexport const CODERABBIT_REPLY_REJECTED = (finding: string, rationale: string): string =>\n `Thanks for the finding — after review we're not applying this: ${finding}. Rationale: ${rationale}`;\n\n/**\n * Reply to a CodeRabbit finding on the PR thread.\n *\n * @param prUrl - The GitHub PR URL.\n * @param disposition - Whether the finding was addressed or rejected.\n * @param finding - The finding text (usually `finding.body`).\n * @param rationale - Required when disposition is 'rejected'; explains why.\n * @returns true if the comment was posted, false on any error (never throws).\n *\n * This is a best-effort reply path and MUST NOT read or write review status.\n */\nexport async function postCodeRabbitReply(\n prUrl: string,\n disposition: 'addressed' | 'rejected',\n finding: string,\n rationale?: string,\n): Promise<boolean> {\n try {\n const body =\n disposition === 'addressed'\n ? CODERABBIT_REPLY_ADDRESSED(finding)\n : CODERABBIT_REPLY_REJECTED(finding, rationale ?? 'no rationale provided');\n return await postPrComment(prUrl, body);\n } catch {\n return false;\n }\n}\n\nasync function resolveOwnerRepo(workspace: string): Promise<{ owner: string; repo: string } | null> {\n try {\n const { stdout } = await execAsync('gh repo view --json owner,name', {\n cwd: workspace,\n encoding: 'utf-8',\n timeout: 15000,\n });\n const parsed = JSON.parse(stdout) as { owner?: { login?: string }; name?: string };\n const owner = parsed.owner?.login;\n const repo = parsed.name;\n if (!owner || !repo) return null;\n return { owner, repo };\n } catch {\n return null;\n }\n}\n\nasync function fetchFindingsFromReviews(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/reviews?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n state?: string;\n html_url?: string;\n }>;\n return arr\n .filter(r => r.user?.login === CODERABBIT_BOT_LOGIN && r.state !== 'DISMISSED')\n .map(r => ({\n body: r.body ?? '',\n url: r.html_url,\n }));\n}\n\nasync function fetchFindingsFromComments(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/comments?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n html_url?: string;\n path?: string;\n line?: number | null;\n }>;\n return arr\n .filter(c => c.user?.login === CODERABBIT_BOT_LOGIN)\n .map(c => ({\n path: c.path,\n line: typeof c.line === 'number' ? c.line : undefined,\n body: c.body ?? '',\n url: c.html_url,\n }));\n}\n\nexport async function fetchCodeRabbitFindings(opts: {\n workspace: string;\n branch: string;\n}): Promise<CodeRabbitFinding[]> {\n try {\n const ownerRepo = await resolveOwnerRepo(opts.workspace);\n if (!ownerRepo) return [];\n const { owner, repo } = ownerRepo;\n\n const prNumber = await lookupPullRequestNumberForBranch(owner, repo, opts.branch);\n if (!prNumber) return [];\n\n const [reviewFindings, commentFindings] = await Promise.all([\n fetchFindingsFromReviews(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n fetchFindingsFromComments(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n ]);\n\n return [...reviewFindings, ...commentFindings];\n } catch (err) {\n console.warn(\n `[coderabbit-ingestion] fetch failed — skipping: ${err instanceof Error ? err.message : String(err)}`,\n );\n return [];\n }\n}\n","/**\n * Review Context Manifest Builder (PAN-1059)\n *\n * Builds a shared `.pan/review/<runId>/context.json` before spawning\n * any sub-reviewers. All four convoy agents read this file instead of\n * independently running `git diff` and reading every changed file,\n * eliminating ~4× redundant I/O and the token cost that goes with it.\n */\n\nimport { exec } from 'child_process';\nimport { existsSync } from 'fs';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { findPlanSync, readPlan } from '../xbrief/io.js';\nimport { scanStubUi, type StubUiFinding } from './lint-stub-ui.js';\nimport { fetchCodeRabbitFindings, type CodeRabbitFinding } from './coderabbit-ingestion.js';\nimport { findXBriefByIssueSync } from '../xbrief/lifecycle-io.js';\nimport { getDevrootPathSync } from '../config.js';\nimport { FsError } from '../errors.js';\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\n\nconst execAsync = promisify(exec);\n\nexport const REVIEW_LARGE_CHANGESET_FILES = 25;\nexport const REVIEW_LARGE_CHANGESET_LINES = 1500;\n\n// The manifest no longer embeds raw diff text (PAN-1125).\n// Reviewers receive a concise inline summary in their spawn prompt and read\n// individual files on demand. The manifest carries metadata only: stat,\n// changedFiles, acceptanceCriteria, and policyNotes.\n\nconst RISK_HIGH = 5;\nconst RISK_MED = 3;\nconst RISK_LOW = 1;\n\n// Patterns that raise a file's risk score\nconst HIGH_RISK_PATTERNS = [\n /auth/i, /password/i, /token/i, /secret/i, /crypt/i,\n /permission/i, /privilege/i, /admin/i, /acl/i, /rbac/i,\n /payment/i, /billing/i, /stripe/i,\n /sql/i, /query/i, /inject/i,\n /exec\\b/i, /spawn/i, /shell/i, /eval\\b/i,\n];\n\nconst MED_RISK_PATTERNS = [\n /config/i, /env/i, /setting/i, /migration/i,\n /middleware/i, /route/i, /api/i,\n];\n\nconst LOW_RISK_PATTERNS = [\n /test/i, /spec/i, /mock/i, /fixture/i, /stub/i,\n /\\.md$/, /\\.txt$/, /\\.json$/, /README/i,\n];\n\nexport interface ChangedFile {\n path: string;\n status: 'M' | 'A' | 'D' | 'R' | 'C' | 'U';\n additions: number;\n deletions: number;\n riskScore: number;\n}\n\nexport interface ReviewItemTrace {\n itemId: string;\n title: string;\n traces: string[];\n}\n\n/** Per-repo git context for polyrepo workspaces (PAN-2948). Additive — absent\n * or single-entry for monorepo workspaces, so existing consumers are untouched. */\nexport interface ReviewRepoContext {\n repoKey: string;\n branch: string;\n headSha: string;\n diffBase: string;\n fileCount: number;\n}\n\nexport interface ReviewContextManifest {\n runId: string;\n issueId: string;\n generatedAt: string;\n branch: string;\n headSha: string;\n /** Present when the workspace is polyrepo: one entry per sub-repo reviewed. */\n repos?: ReviewRepoContext[];\n diff: {\n stat: string;\n truncated: boolean;\n };\n changedFiles: ChangedFile[];\n largeChangeset: {\n fileCount: number;\n changedLines: number;\n isLarge: boolean;\n };\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n policyNotes: string[];\n stubUiFindings: StubUiFinding[];\n codeRabbitFindings: CodeRabbitFinding[];\n manifestPath: string;\n}\n\nfunction riskScore(filePath: string): number {\n if (LOW_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_LOW;\n if (HIGH_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_HIGH;\n if (MED_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_MED;\n return 2; // default: between low and medium\n}\n\nasync function getHeadSha(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git rev-parse HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return 'unknown';\n }\n}\n\nasync function getCurrentBranch(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, encoding: 'utf-8' });\n return stdout.trim() || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport async function getDiffBase(cwd: string, targetBranch = 'main'): Promise<string> {\n try {\n const { stdout } = await execAsync(`git merge-base origin/${targetBranch} HEAD`, { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n try {\n const { stdout } = await execAsync(`git merge-base ${targetBranch} HEAD`, { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return targetBranch;\n }\n }\n}\n\nexport async function getChangedFiles(cwd: string, base: string, pathPrefix = ''): Promise<ChangedFile[]> {\n // --name-status gives us the status letter + path\n let nameStatus = '';\n try {\n const { stdout } = await execAsync(\n `git diff --name-status \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n nameStatus = stdout;\n } catch {\n return [];\n }\n\n // --numstat gives us additions + deletions per file\n const numstatMap = new Map<string, { additions: number; deletions: number }>();\n try {\n const { stdout } = await execAsync(\n `git diff --numstat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n for (const line of stdout.split('\\n')) {\n const parts = line.split('\\t');\n if (parts.length >= 3) {\n const additions = parseInt(parts[0], 10) || 0;\n const deletions = parseInt(parts[1], 10) || 0;\n // Binary files show '-'; treat as 0\n numstatMap.set(parts[2], { additions, deletions });\n }\n }\n } catch {\n // numstat failure is non-fatal; additions/deletions default to 0\n }\n\n const files: ChangedFile[] = [];\n for (const line of nameStatus.split('\\n')) {\n if (!line.trim()) continue;\n const [statusChar, ...pathParts] = line.split('\\t');\n const path = pathParts[pathParts.length - 1] ?? '';\n if (!path) continue;\n\n const statusLetter = (statusChar?.[0] ?? 'M') as ChangedFile['status'];\n const counts = numstatMap.get(path) ?? { additions: 0, deletions: 0 };\n const prefixedPath = `${pathPrefix}${path}`;\n\n files.push({\n path: prefixedPath,\n status: statusLetter,\n additions: counts.additions,\n deletions: counts.deletions,\n riskScore: riskScore(prefixedPath),\n });\n }\n\n // Sort descending by risk score so reviewers see hotspots first\n return files.sort((a, b) => b.riskScore - a.riskScore);\n}\n\nexport async function getDiffStat(cwd: string, base: string): Promise<{ stat: string; truncated: boolean }> {\n let stat = '';\n\n try {\n const { stdout } = await execAsync(\n `git diff --stat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n stat = stdout.trim() || 'No changes';\n } catch {\n stat = 'Unable to compute diff stat';\n }\n\n // We no longer embed raw diff text in the manifest (PAN-1125).\n // Reviewers read individual files via Read/Grep as needed.\n return { stat, truncated: true };\n}\n\ninterface PlanReviewRequirements {\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n}\n\nasync function extractPlanReviewRequirements(workspace: string, issueId: string): Promise<PlanReviewRequirements> {\n // Try workspace-local spec first\n const planPath = findPlanSync(workspace);\n if (planPath) {\n try {\n const doc = await Effect.runPromise(readPlan(planPath));\n return {\n acceptanceCriteria: flattenAC(doc),\n nonGoals: flattenNonGoals(doc),\n traces: flattenTraces(doc),\n };\n } catch {\n // Fall through to lifecycle lookup\n }\n }\n\n // Try project-root lifecycle directories\n try {\n const projectRoot = getDevrootPathSync();\n if (!projectRoot) return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n const found = findXBriefByIssueSync(projectRoot, issueId);\n if (found) {\n return {\n acceptanceCriteria: flattenAC(found.document),\n nonGoals: flattenNonGoals(found.document),\n traces: flattenTraces(found.document),\n };\n }\n } catch {\n // Non-fatal\n }\n\n return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n}\n\ninterface PanItem {\n id?: string;\n title?: string;\n acceptanceCriteria?: string[];\n subItems?: Array<{ title?: string; description?: string }>;\n metadata?: Record<string, unknown>;\n}\n\nfunction flattenAC(doc: { plan?: { items?: PanItem[] } }): string[] {\n const acs: string[] = [];\n for (const item of doc?.plan?.items ?? []) {\n // Planning agent writes acceptanceCriteria directly on items\n if (Array.isArray(item.acceptanceCriteria)) {\n acs.push(...item.acceptanceCriteria);\n }\n // Standard xBRIEF v0.5 sub-items\n for (const sub of item.subItems ?? []) {\n const text = sub.title ?? sub.description ?? '';\n if (text) acs.push(text);\n }\n }\n return acs;\n}\n\nfunction flattenNonGoals(doc: { plan?: { narratives?: Record<string, string | undefined> } }): string[] {\n const raw = doc.plan?.narratives?.NonGoals?.trim();\n if (!raw || raw.toLowerCase() === 'none') return [];\n return raw\n .split('\\n')\n .map(line => line.trim().replace(/^[-*]\\s*/, '').trim())\n .filter(Boolean);\n}\n\nfunction flattenTraces(doc: { plan?: { items?: PanItem[] } }): ReviewItemTrace[] {\n const traces: ReviewItemTrace[] = [];\n for (const item of doc.plan?.items ?? []) {\n const raw = item.metadata?.traces;\n if (!Array.isArray(raw)) continue;\n const itemTraces = raw.filter((trace): trace is string => typeof trace === 'string' && trace.trim().length > 0);\n if (itemTraces.length === 0) continue;\n traces.push({\n itemId: item.id ?? '<unknown>',\n title: item.title ?? '',\n traces: itemTraces,\n });\n }\n return traces;\n}\n\nasync function readPolicyNotes(workspace: string): Promise<string[]> {\n const notes: string[] = [];\n\n // Pull CRITICAL rules from CLAUDE.md\n const claudeMdPath = join(workspace, 'CLAUDE.md');\n if (existsSync(claudeMdPath)) {\n try {\n const content = await readFile(claudeMdPath, 'utf-8');\n const criticalLines = content\n .split('\\n')\n .filter(l => l.startsWith('## CRITICAL') || l.startsWith('**NEVER') || l.startsWith('**CRITICAL'))\n .map(l => l.replace(/^#+\\s*/, '').replace(/^\\*+/, '').trim())\n .filter(Boolean);\n notes.push(...criticalLines.slice(0, 10));\n } catch {\n // Non-fatal\n }\n }\n\n return notes;\n}\n\nexport interface BuildReviewContextOpts {\n runId: string;\n issueId: string;\n workspace: string;\n branch?: string;\n}\n\n/**\n * Build and persist the review context manifest for a review run.\n *\n * Returns the manifest object and its path on disk.\n * Throws if the workspace directory does not exist.\n */\n/**\n * Format a concise Tier-1 inline summary from manifest fields.\n *\n * This summary is embedded directly into each convoy reviewer's spawn prompt\n * so they receive scope, priority, and constraints without reading a large\n * manifest file first (PAN-1125).\n */\nexport function formatTier1Summary(\n manifest: Pick<\n ReviewContextManifest,\n | 'issueId' | 'branch' | 'headSha' | 'repos' | 'changedFiles' | 'acceptanceCriteria' | 'nonGoals' | 'traces' | 'policyNotes' | 'stubUiFindings' | 'codeRabbitFindings' | 'diff' | 'largeChangeset'\n >,\n): string {\n const lines: string[] = [];\n\n lines.push(`Issue: ${manifest.issueId}`);\n lines.push(`Branch: ${manifest.branch}`);\n lines.push(`Head: ${manifest.headSha}`);\n\n if (manifest.repos && manifest.repos.length > 1) {\n lines.push('Polyrepo workspace — changed-file paths are prefixed with the sub-repo name:');\n for (const repo of manifest.repos) {\n lines.push(` ${repo.repoKey}: ${repo.branch} @ ${repo.headSha.slice(0, 8)} (${repo.fileCount} files changed)`);\n }\n }\n\n const highRisk = manifest.changedFiles.filter((f) => f.riskScore >= 5);\n const medRisk = manifest.changedFiles.filter((f) => f.riskScore >= 3 && f.riskScore < 5);\n const lowRisk = manifest.changedFiles.filter((f) => f.riskScore < 3);\n lines.push(\n `Files changed: ${manifest.changedFiles.length} (${highRisk.length} high-risk, ${medRisk.length} medium, ${lowRisk.length} low)`,\n );\n\n if (manifest.changedFiles.length > 0) {\n lines.push('');\n lines.push('Changed files (risk-ranked):');\n for (const f of manifest.changedFiles.slice(0, 15)) {\n const riskLabel = f.riskScore >= 5 ? 'HIGH' : f.riskScore >= 3 ? 'MED' : 'LOW';\n lines.push(` ${f.path.padEnd(40)} (+${f.additions}/-${f.deletions}) risk: ${riskLabel}`);\n }\n if (manifest.changedFiles.length > 15) {\n lines.push(` ... and ${manifest.changedFiles.length - 15} more (see manifest)`);\n }\n }\n\n if (manifest.largeChangeset?.isLarge) {\n lines.push('');\n lines.push(`LARGE CHANGESET: ${manifest.largeChangeset.fileCount} files, ${manifest.largeChangeset.changedLines} changed lines.`);\n lines.push('Read the highest-risk files first, then medium-risk. Read only changed regions, not whole files.');\n lines.push('If you approach the context limit, write a report on what you reviewed and flag any uncovered high-risk files as a BLOCKING coverage gap.');\n lines.push('Do not read everything and overflow; do not approve changes you have not reviewed.');\n }\n\n if (manifest.acceptanceCriteria.length > 0) {\n lines.push('');\n lines.push('Acceptance criteria:');\n for (const [i, ac] of manifest.acceptanceCriteria.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${ac}`);\n }\n if (manifest.acceptanceCriteria.length > 7) {\n lines.push(` ... and ${manifest.acceptanceCriteria.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.nonGoals.length > 0) {\n lines.push('');\n lines.push('NonGoals / must-not constraints:');\n for (const [i, nonGoal] of manifest.nonGoals.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${nonGoal}`);\n }\n if (manifest.nonGoals.length > 7) {\n lines.push(` ... and ${manifest.nonGoals.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.traces.length > 0) {\n lines.push('');\n lines.push('Requirement traces:');\n for (const trace of manifest.traces.slice(0, 7)) {\n lines.push(` ${trace.itemId}: ${trace.traces.join(', ')}`);\n }\n if (manifest.traces.length > 7) {\n lines.push(` ... and ${manifest.traces.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.policyNotes.length > 0) {\n lines.push('');\n lines.push('Policy notes:');\n for (const note of manifest.policyNotes.slice(0, 5)) {\n lines.push(` - ${note}`);\n }\n if (manifest.policyNotes.length > 5) {\n lines.push(` ... and ${manifest.policyNotes.length - 5} more (see manifest)`);\n }\n }\n\n if (manifest.stubUiFindings.length > 0) {\n lines.push('');\n lines.push('Stub UI findings (BLOCKING if unmitigated):');\n for (const finding of manifest.stubUiFindings.slice(0, 10)) {\n lines.push(` - ${finding.patternLabel} @ ${finding.filePath}:${finding.lineNumber}`);\n }\n if (manifest.stubUiFindings.length > 10) {\n lines.push(` ... and ${manifest.stubUiFindings.length - 10} more (see manifest)`);\n }\n }\n\n if (manifest.codeRabbitFindings.length > 0) {\n lines.push('');\n lines.push('CodeRabbit findings (ADVISORY — non-gating; context, not acceptance criteria):');\n for (const finding of manifest.codeRabbitFindings.slice(0, 10)) {\n const location = finding.path ? `${finding.path}${typeof finding.line === 'number' ? `:${finding.line}` : ''}` : 'general';\n const bodyPreview = finding.body.slice(0, 200);\n lines.push(` - ${location} ${bodyPreview}${finding.body.length > 200 ? '...' : ''}`);\n }\n if (manifest.codeRabbitFindings.length > 10) {\n lines.push(` ... and ${manifest.codeRabbitFindings.length - 10} more (see manifest)`);\n }\n }\n\n lines.push('');\n lines.push(`Diff stat: ${manifest.diff.stat}`);\n\n return lines.join('\\n');\n}export async function buildReviewContextPromise(opts: BuildReviewContextOpts): Promise<ReviewContextManifest> {\n const { runId, issueId, workspace } = opts;\n\n if (!existsSync(workspace)) {\n throw new Error(`Workspace directory does not exist: ${workspace}`);\n }\n\n // PAN-2948: a polyrepo workspace root is a one-commit wrapper repo whose\n // .gitignore excludes the code sub-repos — diffing it always yields an empty\n // manifest. Resolve the actual repo roots and build per-repo, aggregating\n // with repo-prefixed paths. Monorepo resolves to one root at the workspace.\n const roots = resolveWorkspaceRepoRootsSync(issueId, workspace);\n const isPolyrepo = roots.some(root => root.isPolyrepo);\n\n const perRepo = await Promise.all(roots.map(async root => {\n const prefix = isPolyrepo ? `${root.repoKey}/` : '';\n const [headSha, branch, diffBase] = await Promise.all([\n getHeadSha(root.dir),\n getCurrentBranch(root.dir),\n getDiffBase(root.dir, root.targetBranch),\n ]);\n const [changedFiles, diffStat, stubUiFindings] = await Promise.all([\n getChangedFiles(root.dir, diffBase, prefix),\n getDiffStat(root.dir, diffBase),\n scanStubUi(root.dir, diffBase).catch((err) => {\n console.warn(`[buildReviewContext] scanStubUi failed for ${root.repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n return [] as StubUiFinding[];\n }),\n ]);\n return {\n root,\n headSha,\n branch,\n diffBase,\n changedFiles,\n diffStat,\n stubUiFindings: prefix\n ? stubUiFindings.map(finding => ({ ...finding, filePath: `${prefix}${finding.filePath}` }))\n : stubUiFindings,\n };\n }));\n\n // Primary repo drives the top-level branch/headSha: the first root with\n // changes (for MYN that's fe), falling back to the first root.\n const primary = perRepo.find(repo => repo.changedFiles.length > 0) ?? perRepo[0];\n const headSha = primary.headSha;\n // Polyrepo: opts.branch describes the wrapper (\"master\") — the primary\n // sub-repo's branch is the one reviewers should see.\n const currentBranch = isPolyrepo ? primary.branch : (opts.branch ?? primary.branch);\n const changedFiles = perRepo\n .flatMap(repo => repo.changedFiles)\n .sort((a, b) => b.riskScore - a.riskScore);\n const diff = isPolyrepo\n ? {\n stat: perRepo\n .filter(repo => repo.changedFiles.length > 0)\n .map(repo => `── ${repo.root.repoKey} ──\\n${repo.diffStat.stat}`)\n .join('\\n') || 'No changes',\n truncated: true,\n }\n : primary.diffStat;\n const stubUiFindings = perRepo.flatMap(repo => repo.stubUiFindings);\n const repoContexts: ReviewRepoContext[] = perRepo.map(repo => ({\n repoKey: repo.root.repoKey,\n branch: repo.branch,\n headSha: repo.headSha,\n diffBase: repo.diffBase,\n fileCount: repo.changedFiles.length,\n }));\n\n const [planRequirements, policyNotes, codeRabbitFindings] = await Promise.all([\n extractPlanReviewRequirements(workspace, issueId),\n readPolicyNotes(workspace),\n fetchCodeRabbitFindings({ workspace, branch: currentBranch }).catch((err) => {\n console.warn(`[buildReviewContext] CodeRabbit ingestion failed: ${err instanceof Error ? err.message : String(err)}`);\n return [] as CodeRabbitFinding[];\n }),\n ]);\n\n const fileCount = changedFiles.length;\n const changedLines = changedFiles.reduce((sum, f) => sum + f.additions + f.deletions, 0);\n const largeChangeset = {\n fileCount,\n changedLines,\n isLarge: fileCount > REVIEW_LARGE_CHANGESET_FILES || changedLines > REVIEW_LARGE_CHANGESET_LINES,\n };\n\n const manifestDir = join(workspace, PAN_DIRNAME, 'review', runId);\n await mkdir(manifestDir, { recursive: true });\n const manifestPath = join(manifestDir, 'context.json');\n\n const manifest: ReviewContextManifest = {\n runId,\n issueId,\n generatedAt: new Date().toISOString(),\n branch: currentBranch,\n headSha,\n ...(isPolyrepo ? { repos: repoContexts } : {}),\n diff,\n changedFiles,\n largeChangeset,\n acceptanceCriteria: planRequirements.acceptanceCriteria,\n nonGoals: planRequirements.nonGoals,\n traces: planRequirements.traces,\n policyNotes,\n stubUiFindings,\n codeRabbitFindings,\n manifestPath,\n };\n\n await writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');\n\n return manifest;\n}\n\n// ─── Effect variant (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link buildReviewContext}. Wraps the Promise-based\n * implementation in `Effect.tryPromise` so callers in Effect pipelines can\n * compose it with typed error channels. The git probes inside\n * {@link buildReviewContext} are best-effort — they fall back to sentinel\n * strings instead of failing — so the only error this Effect can surface is\n * the workspace-not-found {@link FsError}.\n */\nexport const buildReviewContext = (\n opts: BuildReviewContextOpts,\n): Effect.Effect<ReviewContextManifest, FsError> =>\n Effect.tryPromise({\n try: () => buildReviewContextPromise(opts),\n catch: (cause) =>\n new FsError({\n path: opts.workspace,\n operation: 'buildReviewContext',\n cause,\n }),\n });\n","import { join } from 'node:path';\n\nimport {\n markRetainedTranscripts,\n removeAgentStateDir,\n type RemoveAgentStateDirResult,\n} from './state-dir-removal.js';\nimport { getAgentStateSync } from './agent-state.js';\nimport {\n ensureAgentTombstoneSync,\n listAllAgentsSync,\n removeAgentRecordSync,\n type AgentTombstoneIdentity,\n} from '../overdeck/agents.js';\nimport { getOverdeckHome } from '../paths.js';\n\nfunction resolveTombstoneIdentity(agentId: string): AgentTombstoneIdentity | null {\n const source = listAllAgentsSync().find((agent) => agent.id === agentId) ?? getAgentStateSync(agentId);\n if (!source || source.id !== agentId) return null;\n const fields = [source.issueId, source.role, source.workspace, source.harness, source.model];\n if (fields.some((value) => typeof value !== 'string' || value.length === 0)) return null;\n return {\n id: agentId,\n issueId: source.issueId,\n role: source.role,\n workspace: source.workspace,\n harness: source.harness,\n model: source.model,\n } as AgentTombstoneIdentity;\n}\n\n/**\n * Remove runtime residue while retaining every JSONL transcript and its registry\n * linkage. The row becomes a stopped tombstone until configured retention expires\n * the final transcript; a fully removed directory permits immediate row removal.\n */\nexport async function removeAgent(agentId: string): Promise<RemoveAgentStateDirResult> {\n const identity = resolveTombstoneIdentity(agentId);\n if (identity === null) {\n throw new Error(`removeAgent: cannot preserve transcript linkage for ${agentId}`);\n }\n ensureAgentTombstoneSync(identity);\n\n const agentsDir = join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsDir, agentId);\n const result = await removeAgentStateDir(agentDir, agentsDir);\n if (result.removedDir) removeAgentRecordSync(agentId);\n else await markRetainedTranscripts(agentDir);\n return result;\n}\n","/**\n * PAN-1862 — the resume-vs-fresh decision for a review agent, as a PURE function so it is locked\n * by tests and shared by both quick review (the parent) and convoy (the sub-reviewers).\n *\n * RESUME (preserve the prior review's context — files read, findings raised — so a re-review\n * checks the fix instead of re-researching the whole diff) when there is a saved, resumable\n * session AND the requested harness/model is unchanged. Fresh-spawn ONLY when the harness or\n * model actually changed (it's a different agent then) or there is no resumable session. A change\n * is detected only when BOTH the requested and saved values are known and differ — an unspecified\n * request (use role default) or an unknown saved value never forces a wipe.\n */\nexport function reviewResumeDecision(params: {\n requestedModel?: string;\n requestedHarness?: string;\n savedModel?: string;\n savedHarness?: string;\n hasSavedState: boolean;\n hasSavedSession: boolean;\n}): boolean {\n const modelChanged =\n !!params.requestedModel && !!params.savedModel && params.requestedModel !== params.savedModel;\n const harnessChanged =\n !!params.requestedHarness && !!params.savedHarness && params.requestedHarness !== params.savedHarness;\n return params.hasSavedState && params.hasSavedSession && !modelChanged && !harnessChanged;\n}\n","/**\n * PAN-1862: the review CONVOY — sub-reviewer prompts and spawning, plus\n * missing-reviewer recovery.\n *\n * Split out of review-agent.ts (which keeps the review entry point, the\n * synthesis/self-review prompts, mode resolution, and session teardown).\n * This module must NEVER import review-agent.ts — review-agent imports (and\n * re-exports) from here, and a reverse edge would close a cycle.\n *\n * The four convoy sub-roles are security, correctness, performance, and\n * requirements (REVIEW_SUB_ROLES, review-monitor.ts); the synthesis parent is\n * the fifth review role. See docs/REVIEW-AGENT-ARCHITECTURE.md.\n */\n\nimport { mkdir, readFile, rm } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport { Effect } from 'effect';\nimport { listSessionNames } from '../tmux.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\nimport { formatTier1Summary, type ReviewContextManifest } from './review-context.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nimport { reviewResumeDecision } from './review-resume-decision.js';\nimport { readIssueRecordSync, resolveProjectForIssue } from '../pan-dir/record.js';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { AGENTS_DIR, packageRoot } from '../paths.js';\nimport type { RuntimeName } from '../runtimes/types.js';\n\n\n/**\n * Read a convoy sub-role prompt template from the overdeck install.\n *\n * Sub-role prompts are harness-agnostic templates owned by Overdeck. The\n * orchestrator reads them from its own install (packageRoot/roles/) and\n * inlines the body into the spawn message — they never live in the agent's\n * workspace, and they are never loaded via the Claude-specific `--agent` flag.\n * That keeps the same prompt content driving Claude Code, Pi, Codex, or any\n * future harness, and prevents a work agent from ambiently discovering its\n * own reviewer prompts in the workspace tree.\n */\nasync function readConvoySubRoleTemplate(subRole: string): Promise<string> {\n const path = join(packageRoot, 'roles', `review-${subRole}.md`);\n return readFile(path, 'utf-8');\n}\n\n\n\nconst REVIEWER_TIMEOUT_MS = 20 * 60 * 1000;\nconst REVIEWER_SPAWN_BACKOFF_DELAYS_MS = [100, 250, 500, 1_000, 2_000] as const;\n\nfunction reviewerAgentId(issueId: string, subRole: ReviewSubRole): string {\n return `agent-${issueId.toLowerCase()}-review-${subRole}`;\n}\n\nfunction isReviewerStateWriteContention(error: unknown, agentId: string): boolean {\n const message = error instanceof Error ? error.message : String(error);\n return message.includes(`agents-db:${agentId}`)\n && /database is locked|SQLITE_BUSY/i.test(message);\n}\n\nasync function reviewerSessionIsLive(agentId: string): Promise<boolean> {\n try {\n return (await Effect.runPromise(listSessionNames())).includes(agentId);\n } catch {\n return false;\n }\n}\n\nfunction reviewerAgentOutputPath(workspace: string, runId: string, subRole: ReviewSubRole): string {\n return join(workspace, PAN_DIRNAME, 'review', runId, `${subRole}.md`);\n}\n\n\n\nasync function buildConvoyPromptPromise(opts: {\n issueId: string;\n subRole: string;\n outputPath: string;\n synthesisAgentId: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): Promise<string> {\n const template = await readConvoySubRoleTemplate(opts.subRole);\n const prompt = [\n `REVIEW TASK for ${opts.issueId} — ${opts.subRole.toUpperCase()} REVIEW:`,\n '',\n `Issue: ${opts.issueId}`,\n `Sub-role: ${opts.subRole}`,\n '',\n 'Output file — write your full findings here when done:',\n ` ${opts.outputPath}`,\n '',\n opts.tier1Summary\n ? [\n 'Shared review context (read this first; do not run git diff yourself):',\n '─────────────────────────────────────────────────────────────',\n opts.tier1Summary,\n '─────────────────────────────────────────────────────────────',\n '',\n opts.contextManifestPath\n ? `Full manifest (read on demand for additional detail): ${opts.contextManifestPath}`\n : '',\n ].join('\\n')\n : opts.contextManifestPath\n ? [\n 'Context manifest (read this first; do not run git diff yourself):',\n ` ${opts.contextManifestPath}`,\n 'The manifest contains per-file risk ranking and acceptance criteria.',\n ].join('\\n')\n : 'No context manifest available. Write a blocked reviewer report explaining that the shared review context is missing.',\n '',\n '─────────────────────────────────────────────────────────────',\n 'REVIEW METHODOLOGY (inlined from roles/review-' + opts.subRole + '.md):',\n '─────────────────────────────────────────────────────────────',\n '',\n template.trim(),\n '',\n '─────────────────────────────────────────────────────────────',\n '',\n 'Write exactly one final report to the output file shown above, then stop',\n 'and wait. You do NOT need to signal synthesis or run any pan command —',\n 'when you finish your turn with the report written, Overdeck detects it',\n 'and signals the synthesis agent REVIEWER_READY automatically. Your only',\n 'job is to write the report file, then stop.',\n 'Only the output file is consumed by synthesis; your chat response is not the review report.',\n ].filter(Boolean).join('\\n');\n\n const sizeBytes = Buffer.byteLength(prompt, 'utf-8');\n console.log(`[review-agent] Convoy prompt for ${opts.issueId}/${opts.subRole}: ${sizeBytes} bytes`);\n return prompt;\n}\n\n\n\nasync function spawnReviewSubRoleForIssuePromise(opts: {\n issueId: string;\n workspace: string;\n subRole: ReviewSubRole;\n runId: string;\n outputPath?: string;\n contextManifestPath?: string;\n synthesisAgentId?: string;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}): Promise<{ success: boolean; message: string; error?: string; sessionId?: string }> {\n try {\n const { saveAgentState, spawnRun, getAgentStateSync, getLatestSessionIdSync, resumeAgent, stopAgent } = await import('../agents.js');\n const cfg = loadYamlConfig().config;\n const outputPath = opts.outputPath ?? reviewerAgentOutputPath(opts.workspace, opts.runId, opts.subRole);\n const synthesisAgentId = opts.synthesisAgentId ?? `agent-${opts.issueId.toLowerCase()}-review`;\n const model = opts.model ?? resolveModel('review', opts.subRole, cfg, opts.subRole ? undefined : `review:${opts.issueId}`);\n const reviewerDir = join(AGENTS_DIR, reviewerAgentId(opts.issueId, opts.subRole));\n\n await mkdir(dirname(outputPath), { recursive: true });\n // These are named runtime marker files below the agent-dir deletion door.\n await rm(outputPath, { force: true }); // PAN-3357: not a dir removal\n await rm(join(reviewerDir, 'reviewer-signaled'), { force: true }); // PAN-3357: not a dir removal\n await rm(join(reviewerDir, 'reviewer-launcher.pid'), { force: true }); // PAN-3357: not a dir removal\n\n // Build Tier-1 inline summary from manifest when available (PAN-1125)\n let tier1Summary: string | undefined;\n if (opts.contextManifestPath) {\n try {\n const manifestRaw = await readFile(opts.contextManifestPath, 'utf-8');\n const manifest = JSON.parse(manifestRaw) as ReviewContextManifest;\n tier1Summary = formatTier1Summary(manifest);\n } catch (manifestErr) {\n console.warn(`[review-agent] Failed to read manifest for Tier-1 summary (${opts.issueId}/${opts.subRole}):`, manifestErr);\n }\n }\n\n const prompt = await Effect.runPromise(buildConvoyPrompt({\n issueId: opts.issueId,\n subRole: opts.subRole,\n outputPath,\n synthesisAgentId,\n contextManifestPath: opts.contextManifestPath,\n tier1Summary,\n }));\n\n // PAN-1862: convoy sub-reviewers RESUME by default too — same rule as quick review. Each\n // lane keeps its prior round's context so a re-review checks the fix instead of re-reading\n // the whole diff. Fresh-spawn only on a harness/model change or when no session exists.\n const reviewerAgent = reviewerAgentId(opts.issueId, opts.subRole);\n const savedReviewer = getAgentStateSync(reviewerAgent);\n const canResumeReviewer = reviewResumeDecision({\n requestedModel: opts.model ?? model,\n requestedHarness: opts.harness,\n savedModel: savedReviewer?.model,\n savedHarness: savedReviewer?.harness,\n hasSavedState: !!savedReviewer,\n hasSavedSession: !!getLatestSessionIdSync(reviewerAgent),\n });\n if (canResumeReviewer) {\n console.log(`[review-agent] Resuming convoy sub-reviewer ${opts.subRole} for ${opts.issueId} — preserving context (PAN-1862)`);\n const resumeResult = await resumeAgent(reviewerAgent, prompt);\n if (resumeResult.success && resumeResult.messageDelivered !== false) {\n try {\n const resumed = getAgentStateSync(reviewerAgent);\n if (resumed) {\n resumed.reviewSubRole = opts.subRole;\n resumed.reviewRunId = opts.runId;\n resumed.reviewOutputPath = outputPath;\n resumed.reviewSynthesisAgentId = synthesisAgentId;\n resumed.reviewDeadlineAt = new Date(Date.now() + REVIEWER_TIMEOUT_MS).toISOString();\n delete resumed.reviewMonitorSignaled;\n delete resumed.reviewRetryAttempt;\n await Effect.runPromise(saveAgentState(resumed));\n }\n } catch { /* non-fatal */ }\n return { success: true, message: `Review ${opts.subRole} resumed (session preserved): ${reviewerAgent}`, sessionId: reviewerAgent };\n }\n const resumeError = resumeResult.messageDelivered === false\n ? 'continue prompt was not confirmed in the resumed session'\n : resumeResult.error;\n console.warn(`[review-agent] Convoy sub-reviewer ${opts.subRole} resume failed; falling back to a fresh session: ${resumeError}`);\n // PAN-2743: Codex reviewers intentionally remain alive at their prompt after\n // finishing a cycle. A successful process resume whose prompt did not land is\n // also live but unusable. Stop either live collision before the fresh spawn;\n // other resume failures already imply no live session and need no teardown.\n if (resumeResult.messageDelivered === false || resumeResult.error?.includes('it appears healthy')) {\n await Effect.runPromise(stopAgent(reviewerAgent));\n }\n }\n\n const reviewDeadlineAt = new Date(Date.now() + REVIEWER_TIMEOUT_MS).toISOString();\n const spawnOptions = {\n workspace: opts.workspace,\n subRole: opts.subRole,\n prompt,\n model,\n harness: opts.harness,\n // Persist every signal-routing field before tmux launch. A later\n // running-state cache write may contend, but the reviewer can still\n // finish and the Stop-hook can still deliver REVIEWER_READY.\n reviewRunId: opts.runId,\n reviewSynthesisAgentId: synthesisAgentId,\n reviewOutputPath: outputPath,\n reviewDeadlineAt,\n allowHost: opts.allowHost ?? false,\n startedBy: 'review-convoy' as const,\n };\n const agentId = reviewerAgentId(opts.issueId, opts.subRole);\n let run: Awaited<ReturnType<typeof spawnRun>>;\n for (let attempt = 0; ; attempt += 1) {\n try {\n run = await spawnRun(opts.issueId, 'review', spawnOptions);\n break;\n } catch (err) {\n if (!isReviewerStateWriteContention(err, agentId)) throw err;\n if (await reviewerSessionIsLive(agentId)) {\n console.warn(\n `[review-agent] ${agentId} launched but its running-state cache write was contended; `\n + 'waiting for its report instead of emitting REVIEWER_FAILED',\n );\n return {\n success: true,\n message: `Review ${opts.subRole} launched with deferred cache reconciliation: ${agentId}`,\n sessionId: agentId,\n };\n }\n const delay = REVIEWER_SPAWN_BACKOFF_DELAYS_MS[attempt];\n if (delay === undefined) throw err;\n console.warn(\n `[review-agent] ${agentId} state write was contended before launch; retrying in ${delay}ms`,\n );\n await new Promise<void>((resolve) => { setTimeout(resolve, delay); });\n }\n }\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({ type: 'reviewer_started', issueId: opts.issueId, role: opts.subRole, sessionName: run.id });\n } catch {\n // Non-fatal\n }\n return { success: true, message: `Review ${opts.subRole} spawned: ${run.id}`, sessionId: run.id };\n } catch (err) {\n return {\n success: false,\n message: `Failed to spawn review ${opts.subRole}`,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n\nexport interface ConvoyLaunchParams {\n issueId: string;\n workspace: string;\n runId: string;\n synthesisAgentId: string;\n /** Recovery provides only lanes with no report and no live session. */\n subRoles?: ReviewSubRole[];\n contextManifestPath?: string;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}\n\n/** Launch all four independent reviewers for one review run. */\nexport async function launchConvoyReviewersPromise(params: ConvoyLaunchParams): Promise<Array<{ success: boolean; message: string }>> {\n const reviewerResults = await Promise.all((params.subRoles ?? REVIEW_SUB_ROLES).map(async (subRole) => {\n const outputPath = reviewerAgentOutputPath(params.workspace, params.runId, subRole);\n\n const result = await Effect.runPromise(spawnReviewSubRoleForIssue({\n issueId: params.issueId,\n workspace: params.workspace,\n subRole,\n runId: params.runId,\n outputPath,\n contextManifestPath: params.contextManifestPath,\n synthesisAgentId: params.synthesisAgentId,\n ...(params.model ? { model: params.model } : {}),\n ...(params.harness ? { harness: params.harness } : {}),\n allowHost: params.allowHost ?? false,\n }));\n if (!result.success) {\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(params.synthesisAgentId, `REVIEWER_FAILED ${subRole} ${result.error ?? result.message}`);\n } catch (signalErr) {\n console.warn(`[review-agent] Failed to signal ${subRole} spawn failure to ${params.synthesisAgentId}:`, signalErr);\n }\n }\n return result;\n }));\n\n const failedReviewers = reviewerResults.filter(r => !r.success);\n if (failedReviewers.length > 0) {\n console.warn(`[review-agent] Convoy launched for ${params.issueId}, but ${failedReviewers.length} reviewer(s) failed to spawn`);\n }\n return reviewerResults;\n}\n\n\n\nexport const buildConvoyPrompt = (opts: {\n issueId: string;\n subRole: string;\n outputPath: string;\n synthesisAgentId: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): Effect.Effect<string> => Effect.promise(() => buildConvoyPromptPromise(opts));\n\n/**\n * Effect variant of {@link spawnReviewSubRoleForIssue}. The Promise version\n * already aggregates errors into the structured result shape, so the Effect\n * form lifts via `Effect.promise`.\n */\nexport const spawnReviewSubRoleForIssue = (opts: {\n issueId: string;\n workspace: string;\n subRole: ReviewSubRole;\n runId: string;\n outputPath?: string;\n contextManifestPath?: string;\n synthesisAgentId?: string;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}): Effect.Effect<{ success: boolean; message: string; error?: string; sessionId?: string }> =>\n Effect.promise(() => spawnReviewSubRoleForIssuePromise(opts));\n\n/**\n * Effect variant of {@link spawnReviewRoleForIssue}. The Promise version\n * returns a structured result instead of throwing, so the Effect form lifts\n * via `Effect.promise`.\n */\n\n\n/**\n * Re-launch only convoy lanes whose report and session are both absent for the\n * current parent run. It is idempotent, so a repeated recovery request is a no-op.\n */\nexport async function recoverMissingConvoyReviewers(\n issueId: string,\n opts: { source?: string; model?: string; harness?: RuntimeName } = {},\n): Promise<{ success: boolean; message: string; launched?: number }> {\n const normalized = issueId.toUpperCase();\n const parentId = `agent-${normalized.toLowerCase()}-review`;\n const { saveAgentState, getAgentStateSync } = await import('../agents.js');\n const parentState = getAgentStateSync(parentId);\n if (!parentState) {\n return { success: false, message: `No review parent state for ${normalized} — cannot recover reviewers` };\n }\n\n let parent: typeof parentState | null;\n try {\n const { resolveReviewParentRunState } = await import('./review-run-recovery.js');\n parent = await resolveReviewParentRunState(parentState, { persistCurrent: true });\n } catch (error) {\n return {\n success: false,\n message: `Could not persist active review run state for ${normalized}: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n if (!parent?.workspace || !parent.reviewRunId) {\n return { success: false, message: `Review parent for ${normalized} is missing workspace/runId state — cannot launch the convoy` };\n }\n const workspace = parent.workspace;\n const runId = parent.reviewRunId;\n\n // Reviewer evidence is per lane: one completed report or live session never\n // proves that a sibling reviewer launched.\n let sessions = new Set<string>();\n let livenessProbeOk = true;\n try {\n sessions = new Set(await Effect.runPromise(listSessionNames()));\n } catch {\n // Liveness probe failed — tmux cannot answer, so the state row's live claim\n // is the only signal left and keeps its conservative vote below.\n livenessProbeOk = false;\n }\n\n const { existsSync } = await import('node:fs');\n const reviewersToLaunch: ReviewSubRole[] = [];\n for (const subRole of REVIEW_SUB_ROLES) {\n const reviewerId = reviewerAgentId(normalized, subRole);\n const reviewer = getAgentStateSync(reviewerId);\n const stateClaimsLive = reviewer?.status === 'running' || reviewer?.status === 'starting';\n if (existsSync(reviewerAgentOutputPath(workspace, runId, subRole))) continue;\n if (sessions.has(reviewerId)) continue;\n // tmux is the liveness oracle (docs/AGENT-STATE-PLANES.md): a probe that\n // answered \"no session\" outranks a state.json row still claiming\n // running/starting. Rows go stale whenever liveness reconciliation cannot\n // run (deacon freeze, boot --no-resume) or a reviewer exits without a\n // stopped event; trusting the claim no-oped the convoy launch and stranded\n // the synthesis parent waiting for reviewers that would never exist\n // (PAN-3545; PAN-3511 cycle 3, 2026-08-04). Heal the stale row here, at the\n // signal — event-driven, no patrol required. Probe failure keeps the row's\n // conservative vote: better a skipped launch than a duplicate reviewer.\n if (stateClaimsLive) {\n if (!livenessProbeOk) continue;\n try {\n const { markAgentStoppedState } = await import('../agents/agent-state.js');\n await Effect.runPromise(saveAgentState(markAgentStoppedState(reviewer!, 'system')));\n } catch { /* best-effort heal — the launch must not fail over bookkeeping */ }\n }\n reviewersToLaunch.push(subRole);\n }\n if (reviewersToLaunch.length === 0) {\n return { success: true, message: `Convoy already launched for ${normalized} run ${runId} — no-op` };\n }\n\n // The per-issue review-model override has to be re-read here: this path is entered from a\n // signal, not from the parent's spawn opts, so nothing carries `model` in. Read the record\n // rather than `parent.model` — that is the parent's *resolved* model, and forwarding it\n // would override each sub-role's own configured model even with no override set.\n const project = resolveProjectForIssue(normalized);\n const issueReviewModel = project ? readIssueRecordSync(project, normalized)?.reviewModel : undefined;\n const reviewModel = opts.model ?? issueReviewModel;\n const results = await launchConvoyReviewersPromise({\n issueId: normalized,\n workspace,\n runId,\n synthesisAgentId: parentId,\n subRoles: reviewersToLaunch,\n contextManifestPath: parent.reviewContextManifestPath,\n ...(reviewModel ? { model: reviewModel } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n allowHost: parent.hostOverride ?? false,\n });\n\n const launched = results.filter(r => r.success).length;\n const message = `Convoy recovery for ${normalized}${opts.source ? ` (${opts.source})` : ''}: launched ${launched}/${reviewersToLaunch.length} missing reviewer(s)`;\n console.log(`[review-agent] ${message}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: normalized });\n return { success: launched === reviewersToLaunch.length, message, launched };\n}\n","/**\n * Review role entry point.\n *\n * PAN-1048 review feedback 007: every legacy convoy helper has been retired.\n * The bash/tmux coordinator path (dispatchParallelReview /\n * spawnReviewCoordinatorSession / runParallelReview) was deleted in R6, and\n * the round-7 cleanup additionally removed the supporting cast that the\n * coordinator used to drive — spawnSingleReviewer, waitForReviewer,\n * archiveReviewerRound, parseReviewSynthesis, parseAgentOutput,\n * selectCompletedReviewers, getReviewAgents, getFilesChangedFromPR,\n * buildReviewFeedbackBody, parseReviewerTemplate, resolvePromptTemplatePath\n * (and the resolveTemplatePath alias), resolveReviewerModel,\n * reviewResultToReviewStatus, reviewerRetryBackoffMs,\n * isRetryableReviewerFailure, the ReviewContext / ReviewResult /\n * ReviewerTemplate / ReviewerRoundArtifact / ReviewerOutcome /\n * ReviewerWaitResult / ReviewerFailureReason / ReviewHistoryEntry types,\n * and DEFAULT_REVIEW_AGENTS / REVIEW_TIMEOUT_MS / MAX_REVIEWER_TIMEOUT_RETRIES\n * / REVIEWER_TIMEOUT_RETRY_BACKOFF_MS / REVIEW_HISTORY_DIR / REVIEW_HISTORY_FILE\n * / SPECIALISTS_DIR constants. None of these had any production caller after R6;\n * their tests went with them.\n *\n * Every active review surface — POST /api/review/:issueId/trigger, the\n * reactive scheduler review branch, and the dashboard kanban \"Review again\"\n * button — flows through\n * spawnReviewRoleForIssue → spawnRun(issueId, 'review'). The review role\n * launches four isolated review sub-role sessions via `pan review spawn-reviewer`,\n * then writes the report and signals the verdict via Overdeck's CLI inside\n * the role itself (see roles/review.md).\n *\n * Surface area kept:\n * - spawnReviewRoleForIssue — the only review entry point\n * - killAllReviewerSessions — kill the canonical reviewer sessions\n * for one issue (merge-agent +\n * dashboard cancel/abort routes)\n * - killAllReviewSessions — kill ALL review sessions on shutdown\n * (pan down)\n */\n\nimport { exec } from 'child_process';\nimport { createHash } from 'node:crypto';\nimport { existsSync } from 'fs';\nimport { mkdir, readFile, rm, writeFile } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { killSession, listSessionNames, isPaneDead } from '../tmux.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { removeAgent } from '../agents/removal.js';\nimport { listAgentIdsByPrefixSync } from '../overdeck/agents.js';\nimport { getAgentStateSync as getAgentStateFileSync } from '../agents/agent-state.js';\nimport { getReviewStatusSync, setReviewStatusSync } from '../review-status.js';\nimport { clearSupersededReviewInfrastructureFailure } from '../review-verdict-guards.js';\nimport { loadConfigSync as loadYamlConfig, type ReviewMode } from '../config-yaml.js';\nimport { buildReviewContext, formatTier1Summary, type ReviewContextManifest } from './review-context.js';\nimport { buildRealConflictGateDeps, getCachedConflictGateMergeability, resolveConflictGate } from './conflict-gate.js';\nimport { createPromiseCoalescer } from './in-flight-guard.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nimport { reviewResumeDecision } from './review-resume-decision.js';\nimport { evaluateReviewConvoyLiveness } from './review-convoy-liveness.js';\nimport { isReviewSessionForIssue } from './specialists-registry.js';\nimport { convergeRowFromVerdictOfRecord } from './verdict-restore.js';\nimport {\n recoverMissingConvoyReviewers,\n launchConvoyReviewersPromise,\n} from './review-convoy.js';\nimport { shouldSkipDispatchAsMerged } from './merge-verification.js';\nimport { readIssueRecordSync, resolveProjectForIssue } from '../pan-dir/record.js';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { AGENTS_DIR, packageRoot, sessionFilePath } from '../paths.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { withReviewLifecycleGuard } from '../review-lifecycle-guard.js';\n\nconst execAsync = promisify(exec);\n// PAN-1531: review-temp stash helpers removed.\n\n// PAN-2584: liveness budget for the review PARENT (discovery + convoy + synthesis).\n// Sub-reviewers get their own 20-minute deadlines in review-convoy.ts; the parent\n// needs headroom for all three phases. Enforced by checkStalledReviewParents.\nexport const PARENT_REVIEW_TIMEOUT_MS = 45 * 60 * 1000;\n// Review now runs against the committed diff only. The dirty-worktree gate\n// at pan done time (and the same gate added to /api/review/:id/request)\n// guarantees the worktree is clean before specialists see the diff.\n\nconst reviewSynthesisPath = (reviewDir: string): string => join(reviewDir, 'synthesis.md');\nconst selfReviewReportPath = (reviewDir: string): string => join(reviewDir, 'review.md');\n\nasync function deriveReviewRunHead8(issueId: string, workspace: string): Promise<string> {\n try {\n const { resolvePrimaryWorkspaceRepoDirSync, resolveWorkspaceRepoRootsSync } = await import('../project-repos.js');\n const roots = resolveWorkspaceRepoRootsSync(issueId, workspace);\n if (roots.some(root => root.isPolyrepo)) {\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n const anchor = await snapshotWorkspaceHeadsPromise(issueId, workspace);\n return anchor\n ? createHash('sha1').update(anchor).digest('hex').substring(0, 8)\n : 'unknown';\n }\n\n // PAN-3037: the primary code repo is resolved through the shared helper —\n // the polyrepo wrapper's HEAD never moves, so a wrapper-derived runId would\n // mismatch every live run and kill a healthy convoy on each dispatch.\n const probeDir = resolvePrimaryWorkspaceRepoDirSync(issueId, workspace);\n const { stdout } = await execAsync(['git', 'rev-parse', '--short=8', 'HEAD'].join(' '), {\n cwd: probeDir,\n encoding: 'utf-8',\n timeout: 10_000,\n });\n return stdout.trim() || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport function buildReviewRolePrompt(opts: {\n issueId: string;\n workspace: string;\n branch: string;\n prUrl?: string;\n runId: string;\n reviewDir: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): string {\n const subRoleFiles = REVIEW_SUB_ROLES.map(r => ` ${join(opts.reviewDir, `${r}.md`)}`).join('\\n');\n const expectedSignals = REVIEW_SUB_ROLES.map(r => ` REVIEWER_READY ${r} <outputPath> or REVIEWER_FAILED ${r} <reason> or REVIEWER_TIMEOUT ${r} <reason>`).join('\\n');\n const synthesisPath = reviewSynthesisPath(opts.reviewDir);\n const runningDesc = 'the four convoy reviewers (security, correctness, performance, requirements)';\n const prompt = [\n `STANDBY — REVIEW SYNTHESIS for ${opts.issueId}`,\n '',\n `Do NOT do anything yet. The Overdeck server has already spawned ${runningDesc}`,\n 'and they are running in parallel right now. Your work begins only once they finish.',\n '',\n `You will receive exactly one \\`pan tell\\` signal per RUNNING sub-role as each`,\n 'reviewer finishes — these are delivered to you as user messages:',\n expectedSignals,\n '',\n `Until all ${REVIEW_SUB_ROLES.length} terminal signal(s) have arrived: do nothing. Do not read the`,\n 'reviewer output files, do not run git, do not inspect tmux sessions, do not',\n 'poll anything. Just wait — the reviewers notify you when they finish, and',\n 'Deacon is the failsafe if one never starts or never completes. Acting early',\n 'wastes tokens reviewing nothing.',\n '',\n 'STALE-SIGNAL GUARD (PAN-3549): terminal signals from a dead prior attempt of',\n 'this same branch can replay into your session when you resume — every signal',\n 'carries its deadline in its text. A signal is current only if its deadline is',\n \"NEWER than this dispatch's context manifest. Before counting any\",\n 'REVIEWER_TIMEOUT or REVIEWER_FAILED signal, compare: run',\n '`stat -c %y <manifest path>` and discard any signal whose deadline is older.',\n 'Stale signals describe lanes that no longer exist; the current lanes are',\n 'still running and their signals arrive later.',\n '',\n `Once you have all ${REVIEW_SUB_ROLES.length} terminal signal(s), follow roles/review.md exactly to`,\n 'read the reports, synthesize the verdict,',\n 'write the synthesis report, and signal the status.',\n '',\n '── Review context ──',\n `Issue: ${opts.issueId}`,\n `Branch: ${opts.branch}`,\n `Workspace: ${opts.workspace}`,\n opts.prUrl ? `PR: ${opts.prUrl}` : `PR: (resolve via: gh pr view ${opts.branch})`,\n `Run ID: ${opts.runId}`,\n `Review directory: ${opts.reviewDir}`,\n `Synthesis output file: ${synthesisPath}`,\n '',\n opts.tier1Summary\n ? [\n 'Shared review context:',\n '─────────────────────────────────────────────────────────────',\n opts.tier1Summary,\n '─────────────────────────────────────────────────────────────',\n '',\n opts.contextManifestPath ? `Full manifest: ${opts.contextManifestPath}` : '',\n ].join('\\n')\n : opts.contextManifestPath\n ? `Context manifest: ${opts.contextManifestPath}`\n : 'Context manifest: (missing — block review per roles/review.md)',\n '',\n 'Convoy reviewer output files (read each one ONLY after its REVIEWER_READY signal):',\n subRoleFiles,\n '',\n 'After writing the synthesis report, signal the verdict with Overdeck CLI:',\n ` pan admin specialists done review ${opts.issueId} --status passed --notes \"<one-line summary>\" --run-id \"${opts.runId}\"`,\n ` pan admin specialists done review ${opts.issueId} --status blocked --notes \"<one-line top blocker>\" --run-id \"${opts.runId}\"`,\n '',\n // PAN-2007: do NOT tell the agent to `exit`. The session is kept alive through\n // the pipeline (KEEP_SPECIALIST_SESSIONS_ALIVE) so it can be reused for the next\n // review cycle without a cold re-spawn. Exiting before the signal command is\n // what stranded reviews at reviewStatus=reviewing.\n 'After running the signal command above, STOP and wait — do not exit, do not run',\n 'any further commands. The session stays open for the next review cycle.',\n '',\n 'Reactive Cloister dispatches the test role after review passes. Never queue tests yourself and never edit code.',\n ].filter(Boolean).join('\\n');\n\n const sizeBytes = Buffer.byteLength(prompt, 'utf-8');\n console.log(`[review-agent] Synthesis prompt for ${opts.issueId}: ${sizeBytes} bytes`);\n return prompt;\n}\n\n// PAN-1981 (quick path to production): the review role agent reviews the diff\n// ITSELF — no convoy, no synthesis. `buildReviewRolePrompt` above (the synthesis\n// \"stand by, wait for the convoy\" prompt) is kept for when we restore the convoy\n// as an opt-in (#1982 fast-follow); for now the review agent gets this self-review\n// prompt instead. We will decide convoy-vs-self-review (and better per-harness\n// message transmission) in the fast-follow.\nfunction buildSelfReviewPrompt(opts: {\n issueId: string;\n workspace: string;\n branch: string;\n prUrl?: string;\n runId: string;\n reviewDir: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): string {\n const reviewReportPath = selfReviewReportPath(opts.reviewDir);\n const prompt = [\n `CODE REVIEW for ${opts.issueId} — you are the sole reviewer; review the change yourself.`,\n '',\n 'Review the diff for this branch yourself, across ALL dimensions in one pass:',\n 'correctness/logic, security, requirements/acceptance-criteria, and performance.',\n 'Do NOT spawn or wait for any sub-reviewers — there is no convoy; you do the',\n 'whole review yourself and emit the verdict.',\n '',\n '── Review context ──',\n `Issue: ${opts.issueId}`,\n `Branch: ${opts.branch}`,\n `Workspace: ${opts.workspace}`,\n opts.prUrl ? `PR: ${opts.prUrl}` : `PR: (resolve via: gh pr view ${opts.branch})`,\n `Run ID: ${opts.runId}`,\n `Review directory: ${opts.reviewDir}`,\n `Review output file: ${reviewReportPath}`,\n '',\n opts.tier1Summary\n ? [\n 'Shared review context (risk-ranked changed files + acceptance criteria):',\n '─────────────────────────────────────────────────────────────',\n opts.tier1Summary,\n '─────────────────────────────────────────────────────────────',\n '',\n opts.contextManifestPath ? `Full manifest: ${opts.contextManifestPath}` : '',\n ].join('\\n')\n : opts.contextManifestPath\n ? `Context manifest: ${opts.contextManifestPath}`\n : 'Context manifest: (missing — inspect the diff directly: git diff origin/main...HEAD)',\n '',\n 'How to review:',\n '1. Read the diff — use the manifest risk ranking, `git diff` the high-risk files,',\n ' and read the surrounding code as needed.',\n '2. Evaluate correctness, security, requirements/AC, and performance. Use the',\n ' severity + verdict vocabulary in roles/review.md.',\n `3. Write your findings to ${reviewReportPath}.`,\n '',\n 'Then signal the verdict with the Overdeck CLI (exactly one):',\n ` pan admin specialists done review ${opts.issueId} --status passed --notes \"<one-line summary>\" --run-id \"${opts.runId}\"`,\n ` pan admin specialists done review ${opts.issueId} --status blocked --notes \"<one-line top blocker>\" --run-id \"${opts.runId}\"`,\n '',\n // PAN-2007: do NOT tell the agent to `exit`. The session is kept alive through\n // the pipeline (KEEP_SPECIALIST_SESSIONS_ALIVE) so it can be reused for the next\n // review cycle without a cold re-spawn. Exiting before the signal command is\n // what stranded reviews at reviewStatus=reviewing.\n 'After running the signal command above, STOP and wait — do not exit, do not run',\n 'any further commands. The session stays open for the next review cycle.',\n '',\n 'Reactive Cloister dispatches the test role after review passes. Never queue tests yourself and never edit code.',\n ].filter(Boolean).join('\\n');\n\n const sizeBytes = Buffer.byteLength(prompt, 'utf-8');\n console.log(`[review-agent] Self-review prompt for ${opts.issueId}: ${sizeBytes} bytes`);\n return prompt;\n}\nasync function spawnReviewRoleForIssuePromise(\n opts: { issueId: string; workspace: string; branch: string; prUrl?: string; model?: string; harness?: RuntimeName; force?: boolean; allowHost?: boolean },\n): Promise<{ success: boolean; message: string; error?: string; gated?: boolean }> {\n const dispatchStartedAtMs = Date.now();\n if (!opts.model) {\n const project = resolveProjectForIssue(opts.issueId);\n const issueModel = project ? readIssueRecordSync(project, opts.issueId)?.reviewModel : undefined;\n if (issueModel) opts = { ...opts, model: issueModel };\n }\n const reviewSessionName = `agent-${opts.issueId.toLowerCase()}-review`;\n\n // PAN-2420: GitHub-authoritative guard. Do not waste time on conflict-gate\n // checks or context builds for a PR that GitHub already reports merged.\n const mergedGuard = await shouldSkipDispatchAsMerged(opts.issueId);\n if (mergedGuard.skip) {\n const message = `[review-agent] Skipping review dispatch for ${opts.issueId} — ${mergedGuard.reason}`;\n console.log(message);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message, issueId: opts.issueId });\n return { success: false, message };\n }\n\n // PAN-1862 (FR-14): review mode 'none' — skip the AI review entirely. This sits at\n // the single review entry point so the trigger route, host auto-dispatch, and every\n // Deacon re-dispatch site all honor it without per-call-site logic. The pre-review\n // verification gate (typecheck/lint/test floor) has already run by the time any\n // caller reaches here — 'none' skips only the AI review, never the quality floor.\n // reviewSpawnedAt is stamped so the durable reviewRequestedAt intent counts as\n // serviced (otherwise needsReviewDispatch would re-fire this skip every read).\n // Setting reviewStatus 'skipped' advances the lifecycle exactly like an approved\n // review (the setReviewStatusSync write path emits review.approved for it).\n if (resolveReviewMode(opts.issueId) === 'none') {\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'skipped',\n reviewNotes: 'Review mode: none — AI review skipped by configuration; verification gate still enforced',\n reviewSpawnedAt: new Date().toISOString(),\n });\n const message = `Review skipped for ${opts.issueId} (mode=none) — advancing to test`;\n console.log(`[review-agent] ${message}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: opts.issueId });\n return { success: true, message };\n }\n\n // Idempotency: if a review role agent for this issue already has an alive\n // tmux pane, treat the current dispatch as a no-op. spawnRun has its own\n // session-exists check but it throws — we want soft \"already running\"\n // semantics so callers can keep their existing success-path messaging.\n //\n // Force mode (human override from dashboard) kills the old session and\n // respawns so the review runs against current HEAD, not stale state.\n if (opts.force) {\n const stopped = await Effect.runPromise(\n killAllReviewerSessions(undefined, opts.issueId).pipe(\n Effect.catch(() => Effect.succeed({\n killed: [],\n failed: [reviewSessionName],\n })),\n ),\n );\n if (stopped.failed.length > 0) {\n return {\n success: false,\n message: `Review replacement aborted — could not stop ${stopped.failed.join(', ')}`,\n error: `Review sessions still live: ${stopped.failed.join(', ')}`,\n };\n }\n }\n\n try {\n const sessions = opts.force ? [] : await Effect.runPromise(listSessionNames());\n if (sessions.includes(reviewSessionName)) {\n const paneDead = await Effect.runPromise(isPaneDead(reviewSessionName));\n\n // A synthesis agent that has finished its verdict does NOT terminate:\n // its role prompt tells it to \"exit\", but it runs `Bash(exit)` which\n // only exits a subshell — the Claude process stays idle-alive with a\n // live pane. So \"pane alive\" does NOT mean \"actively reviewing\", and the\n // old guard would skip re-dispatch forever, jamming the issue at\n // review=reviewing with no convoy actually running (PAN-1131).\n //\n // Disambiguate via the run id: every review run is keyed to a HEAD sha\n // (runId = agent-<issue>-review-<head8>). If the existing synthesis\n // session was started for a different HEAD than the one we are about to\n // review, it is a stale leftover — kill the convoy and respawn. Only a\n // session whose runId matches the *current* HEAD is genuinely the\n // review-in-progress we should defer to.\n let staleRunId = false;\n let currentRunId: string | undefined;\n if (!paneDead && !opts.force) {\n try {\n const head8 = await deriveReviewRunHead8(opts.issueId, opts.workspace);\n if (head8 === 'unknown') throw new Error('workspace HEAD unavailable');\n currentRunId = `agent-${opts.issueId.toLowerCase()}-review-${head8}`;\n const synthReviewRunId = getAgentStateSync(reviewSessionName)?.reviewRunId;\n // A missing identity cannot prove that the live pane covers the\n // current obligation, so legacy/unknown sessions are stale too.\n if (!synthReviewRunId || synthReviewRunId !== currentRunId) {\n staleRunId = true;\n console.log(\n `[review-agent] ${reviewSessionName} is stale — runId ${synthReviewRunId ?? 'missing'} != current ${currentRunId}; killing convoy and respawning`,\n );\n }\n } catch (probeErr) {\n console.warn(\n `[review-agent] Could not probe ${reviewSessionName} runId, falling back to pane-alive idempotency:`,\n probeErr,\n );\n }\n }\n\n // PAN-1131 residual + PAN-2579: a runId-matching live pane is only \"actively\n // reviewing\" while this cycle's verdict is UNRECORDED. Once the verdict is\n // terminal, the session is warm-idle (kept alive by the warm-by-default\n // lifecycle) — a re-dispatch request must NOT be swallowed by the guard, or\n // the issue jams at a stale verdict with a live-but-finished reviewer. Fall\n // through to the respawn path below: it kills the convoy tmux and the spawn\n // machinery resumes the saved session with its context intact (warm reuse).\n let finishedIdle = false;\n if (!paneDead && !opts.force && !staleRunId) {\n try {\n const status = getReviewStatusSync(opts.issueId);\n const terminal = status?.reviewStatus === 'passed'\n || status?.reviewStatus === 'blocked'\n || status?.reviewStatus === 'failed';\n // Warm-reuse ONLY for a genuinely un-serviced newer request (same\n // ISO-string comparison as needsReviewDispatch). A terminal verdict\n // with NO newer request means this call is a stale duplicate dispatch\n // racing the verdict (the PAN-399 shape) — skip below and leave the\n // verdict alone rather than re-entering 'reviewing'.\n const newerRequest = !!status?.reviewRequestedAt\n && (!status.reviewSpawnedAt || Date.parse(status.reviewRequestedAt) > new Date(status.reviewSpawnedAt).getTime());\n // PAN-2584: a lost verdict leaves the status non-terminal while the\n // reviewer already wrote its report for this exact HEAD — that session\n // is finished, not reviewing. Report-on-disk for the current runId is\n // terminal evidence too; without it a newer request deadlocks behind\n // the guard forever.\n let reportWritten = false;\n if (currentRunId) {\n try {\n const reviewDir = join(opts.workspace, PAN_DIRNAME, 'review', currentRunId);\n reportWritten = existsSync(selfReviewReportPath(reviewDir))\n || existsSync(reviewSynthesisPath(reviewDir));\n } catch { /* probe failure — fall back to verdict-only evidence */ }\n }\n const reviewAgents = listAgentIdsByPrefixSync(reviewSessionName)\n .map(id => getAgentStateFileSync(id))\n .filter(state => state !== null && state !== undefined);\n const convoyLiveness = evaluateReviewConvoyLiveness(opts.issueId, status ?? {}, reviewAgents);\n finishedIdle = (terminal || reportWritten || !convoyLiveness.active) && newerRequest;\n if (finishedIdle) {\n console.log(\n `[review-agent] ${reviewSessionName} is finished-idle (verdict ${status?.reviewStatus}, newer request pending) — warm-reusing for the new review cycle`,\n );\n } else if (terminal) {\n console.log(\n `[review-agent] ${reviewSessionName} has a terminal verdict (${status?.reviewStatus}) and no newer request — treating this dispatch as a stale duplicate; leaving the verdict intact`,\n );\n }\n } catch (statusErr) {\n console.warn(`[review-agent] Could not probe review status for finished-idle check on ${opts.issueId}:`, statusErr);\n }\n }\n\n if (!paneDead && !opts.force && !staleRunId && !finishedIdle) {\n const convergence = currentRunId\n ? await convergeRowFromVerdictOfRecord(opts.issueId, {\n runId: currentRunId,\n workspacePath: opts.workspace,\n writer: 'dispatch-converge',\n })\n : { converged: false };\n if (convergence.converged) {\n const message = `Review dispatch converged from the verdict of record: ${opts.issueId}`;\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: opts.issueId });\n return { success: true, message };\n }\n console.log(`[review-agent] Idempotency guard: ${reviewSessionName} already running for ${opts.issueId} — skipping spawn`);\n return { success: false, message: `Review dispatch skipped — already running: ${reviewSessionName}` };\n }\n // Session pane is dead, force mode, stale runId, or finished-idle — kill the\n // convoy tmux and respawn (the spawn path resumes the saved session, so a\n // warm reviewer keeps its context).\n const reason = opts.force ? 'force-killed for re-review'\n : paneDead ? 'pane is dead'\n : staleRunId ? 'stale runId'\n : 'finished-idle (warm reuse for new cycle)';\n console.log(`[review-agent] ${reviewSessionName} ${reason} — respawning convoy`);\n const stopped = await Effect.runPromise(\n killAllReviewerSessions(undefined, opts.issueId).pipe(\n Effect.catch(() => Effect.succeed({\n killed: [],\n failed: [reviewSessionName],\n })),\n ),\n );\n if (stopped.failed.length > 0) {\n return {\n success: false,\n message: `Review replacement aborted — could not stop ${stopped.failed.join(', ')}`,\n error: `Review sessions still live: ${stopped.failed.join(', ')}`,\n };\n }\n }\n } catch (err) {\n console.warn(`[review-agent] Idempotency check failed for ${opts.issueId}, proceeding:`, err);\n }\n\n // Fast synchronous cache check for the gated case. If the probe cache says\n // the branch has conflicts (or we cannot verify mergeability), fail fast\n // without shelling out to git on the awaited request path.\n const cachedMergeability = getCachedConflictGateMergeability(opts.issueId);\n if (cachedMergeability === 'conflicts' || cachedMergeability === 'unknown') {\n const targetBranch = 'main';\n const reason = cachedMergeability === 'conflicts'\n ? `merge conflict with ${targetBranch} must be resolved before review dispatch`\n : `mergeability against ${targetBranch} could not be verified; deferring review conservatively`;\n const message = `Review dispatch deferred: ${reason}`;\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'pending',\n reviewNotes: message,\n });\n return { success: false, gated: true, message };\n }\n\n const gate = await resolveConflictGate(\n opts.issueId,\n opts.workspace,\n 'main',\n buildRealConflictGateDeps(),\n );\n if (gate.gated) {\n const message = `Review dispatch deferred: ${gate.reason ?? 'merge conflict must be resolved first'}`;\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'pending',\n reviewNotes: message,\n });\n return { success: false, gated: true, message };\n }\n\n // Clear feedback from any previous review cycle so the work agent only\n // sees current-cycle feedback when it reads .pan/feedback/.\n try {\n const { archiveFeedbackFiles } = await import('./feedback-writer.js');\n await Effect.runPromise(archiveFeedbackFiles(opts.workspace));\n } catch {\n // Non-fatal: archiving is best-effort\n }\n\n // PAN-1531: review-temp stash machinery removed. Reviewers see only the\n // committed diff because the dirty-worktree gate refuses pan done /\n // pan review request before reaching here. If callers somehow bypass the\n // gate, uncommitted scratch becomes visible in the review — that's the\n // correct fail-loud behavior, not a reason to silently stash.\n const convergence = await convergeRowFromVerdictOfRecord(opts.issueId, {\n runId: getAgentStateSync(reviewSessionName)?.reviewRunId,\n workspacePath: opts.workspace,\n writer: 'dispatch-converge',\n });\n if (convergence.converged) {\n const message = `Review dispatch converged from the verdict of record: ${opts.issueId}`;\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: opts.issueId });\n return { success: true, message };\n }\n\n try {\n const currentStatus = getReviewStatusSync(opts.issueId);\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'reviewing',\n reviewSpawnedAt: new Date().toISOString(),\n ...clearSupersededReviewInfrastructureFailure(currentStatus),\n });\n } catch (err) {\n console.error(`[review-agent] Failed to set reviewing status for ${opts.issueId}:`, err);\n return {\n success: false,\n message: 'Failed to initialize review status',\n error: err instanceof Error ? err.message : String(err),\n };\n }\n\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({ type: 'task_queued', specialist: 'review-agent', issueId: opts.issueId });\n } catch {\n // Non-fatal\n }\n\n try {\n const { spawnRun, saveAgentState, getAgentState, getAgentStateSync, getLatestSessionIdSync, resumeAgent, wipeAgentStateDirs } = await import('../agents.js');\n const workAgentState = await Effect.runPromise(getAgentState(`agent-${opts.issueId.toLowerCase()}`));\n const allowHost = opts.allowHost === true || workAgentState?.hostOverride === true;\n\n // Build the shared context manifest before spawning so all reviewers\n // read one pre-built diff+AC object instead of each running git diff\n // independently (PAN-1059).\n //\n // Include the shared workspace-head digest in runId so re-reviews of the\n // same issue get separate directories. Monorepos retain their short HEAD;\n // polyrepos hash the full composite anchor so any sub-repo move changes it.\n const head8 = await deriveReviewRunHead8(opts.issueId, opts.workspace);\n const runId = head8 !== 'unknown'\n ? `agent-${opts.issueId.toLowerCase()}-review-${head8}`\n : `agent-${opts.issueId.toLowerCase()}-review`;\n const reviewDir = join(opts.workspace, PAN_DIRNAME, 'review', runId);\n let contextManifestPath: string | undefined;\n let tier1Summary: string | undefined;\n try {\n const manifest = await Effect.runPromise(buildReviewContext({\n runId,\n issueId: opts.issueId,\n workspace: opts.workspace,\n branch: opts.branch,\n }));\n contextManifestPath = manifest.manifestPath;\n tier1Summary = formatTier1Summary(manifest);\n console.log(`[review-agent] Context manifest built: ${contextManifestPath} (${manifest.changedFiles.length} files)`);\n } catch (ctxErr) {\n console.warn(`[review-agent] Context manifest build failed for ${opts.issueId} — reviewers will block on missing shared context:`, ctxErr);\n }\n\n const fullReview = isExtendedReviewEnabled(opts.issueId);\n\n const prompt = fullReview\n ? buildReviewRolePrompt({ ...opts, runId, reviewDir, contextManifestPath, tier1Summary })\n : buildSelfReviewPrompt({ ...opts, runId, reviewDir, contextManifestPath, tier1Summary });\n\n const spawnConvoyReviewers = (synthesisAgentId: string) =>\n launchConvoyReviewersPromise({\n issueId: opts.issueId,\n workspace: opts.workspace,\n runId,\n synthesisAgentId,\n contextManifestPath,\n ...(opts.model ? { model: opts.model } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n allowHost,\n });\n\n // PAN-1862: RESUME the saved review session by default. The review agent keeps the prior\n // review's context (the files it read, the findings it raised), so a re-review checks the\n // fix instead of re-researching the entire diff from scratch — the token-cost problem this\n // was set out to fix. Fresh-spawn ONLY when the harness/model actually changed (it's a\n // different agent then) or there is no resumable saved session. The resume delivery is\n // resilient (supervisor → tmux fallback, PAN-1988).\n const reviewAgentId = `agent-${opts.issueId.toLowerCase()}-review`;\n const savedReview = getAgentStateSync(reviewAgentId);\n const canResumeReview = reviewResumeDecision({\n requestedModel: opts.model,\n requestedHarness: opts.harness,\n savedModel: savedReview?.model,\n savedHarness: savedReview?.harness,\n hasSavedState: !!savedReview,\n hasSavedSession: !!getLatestSessionIdSync(reviewAgentId),\n });\n if (canResumeReview) {\n console.log(`[review-agent] Resuming saved review session for ${opts.issueId} — model/harness unchanged, preserving context (PAN-1862)`);\n const resumeResult = await resumeAgent(reviewAgentId, prompt);\n if (resumeResult.success) {\n try {\n // Keep the idempotency guard's HEAD-staleness detection honest for the resumed run.\n const resumed = getAgentStateSync(reviewAgentId);\n if (resumed) {\n resumed.reviewRunId = runId;\n // PAN-2584: arm the parent's liveness deadline for this cycle.\n resumed.reviewDeadlineAt = new Date(Date.now() + PARENT_REVIEW_TIMEOUT_MS).toISOString();\n await Effect.runPromise(saveAgentState(resumed));\n }\n } catch { /* non-fatal */ }\n if (fullReview) {\n if (savedReview?.reviewRunId === runId) {\n // PAN-3368: this is recovery of the current run, not a new review cycle.\n // Re-dispatch only lanes with neither a live session nor a report; completed\n // siblings stay intact and the synthesis parent can proceed immediately.\n const recovery = await recoverMissingConvoyReviewers(opts.issueId, {\n source: 'same-run parent resume',\n ...(opts.model ? { model: opts.model } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n });\n if (!recovery.success) {\n return {\n success: false,\n message: `Convoy review resumed, but missing reviewer recovery failed: ${recovery.message}`,\n error: recovery.message,\n };\n }\n } else {\n await spawnConvoyReviewers(reviewAgentId);\n }\n return { success: true, message: `Convoy review resumed (session preserved): ${reviewAgentId}` };\n }\n return { success: true, message: `Review resumed (session preserved): ${reviewAgentId}` };\n }\n console.warn(`[review-agent] Review resume failed for ${reviewAgentId}; falling back to a fresh session: ${resumeResult.error}`);\n }\n // Fresh review: wipe any stale review state (harness/model changed, or the resume above\n // failed) so the new session does not inherit a mismatched saved session id.\n if (savedReview || getLatestSessionIdSync(reviewAgentId)) {\n try { await wipeAgentStateDirs(opts.issueId, { rolePrefix: 'review' }); }\n catch (wipeErr) { console.warn(`[review-agent] review state wipe before fresh spawn failed (non-fatal): ${wipeErr instanceof Error ? wipeErr.message : String(wipeErr)}`); }\n }\n const run = await spawnRun(opts.issueId, 'review', {\n workspace: opts.workspace,\n prompt,\n ...(opts.model ? { model: opts.model } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n ...(allowHost ? { allowHost: true } : {}),\n startedBy: 'review-agent',\n });\n // Persist the runId on the synthesis agent's own state so the idempotency\n // guard above can tell a genuinely-running review (runId matches current\n // HEAD) from a finished-but-idle leftover (runId from an older HEAD) — see\n // PAN-1131. Sub-reviewers already persist this; the synthesis agent did not.\n run.reviewRunId = runId;\n // PAN-2584: arm the parent's liveness deadline for this cycle.\n run.reviewDeadlineAt = new Date(Date.now() + PARENT_REVIEW_TIMEOUT_MS).toISOString();\n try {\n await Effect.runPromise(saveAgentState(run));\n } catch (saveErr) {\n console.warn(`[review-agent] Could not persist reviewRunId on ${run.id}:`, saveErr);\n }\n if (fullReview) {\n await spawnConvoyReviewers(run.id);\n console.log(`[review-agent] Review role (convoy synthesis) spawned for ${opts.issueId}: ${run.id}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message: `Convoy review spawned for ${opts.issueId}: ${run.id}`, issueId: opts.issueId });\n return {\n success: true,\n message: `Convoy review spawned: ${run.id}`,\n };\n }\n\n console.log(`[review-agent] Review role (self-review) spawned for ${opts.issueId}: ${run.id}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message: `Self-review spawned for ${opts.issueId}: ${run.id}`, issueId: opts.issueId });\n\n return {\n success: true,\n message: `Self-review spawned: ${run.id}`,\n };\n } catch (err) {\n console.error(`[review-agent] Failed to spawn review role for ${opts.issueId}:`, err);\n // PAN-3674: tear down the half-started runtime this dispatch created so a\n // later resume does not attach to a wedge — on 2026-08-13 the PAN-3668\n // orchestrator's app-server host sat alive-but-socketless in its pane for\n // 5h after a readiness timeout, invisible to every liveness reader (its\n // state row still said 'starting'). Only tear down what THIS dispatch\n // created: an older startedAt means a pre-existing session that must not\n // be killed here.\n try {\n const orphan = getAgentStateSync(reviewSessionName);\n const orphanStartedMs = orphan?.startedAt ? Date.parse(orphan.startedAt) : Number.NaN;\n const createdByThisDispatch = orphan !== null && orphan !== undefined\n && Number.isFinite(orphanStartedMs) && orphanStartedMs >= dispatchStartedAtMs - 5_000;\n if (createdByThisDispatch) {\n const sessions = await Effect.runPromise(listSessionNames()).catch(() => [] as string[]);\n if (sessions.includes(reviewSessionName)) {\n await Effect.runPromise(killSession(reviewSessionName)).catch(() => {});\n }\n orphan.status = 'error';\n const { saveAgentState } = await import('../agents.js');\n await Effect.runPromise(saveAgentState(orphan)).catch(() => {});\n }\n } catch { /* teardown is best-effort; the status write below still lands */ }\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'failed',\n reviewNotes: `Review role spawn failed: ${err instanceof Error ? err.message : String(err)}`,\n });\n return {\n success: false,\n message: 'Failed to spawn review role',\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Kill all canonical reviewer sessions for one issue.\n *\n * PAN-915: this is no longer called per-round. Canonical reviewer sessions\n * persist across review rounds via PAN-830's `remain-on-exit on` so each\n * round resumes the same Claude process via `sendKeysAsync` — preserving\n * the reviewer's accumulated context (codebase patterns, prior findings,\n * decisions made during earlier rounds). This function is now invoked from\n * terminal lifecycle events: merge complete, reset, cancel, deep-wipe, and\n * explicit `pan review abort`.\n *\n * Matches the parent review role, convoy children, and legacy coordinator\n * sessions so callers do not need to know which review phase has started.\n */\nexport { isReviewSessionForIssue };\n\nasync function killAllReviewerSessionsPromise(\n projectKey: string | undefined,\n issueId: string,\n): Promise<{ killed: string[]; failed: string[] }> {\n const killed: string[] = [];\n const failed: string[] = [];\n let allSessions: readonly string[];\n\n try {\n allSessions = await Effect.runPromise(listSessionNames());\n } catch (err) {\n console.warn('[review-agent] Failed to list tmux sessions during reviewer cleanup:', err instanceof Error ? err.message : String(err));\n return {\n killed,\n failed: [`agent-${issueId.toLowerCase()}-review`],\n };\n }\n\n const sessionsToKill = allSessions.filter(s => isReviewSessionForIssue(s, projectKey, issueId));\n await Promise.all(\n sessionsToKill.map(async (sessionName) => {\n try {\n await Effect.runPromise(killSession(sessionName));\n console.log(`[review-agent] Killed reviewer session ${sessionName}`);\n killed.push(sessionName);\n } catch (err) {\n console.log(`[review-agent] Session ${sessionName} already gone or failed to kill: ${err instanceof Error ? err.message : String(err)}`);\n failed.push(sessionName);\n }\n }),\n );\n return { killed, failed };\n}async function killAllReviewSessionsPromise(): Promise<{ killed: string[]; failed: string[] }> {\n const killed: string[] = [];\n const failed: string[] = [];\n\n let allSessions: readonly string[];\n try {\n allSessions = await Effect.runPromise(listSessionNames());\n } catch (err) {\n console.warn('[review-agent] Failed to list tmux sessions during review cleanup:', err instanceof Error ? err.message : String(err));\n return { killed, failed };\n }\n\n const reviewPatterns = [\n /^agent-[a-z0-9-]+-review(?:-(?:security|correctness|performance|requirements))?$/i,\n /^review-coordinator-/,\n /^specialist-.+-review-/,\n /^review-[A-Z0-9]+-\\d+-\\d+/, // legacy: review-PAN-999-1713456789000-correctness\n ];\n\n const sessionsToKill = allSessions.filter(s => reviewPatterns.some(p => p.test(s)));\n if (sessionsToKill.length === 0) {\n return { killed, failed };\n }\n\n console.log(`[review-agent] Killing ${sessionsToKill.length} review session(s) during shutdown`);\n\n await Promise.all(\n sessionsToKill.map(async (sessionName) => {\n try {\n await Effect.runPromise(killSession(sessionName));\n console.log(`[review-agent] Killed review session ${sessionName}`);\n killed.push(sessionName);\n } catch (err) {\n console.log(`[review-agent] Session ${sessionName} already gone or failed to kill: ${err instanceof Error ? err.message : String(err)}`);\n failed.push(sessionName);\n }\n }),\n );\n\n return { killed, failed };\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link buildConvoyPrompt}. Template reads are the only\n * fallible step; any failure here is fatal and propagates via Effect's defect\n * channel through `Effect.promise`.\n */\n// PAN-2695: dispatch has multiple legitimate callers (request route, deacon\n// reconcile, dispatch reconcile) that can fire near-simultaneously. An\n// uncoalesced second invocation sees the first's milliseconds-old agent state,\n// takes the PAN-1862 resume path against a parent that is still booting, and\n// kills it with the synthesis kickoff undelivered. Coalesce per issue: a\n// concurrent caller awaits the in-flight dispatch's result instead of re-entering.\nconst reviewDispatchCoalescer = createPromiseCoalescer<{ success: boolean; message: string; error?: string; gated?: boolean }>();\n\nexport const spawnReviewRoleForIssue = (\n opts: { issueId: string; workspace: string; branch: string; prUrl?: string; model?: string; harness?: RuntimeName; force?: boolean; allowHost?: boolean },\n): Effect.Effect<{ success: boolean; message: string; error?: string; gated?: boolean }> =>\n Effect.promise(() => {\n const key = opts.issueId.toUpperCase();\n if (reviewDispatchCoalescer.isInFlight(key)) {\n console.log(`[review-agent] Review dispatch already in flight for ${key} — coalescing concurrent dispatch (PAN-2695)`);\n }\n return reviewDispatchCoalescer.run(\n key,\n () => withReviewLifecycleGuard(key, () => spawnReviewRoleForIssuePromise(opts)),\n );\n });\n\n/**\n * Effect variant of {@link killAllReviewerSessions}. Session-kill failures are\n * already aggregated into the `failed` array — this wrapper preserves that\n * contract.\n */\nexport const killAllReviewerSessions = (\n projectKey: string | undefined,\n issueId: string,\n): Effect.Effect<{ killed: string[]; failed: string[] }> =>\n Effect.promise(() => killAllReviewerSessionsPromise(projectKey, issueId));\n\n/**\n * Effect variant of {@link killAllReviewSessions}. Same aggregation semantics\n * as the Promise version.\n */\nexport const killAllReviewSessions = (): Effect.Effect<{ killed: string[]; failed: string[] }> =>\n Effect.promise(() => killAllReviewSessionsPromise());\n\n// PAN-1862 resume-vs-fresh decision lives in its own pure module (review-resume-decision.ts) so\n// it is unit-testable without importing this heavy file. Re-exported for external callers.\nexport { reviewResumeDecision } from './review-resume-decision.js';\n// PAN-1862: convoy machinery lives in review-convoy.ts; re-exported here so the\n// deacon modules, tests, and the CLI keep one stable import surface.\nexport {\n buildConvoyPrompt,\n spawnReviewSubRoleForIssue,\n recoverMissingConvoyReviewers,\n} from './review-convoy.js';\n\n/**\n * Is the issue carrying leftover EXTENDED-review (convoy) sub-reviewer agents from a\n * prior cycle? PAN-2697: full-convoy review runs today, so \"any sub-reviewer exists\"\n * (the old quick-mode assumption) false-flagged every legitimate convoy — and the\n * always-on review supervisor matched the prefix too. A sub-reviewer is stale only\n * when its reviewRunId differs from the parent's active run.\n */\nexport function isReviewStaleSync(issueId: string): boolean {\n const issueLower = issueId.toLowerCase();\n const prefix = `agent-${issueLower}-review-`;\n const parentRunId = getAgentStateFileSync(`agent-${issueLower}-review`)?.reviewRunId;\n return listAgentIdsByPrefixSync(prefix).some((id) => {\n const subRole = id.slice(prefix.length);\n if (!(REVIEW_SUB_ROLES as readonly string[]).includes(subRole)) return false;\n return !parentRunId || getAgentStateFileSync(id)?.reviewRunId !== parentRunId;\n });\n}\n\nexport function resolveReviewMode(issueId?: string): ReviewMode {\n if (issueId) {\n const project = resolveProjectForIssue(issueId);\n const issueMode = project ? readIssueRecordSync(project, issueId)?.reviewMode : undefined;\n if (issueMode === 'quick' || issueMode === 'full' || issueMode === 'none') {\n return issueMode;\n }\n }\n\n const configMode = loadYamlConfig().config.roles?.review?.mode;\n return configMode === 'full' || configMode === 'none' ? configMode : 'quick';\n}\n\n/**\n * Is EXTENDED (convoy) review enabled for this issue?\n *\n * `resolveReviewMode` is the single source of truth: per-issue record override\n * beats merged project/global config, and quick remains the default.\n */\nexport function isExtendedReviewEnabled(issueId?: string): boolean {\n return resolveReviewMode(issueId) === 'full';\n}\n\n/**\n * Tear down an issue's entire review fleet — the `agent-<id>-review` parent plus any\n * extended-mode sub-reviewers. Kills every review tmux session, then removes each agent\n * through the canonical transcript-preserving removal path. Does NOT reset review_status —\n * the caller composes that (see the\n * POST /api/review/:id/purge route). Returns what was killed and removed.\n */\nexport async function purgeReviewAgentsForIssue(\n projectKey: string | undefined,\n issueId: string,\n): Promise<{ killed: string[]; removed: string[] }> {\n const killResult = await killAllReviewerSessionsPromise(projectKey, issueId);\n const removed: string[] = [];\n for (const agentId of listAgentIdsByPrefixSync(`agent-${issueId.toLowerCase()}-review`)) {\n await removeAgent(agentId);\n removed.push(agentId);\n }\n return { killed: killResult.killed, removed };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAA,qBAAA;;;;;;;;;;AAwBA,MAAA,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAA,YAAA,MAAA;;;AAGE,QAAA,IAAA,KAAA,QAAA;;AAEE,MAAA,MAAA,OAAA,KAAA,IAAA,OAAA,KAAA;AACE,OAAA,KAAA,IAAA,OAAA,KAAA;AACE,eAAA;AACA,SAAA;AACA;;AAEF,cAAA;AACA,QAAA;AACA;;AAEF,MAAA,MAAA,KAAA;AACE,cAAA;AACA,QAAA;AACA;;AAEF,MAAA,mBAAA,KAAA,EAAA,EAAA;AACE,cAAA,KAAA;AACA,QAAA;AACA;;AAEF,aAAA;AACA,OAAA;;AAEF,QAAA,IAAA,OAAA,IAAA,QAAA,GAAA;;;;;;AAOF,SAAA,sBAAA,UAAA;AACE,MAAA,MAAA,QAAA,mBAAA,KAAA,YAAA,KAAA,CAAA,KAAA,SAAA,CAAA,QAAA;AAGA,QAAA;;;;;;;AAQF,SAAA,oBAAA,UAAA,QAAA;AACE,KAAA,WAAA,IAAA,QAAA,sBAAA,SAAA;;AAKA,MAAA,MAAA,QAAA,gBAAA,KAAA,YAAA,KAAA,CAAA,KAAA,SAAA,CAAA,QAAA;AAGA,QAAA;;;;;;;;;;;;ACjKF,MAAMA,cAAYC,YAAUC,OAAK;AAOjC,eAAe,iBAAiB,WAAmB,UAA+C;CAChG,MAAM,EAAE,WAAW,MAAMF,YACvB,2BAA2B,SAAS,WACpC;EAAE,KAAK;EAAW,UAAU;EAAS,WAAW,IAAI,OAAO;EAAM,CAClE;CAED,MAAM,UAA8B,EAAE;AACtC,MAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,EAAE;AACrC,MAAI,CAAC,KAAK,MAAM,CAAE;EAClB,MAAM,QAAQ,KAAK,MAAM,IAAK;EAC9B,MAAM,SAAS,MAAM,KAAK,MAAM;EAChC,MAAM,OAAO,MAAM,MAAM,SAAS,MAAM;AACxC,MAAI,CAAC,KAAM;AACX,UAAQ,KAAK;GAAE;GAAM;GAAQ,CAAC;;AAEhC,QAAO;;;;;;AAOT,SAAS,gBAAgB,MAA6B;CACpD,MAAM,QAAQ,KAAK,MAAM,6CAA6C;AACtE,KAAI,CAAC,MAAO,QAAO;AACnB,QAAO,SAAS,MAAM,IAAI,GAAG;;;;;;AAO/B,gBAAgB,kBACd,WACA,UACA,UACyD;CACzD,MAAM,EAAE,WAAW,MAAMA,YACvB,iBAAiB,SAAS,eAAe,SAAS,IAClD;EAAE,KAAK;EAAW,UAAU;EAAS,WAAW,IAAI,OAAO;EAAM,CAClE;CAED,IAAI,cAAc;AAElB,MAAK,MAAM,WAAW,OAAO,MAAM,KAAK,EAAE;AACxC,MAAI,QAAQ,WAAW,KAAK,EAAE;GAC5B,MAAM,SAAS,gBAAgB,QAAQ;AACvC,OAAI,WAAW,KACb,eAAc;AAEhB;;AAGF,MAAI,QAAQ,WAAW,MAAM,CAAE;AAC/B,MAAI,QAAQ,WAAW,MAAM,CAAE;AAE/B,MAAI,QAAQ,WAAW,IAAI,EAAE;GAC3B,MAAM,UAAU,QAAQ,MAAM,EAAE;AAChC,SAAM;IAAE,YAAY;IAAa;IAAS;AAC1C,kBAAe;AACf;;AAGF,MAAI,QAAQ,WAAW,IAAI,EAAE;AAC3B,kBAAe;AACf;;;;;;;;;;AAaN,eAAsB,WAAW,WAAmB,UAA4C;AAC9F,KAAI;EACF,MAAM,eAAe,MAAM,iBAAiB,WAAW,SAAS;EAChE,MAAM,WAA4B,EAAE;AAEpC,OAAK,MAAM,EAAE,MAAM,YAAY,cAAc;AAC3C,OAAI,CAAC,oBAAoB,MAAM,OAAO,CAAE;AAExC,cAAW,MAAM,EAAE,YAAY,aAAa,kBAAkB,WAAW,UAAU,KAAK,CACtF,MAAK,MAAM,WAAW,iBACpB,KAAI,QAAQ,MAAM,KAAK,QAAQ,CAC7B,UAAS,KAAK;IACZ,WAAW,QAAQ;IACnB,cAAc,QAAQ;IACtB,UAAU;IACV;IACA,WAAW,QAAQ,SAAS;IAC5B,UAAU,QAAQ;IACnB,CAAC;;AAMV,SAAO;UACA,KAAK;AACZ,UAAQ,KACN,yCAAyC,UAAU,WAAW,SAAS,IACrE,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAEnD;AACD,SAAO,EAAE;;;;;;;;;;;;;ACzHb,MAAMG,cAAY,UAAU,KAAK;AAEjC,MAAM,uBAAuB;AAqD7B,eAAe,iBAAiB,WAAoE;AAClG,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YAAU,kCAAkC;GACnE,KAAK;GACL,UAAU;GACV,SAAS;GACV,CAAC;EACF,MAAM,SAAS,KAAK,MAAM,OAAO;EACjC,MAAM,QAAQ,OAAO,OAAO;EAC5B,MAAM,OAAO,OAAO;AACpB,MAAI,CAAC,SAAS,CAAC,KAAM,QAAO;AAC5B,SAAO;GAAE;GAAO;GAAM;SAChB;AACN,SAAO;;;AAIX,eAAe,yBACb,OACA,MACA,UAC8B;CAC9B,MAAM,EAAE,WAAW,MAAMA,YACvB,iBAAiB,MAAM,GAAG,KAAK,SAAS,SAAS,yBACjD;EAAE,UAAU;EAAS,SAAS;EAAO,WAAW,IAAI,OAAO;EAAM,CAClE;AAOD,QANY,KAAK,MAAM,OAMb,CACP,QAAO,MAAK,EAAE,MAAM,UAAU,wBAAwB,EAAE,UAAU,YAAY,CAC9E,KAAI,OAAM;EACT,MAAM,EAAE,QAAQ;EAChB,KAAK,EAAE;EACR,EAAE;;AAGP,eAAe,0BACb,OACA,MACA,UAC8B;CAC9B,MAAM,EAAE,WAAW,MAAMA,YACvB,iBAAiB,MAAM,GAAG,KAAK,SAAS,SAAS,0BACjD;EAAE,UAAU;EAAS,SAAS;EAAO,WAAW,IAAI,OAAO;EAAM,CAClE;AAQD,QAPY,KAAK,MAAM,OAOb,CACP,QAAO,MAAK,EAAE,MAAM,UAAU,qBAAqB,CACnD,KAAI,OAAM;EACT,MAAM,EAAE;EACR,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO,KAAA;EAC5C,MAAM,EAAE,QAAQ;EAChB,KAAK,EAAE;EACR,EAAE;;AAGP,eAAsB,wBAAwB,MAGb;AAC/B,KAAI;EACF,MAAM,YAAY,MAAM,iBAAiB,KAAK,UAAU;AACxD,MAAI,CAAC,UAAW,QAAO,EAAE;EACzB,MAAM,EAAE,OAAO,SAAS;EAExB,MAAM,WAAW,MAAM,iCAAiC,OAAO,MAAM,KAAK,OAAO;AACjF,MAAI,CAAC,SAAU,QAAO,EAAE;EAExB,MAAM,CAAC,gBAAgB,mBAAmB,MAAM,QAAQ,IAAI,CAC1D,yBAAyB,OAAO,MAAM,SAAS,CAAC,YAAY,EAAE,CAAwB,EACtF,0BAA0B,OAAO,MAAM,SAAS,CAAC,YAAY,EAAE,CAAwB,CACxF,CAAC;AAEF,SAAO,CAAC,GAAG,gBAAgB,GAAG,gBAAgB;UACvC,KAAK;AACZ,UAAQ,KACN,mDAAmD,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GACpG;AACD,SAAO,EAAE;;;;;;;;;;;;;mBC1IqD;oBAGE;AAEpE,MAAMC,cAAYC,YAAUC,OAAK;AAUjC,MAAM,YAAY;AAClB,MAAM,WAAY;AAClB,MAAM,WAAY;AAGlB,MAAM,qBAAqB;CACzB;CAAS;CAAa;CAAU;CAAW;CAC3C;CAAe;CAAc;CAAU;CAAQ;CAC/C;CAAY;CAAY;CACxB;CAAQ;CAAU;CAClB;CAAW;CAAU;CAAU;CAChC;AAED,MAAM,oBAAoB;CACxB;CAAW;CAAQ;CAAY;CAC/B;CAAe;CAAU;CAC1B;AAED,MAAM,oBAAoB;CACxB;CAAS;CAAS;CAAS;CAAY;CACvC;CAAS;CAAU;CAAW;CAC/B;AAqDD,SAAS,UAAU,UAA0B;AAC3C,KAAI,kBAAkB,MAAK,MAAK,EAAE,KAAK,SAAS,CAAC,CAAE,QAAO;AAC1D,KAAI,mBAAmB,MAAK,MAAK,EAAE,KAAK,SAAS,CAAC,CAAE,QAAO;AAC3D,KAAI,kBAAkB,MAAK,MAAK,EAAE,KAAK,SAAS,CAAC,CAAE,QAAO;AAC1D,QAAO;;AAGT,eAAe,WAAW,KAA8B;AACtD,KAAI;EACF,MAAM,EAAE,WAAW,MAAMF,YAAU,sBAAsB;GAAE;GAAK,UAAU;GAAS,CAAC;AACpF,SAAO,OAAO,MAAM;SACd;AACN,SAAO;;;AAIX,eAAe,iBAAiB,KAA8B;AAC5D,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YAAU,6BAA6B;GAAE;GAAK,UAAU;GAAS,CAAC;AAC3F,SAAO,OAAO,MAAM,IAAI;SAClB;AACN,SAAO;;;AAIX,eAAsB,YAAY,KAAa,eAAe,QAAyB;AACrF,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YAAU,yBAAyB,aAAa,QAAQ;GAAE;GAAK,UAAU;GAAS,CAAC;AAC5G,SAAO,OAAO,MAAM;SACd;AACN,MAAI;GACF,MAAM,EAAE,WAAW,MAAMA,YAAU,kBAAkB,aAAa,QAAQ;IAAE;IAAK,UAAU;IAAS,CAAC;AACrG,UAAO,OAAO,MAAM;UACd;AACN,UAAO;;;;AAKb,eAAsB,gBAAgB,KAAa,MAAc,aAAa,IAA4B;CAExG,IAAI,aAAa;AACjB,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YACvB,2BAA2B,KAAK,WAChC;GAAE;GAAK,UAAU;GAAS,WAAW,IAAI,OAAO;GAAM,CACvD;AACD,eAAa;SACP;AACN,SAAO,EAAE;;CAIX,MAAM,6BAAa,IAAI,KAAuD;AAC9E,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YACvB,uBAAuB,KAAK,WAC5B;GAAE;GAAK,UAAU;GAAS,WAAW,IAAI,OAAO;GAAM,CACvD;AACD,OAAK,MAAM,QAAQ,OAAO,MAAM,KAAK,EAAE;GACrC,MAAM,QAAQ,KAAK,MAAM,IAAK;AAC9B,OAAI,MAAM,UAAU,GAAG;IACrB,MAAM,YAAY,SAAS,MAAM,IAAI,GAAG,IAAI;IAC5C,MAAM,YAAY,SAAS,MAAM,IAAI,GAAG,IAAI;AAE5C,eAAW,IAAI,MAAM,IAAI;KAAE;KAAW;KAAW,CAAC;;;SAGhD;CAIR,MAAM,QAAuB,EAAE;AAC/B,MAAK,MAAM,QAAQ,WAAW,MAAM,KAAK,EAAE;AACzC,MAAI,CAAC,KAAK,MAAM,CAAE;EAClB,MAAM,CAAC,YAAY,GAAG,aAAa,KAAK,MAAM,IAAK;EACnD,MAAM,OAAO,UAAU,UAAU,SAAS,MAAM;AAChD,MAAI,CAAC,KAAM;EAEX,MAAM,eAAgB,aAAa,MAAM;EACzC,MAAM,SAAS,WAAW,IAAI,KAAK,IAAI;GAAE,WAAW;GAAG,WAAW;GAAG;EACrE,MAAM,eAAe,GAAG,aAAa;AAErC,QAAM,KAAK;GACT,MAAM;GACN,QAAQ;GACR,WAAW,OAAO;GAClB,WAAW,OAAO;GAClB,WAAW,UAAU,aAAa;GACnC,CAAC;;AAIJ,QAAO,MAAM,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU;;AAGxD,eAAsB,YAAY,KAAa,MAA6D;CAC1G,IAAI,OAAO;AAEX,KAAI;EACF,MAAM,EAAE,WAAW,MAAMA,YACvB,oBAAoB,KAAK,WACzB;GAAE;GAAK,UAAU;GAAS,WAAW,IAAI,OAAO;GAAM,CACvD;AACD,SAAO,OAAO,MAAM,IAAI;SAClB;AACN,SAAO;;AAKT,QAAO;EAAE;EAAM,WAAW;EAAM;;AASlC,eAAe,8BAA8B,WAAmB,SAAkD;CAEhH,MAAM,WAAW,aAAa,UAAU;AACxC,KAAI,SACF,KAAI;EACF,MAAM,MAAM,MAAM,OAAO,WAAW,SAAS,SAAS,CAAC;AACvD,SAAO;GACL,oBAAoB,UAAU,IAAI;GAClC,UAAU,gBAAgB,IAAI;GAC9B,QAAQ,cAAc,IAAI;GAC3B;SACK;AAMV,KAAI;EACF,MAAM,cAAc,oBAAoB;AACxC,MAAI,CAAC,YAAa,QAAO;GAAE,oBAAoB,EAAE;GAAE,UAAU,EAAE;GAAE,QAAQ,EAAE;GAAE;EAC7E,MAAM,QAAQ,sBAAsB,aAAa,QAAQ;AACzD,MAAI,MACF,QAAO;GACL,oBAAoB,UAAU,MAAM,SAAS;GAC7C,UAAU,gBAAgB,MAAM,SAAS;GACzC,QAAQ,cAAc,MAAM,SAAS;GACtC;SAEG;AAIR,QAAO;EAAE,oBAAoB,EAAE;EAAE,UAAU,EAAE;EAAE,QAAQ,EAAE;EAAE;;AAW7D,SAAS,UAAU,KAAiD;CAClE,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,QAAQ,KAAK,MAAM,SAAS,EAAE,EAAE;AAEzC,MAAI,MAAM,QAAQ,KAAK,mBAAmB,CACxC,KAAI,KAAK,GAAG,KAAK,mBAAmB;AAGtC,OAAK,MAAM,OAAO,KAAK,YAAY,EAAE,EAAE;GACrC,MAAM,OAAO,IAAI,SAAS,IAAI,eAAe;AAC7C,OAAI,KAAM,KAAI,KAAK,KAAK;;;AAG5B,QAAO;;AAGT,SAAS,gBAAgB,KAA+E;CACtG,MAAM,MAAM,IAAI,MAAM,YAAY,UAAU,MAAM;AAClD,KAAI,CAAC,OAAO,IAAI,aAAa,KAAK,OAAQ,QAAO,EAAE;AACnD,QAAO,IACJ,MAAM,KAAK,CACX,KAAI,SAAQ,KAAK,MAAM,CAAC,QAAQ,YAAY,GAAG,CAAC,MAAM,CAAC,CACvD,OAAO,QAAQ;;AAGpB,SAAS,cAAc,KAA0D;CAC/E,MAAM,SAA4B,EAAE;AACpC,MAAK,MAAM,QAAQ,IAAI,MAAM,SAAS,EAAE,EAAE;EACxC,MAAM,MAAM,KAAK,UAAU;AAC3B,MAAI,CAAC,MAAM,QAAQ,IAAI,CAAE;EACzB,MAAM,aAAa,IAAI,QAAQ,UAA2B,OAAO,UAAU,YAAY,MAAM,MAAM,CAAC,SAAS,EAAE;AAC/G,MAAI,WAAW,WAAW,EAAG;AAC7B,SAAO,KAAK;GACV,QAAQ,KAAK,MAAM;GACnB,OAAO,KAAK,SAAS;GACrB,QAAQ;GACT,CAAC;;AAEJ,QAAO;;AAGT,eAAe,gBAAgB,WAAsC;CACnE,MAAM,QAAkB,EAAE;CAG1B,MAAM,eAAeG,OAAK,WAAW,YAAY;AACjD,KAAI,WAAW,aAAa,CAC1B,KAAI;EAEF,MAAM,iBAAgB,MADA,SAAS,cAAc,QAAQ,EAElD,MAAM,KAAK,CACX,QAAO,MAAK,EAAE,WAAW,cAAc,IAAI,EAAE,WAAW,UAAU,IAAI,EAAE,WAAW,aAAa,CAAC,CACjG,KAAI,MAAK,EAAE,QAAQ,UAAU,GAAG,CAAC,QAAQ,QAAQ,GAAG,CAAC,MAAM,CAAC,CAC5D,OAAO,QAAQ;AAClB,QAAM,KAAK,GAAG,cAAc,MAAM,GAAG,GAAG,CAAC;SACnC;AAKV,QAAO;;;;;;;;;;;;;;;AAuBT,SAAgB,mBACd,UAIQ;CACR,MAAM,QAAkB,EAAE;AAE1B,OAAM,KAAK,UAAU,SAAS,UAAU;AACxC,OAAM,KAAK,WAAW,SAAS,SAAS;AACxC,OAAM,KAAK,SAAS,SAAS,UAAU;AAEvC,KAAI,SAAS,SAAS,SAAS,MAAM,SAAS,GAAG;AAC/C,QAAM,KAAK,+EAA+E;AAC1F,OAAK,MAAM,QAAQ,SAAS,MAC1B,OAAM,KAAK,KAAK,KAAK,QAAQ,IAAI,KAAK,OAAO,KAAK,KAAK,QAAQ,MAAM,GAAG,EAAE,CAAC,IAAI,KAAK,UAAU,iBAAiB;;CAInH,MAAM,WAAW,SAAS,aAAa,QAAQ,MAAM,EAAE,aAAa,EAAE;CACtE,MAAM,UAAU,SAAS,aAAa,QAAQ,MAAM,EAAE,aAAa,KAAK,EAAE,YAAY,EAAE;CACxF,MAAM,UAAU,SAAS,aAAa,QAAQ,MAAM,EAAE,YAAY,EAAE;AACpE,OAAM,KACJ,kBAAkB,SAAS,aAAa,OAAO,IAAI,SAAS,OAAO,cAAc,QAAQ,OAAO,WAAW,QAAQ,OAAO,OAC3H;AAED,KAAI,SAAS,aAAa,SAAS,GAAG;AACpC,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,+BAA+B;AAC1C,OAAK,MAAM,KAAK,SAAS,aAAa,MAAM,GAAG,GAAG,EAAE;GAClD,MAAM,YAAY,EAAE,aAAa,IAAI,SAAS,EAAE,aAAa,IAAI,QAAQ;AACzE,SAAM,KAAK,KAAK,EAAE,KAAK,OAAO,GAAG,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE,UAAU,WAAW,YAAY;;AAE5F,MAAI,SAAS,aAAa,SAAS,GACjC,OAAM,KAAK,aAAa,SAAS,aAAa,SAAS,GAAG,sBAAsB;;AAIpF,KAAI,SAAS,gBAAgB,SAAS;AACpC,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,oBAAoB,SAAS,eAAe,UAAU,UAAU,SAAS,eAAe,aAAa,iBAAiB;AACjI,QAAM,KAAK,mGAAmG;AAC9G,QAAM,KAAK,4IAA4I;AACvJ,QAAM,KAAK,qFAAqF;;AAGlG,KAAI,SAAS,mBAAmB,SAAS,GAAG;AAC1C,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,uBAAuB;AAClC,OAAK,MAAM,CAAC,GAAG,OAAO,SAAS,mBAAmB,MAAM,GAAG,EAAE,CAAC,SAAS,CACrE,OAAM,KAAK,KAAK,IAAI,EAAE,IAAI,KAAK;AAEjC,MAAI,SAAS,mBAAmB,SAAS,EACvC,OAAM,KAAK,aAAa,SAAS,mBAAmB,SAAS,EAAE,sBAAsB;;AAIzF,KAAI,SAAS,SAAS,SAAS,GAAG;AAChC,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,mCAAmC;AAC9C,OAAK,MAAM,CAAC,GAAG,YAAY,SAAS,SAAS,MAAM,GAAG,EAAE,CAAC,SAAS,CAChE,OAAM,KAAK,KAAK,IAAI,EAAE,IAAI,UAAU;AAEtC,MAAI,SAAS,SAAS,SAAS,EAC7B,OAAM,KAAK,aAAa,SAAS,SAAS,SAAS,EAAE,sBAAsB;;AAI/E,KAAI,SAAS,OAAO,SAAS,GAAG;AAC9B,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,sBAAsB;AACjC,OAAK,MAAM,SAAS,SAAS,OAAO,MAAM,GAAG,EAAE,CAC7C,OAAM,KAAK,KAAK,MAAM,OAAO,IAAI,MAAM,OAAO,KAAK,KAAK,GAAG;AAE7D,MAAI,SAAS,OAAO,SAAS,EAC3B,OAAM,KAAK,aAAa,SAAS,OAAO,SAAS,EAAE,sBAAsB;;AAI7E,KAAI,SAAS,YAAY,SAAS,GAAG;AACnC,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,gBAAgB;AAC3B,OAAK,MAAM,QAAQ,SAAS,YAAY,MAAM,GAAG,EAAE,CACjD,OAAM,KAAK,OAAO,OAAO;AAE3B,MAAI,SAAS,YAAY,SAAS,EAChC,OAAM,KAAK,aAAa,SAAS,YAAY,SAAS,EAAE,sBAAsB;;AAIlF,KAAI,SAAS,eAAe,SAAS,GAAG;AACtC,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,8CAA8C;AACzD,OAAK,MAAM,WAAW,SAAS,eAAe,MAAM,GAAG,GAAG,CACxD,OAAM,KAAK,OAAO,QAAQ,aAAa,KAAK,QAAQ,SAAS,GAAG,QAAQ,aAAa;AAEvF,MAAI,SAAS,eAAe,SAAS,GACnC,OAAM,KAAK,aAAa,SAAS,eAAe,SAAS,GAAG,sBAAsB;;AAItF,KAAI,SAAS,mBAAmB,SAAS,GAAG;AAC1C,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,iFAAiF;AAC5F,OAAK,MAAM,WAAW,SAAS,mBAAmB,MAAM,GAAG,GAAG,EAAE;GAC9D,MAAM,WAAW,QAAQ,OAAO,GAAG,QAAQ,OAAO,OAAO,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,OAAO;GACjH,MAAM,cAAc,QAAQ,KAAK,MAAM,GAAG,IAAI;AAC9C,SAAM,KAAK,OAAO,SAAS,GAAG,cAAc,QAAQ,KAAK,SAAS,MAAM,QAAQ,KAAK;;AAEvF,MAAI,SAAS,mBAAmB,SAAS,GACvC,OAAM,KAAK,aAAa,SAAS,mBAAmB,SAAS,GAAG,sBAAsB;;AAI1F,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,cAAc,SAAS,KAAK,OAAO;AAE9C,QAAO,MAAM,KAAK,KAAK;;AACxB,eAAsB,0BAA0B,MAA8D;CAC7G,MAAM,EAAE,OAAO,SAAS,cAAc;AAEtC,KAAI,CAAC,WAAW,UAAU,CACxB,OAAM,IAAI,MAAM,uCAAuC,YAAY;CAOrE,MAAM,QAAQ,8BAA8B,SAAS,UAAU;CAC/D,MAAM,aAAa,MAAM,MAAK,SAAQ,KAAK,WAAW;CAEtD,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAM,SAAQ;EACxD,MAAM,SAAS,aAAa,GAAG,KAAK,QAAQ,KAAK;EACjD,MAAM,CAAC,SAAS,QAAQ,YAAY,MAAM,QAAQ,IAAI;GACpD,WAAW,KAAK,IAAI;GACpB,iBAAiB,KAAK,IAAI;GAC1B,YAAY,KAAK,KAAK,KAAK,aAAa;GACzC,CAAC;EACF,MAAM,CAAC,cAAc,UAAU,kBAAkB,MAAM,QAAQ,IAAI;GACjE,gBAAgB,KAAK,KAAK,UAAU,OAAO;GAC3C,YAAY,KAAK,KAAK,SAAS;GAC/B,WAAW,KAAK,KAAK,SAAS,CAAC,OAAO,QAAQ;AAC5C,YAAQ,KAAK,8CAA8C,KAAK,QAAQ,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAC/H,WAAO,EAAE;KACT;GACH,CAAC;AACF,SAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA,gBAAgB,SACZ,eAAe,KAAI,aAAY;IAAE,GAAG;IAAS,UAAU,GAAG,SAAS,QAAQ;IAAY,EAAE,GACzF;GACL;GACD,CAAC;CAIH,MAAM,UAAU,QAAQ,MAAK,SAAQ,KAAK,aAAa,SAAS,EAAE,IAAI,QAAQ;CAC9E,MAAM,UAAU,QAAQ;CAGxB,MAAM,gBAAgB,aAAa,QAAQ,SAAU,KAAK,UAAU,QAAQ;CAC5E,MAAM,eAAe,QAClB,SAAQ,SAAQ,KAAK,aAAa,CAClC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,UAAU;CAC5C,MAAM,OAAO,aACT;EACE,MAAM,QACH,QAAO,SAAQ,KAAK,aAAa,SAAS,EAAE,CAC5C,KAAI,SAAQ,MAAM,KAAK,KAAK,QAAQ,OAAO,KAAK,SAAS,OAAO,CAChE,KAAK,KAAK,IAAI;EACjB,WAAW;EACZ,GACD,QAAQ;CACZ,MAAM,iBAAiB,QAAQ,SAAQ,SAAQ,KAAK,eAAe;CACnE,MAAM,eAAoC,QAAQ,KAAI,UAAS;EAC7D,SAAS,KAAK,KAAK;EACnB,QAAQ,KAAK;EACb,SAAS,KAAK;EACd,UAAU,KAAK;EACf,WAAW,KAAK,aAAa;EAC9B,EAAE;CAEH,MAAM,CAAC,kBAAkB,aAAa,sBAAsB,MAAM,QAAQ,IAAI;EAC5E,8BAA8B,WAAW,QAAQ;EACjD,gBAAgB,UAAU;EAC1B,wBAAwB;GAAE;GAAW,QAAQ;GAAe,CAAC,CAAC,OAAO,QAAQ;AAC3E,WAAQ,KAAK,qDAAqD,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACrH,UAAO,EAAE;IACT;EACH,CAAC;CAEF,MAAM,YAAY,aAAa;CAC/B,MAAM,eAAe,aAAa,QAAQ,KAAK,MAAM,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE;CACxF,MAAM,iBAAiB;EACrB;EACA;EACA,SAAS,YAAA,MAA4C,eAAA;EACtD;CAED,MAAM,cAAcA,OAAK,WAAW,aAAa,UAAU,MAAM;AACjE,OAAM,MAAM,aAAa,EAAE,WAAW,MAAM,CAAC;CAC7C,MAAM,eAAeA,OAAK,aAAa,eAAe;CAEtD,MAAM,WAAkC;EACtC;EACA;EACA,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACR;EACA,GAAI,aAAa,EAAE,OAAO,cAAc,GAAG,EAAE;EAC7C;EACA;EACA;EACA,oBAAoB,iBAAiB;EACrC,UAAU,iBAAiB;EAC3B,QAAQ,iBAAiB;EACzB;EACA;EACA;EACA;EACD;AAED,OAAM,UAAU,cAAc,KAAK,UAAU,UAAU,MAAM,EAAE,EAAE,QAAQ;AAEzE,QAAO;;;;;;;;;;AAaT,MAAa,sBACX,SAEA,OAAO,WAAW;CAChB,WAAW,0BAA0B,KAAK;CAC1C,QAAQ,UACN,IAAI,QAAQ;EACV,MAAM,KAAK;EACX,WAAW;EACX;EACD,CAAC;CACL,CAAC;;;AChlBJ,SAAS,yBAAyB,SAAgD;CAChF,MAAM,SAAS,mBAAmB,CAAC,MAAM,UAAU,MAAM,OAAO,QAAQ,IAAI,kBAAkB,QAAQ;AACtG,KAAI,CAAC,UAAU,OAAO,OAAO,QAAS,QAAO;AAE7C,KAAI;EADY,OAAO;EAAS,OAAO;EAAM,OAAO;EAAW,OAAO;EAAS,OAAO;EAC5E,CAAC,MAAM,UAAU,OAAO,UAAU,YAAY,MAAM,WAAW,EAAE,CAAE,QAAO;AACpF,QAAO;EACL,IAAI;EACJ,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,OAAO,OAAO;EACf;;;;;;;AAQH,eAAsB,YAAY,SAAqD;CACrF,MAAM,WAAW,yBAAyB,QAAQ;AAClD,KAAI,aAAa,KACf,OAAM,IAAI,MAAM,uDAAuD,UAAU;AAEnF,0BAAyB,SAAS;CAElC,MAAM,YAAY,KAAK,iBAAiB,EAAE,SAAS;CACnD,MAAM,WAAW,KAAK,WAAW,QAAQ;CACzC,MAAM,SAAS,MAAM,oBAAoB,UAAU,UAAU;AAC7D,KAAI,OAAO,WAAY,uBAAsB,QAAQ;KAChD,OAAM,wBAAwB,SAAS;AAC5C,QAAO;;;;;;;;;;;;;;;ACrCT,SAAgB,qBAAqB,QAOzB;CACV,MAAM,eACJ,CAAC,CAAC,OAAO,kBAAkB,CAAC,CAAC,OAAO,cAAc,OAAO,mBAAmB,OAAO;CACrF,MAAM,iBACJ,CAAC,CAAC,OAAO,oBAAoB,CAAC,CAAC,OAAO,gBAAgB,OAAO,qBAAqB,OAAO;AAC3F,QAAO,OAAO,iBAAiB,OAAO,mBAAmB,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACiB7E,eAAA,0BAAA,SAAA;AAEE,QAAA,SAAA,OAAA,aAAA,SAAA,UAAA,QAAA,KAAA,EAAA,QAAA;;AAKF,MAAA,sBAAA,OAAA;AACA,MAAA,mCAAA;;;;;;;AAEA,SAAA,gBAAA,SAAA,SAAA;AACE,QAAA,SAAA,QAAA,aAAA,CAAA,UAAA;;AAGF,SAAA,+BAAA,OAAA,SAAA;;AAEE,QAAA,QAAA,SAAA,aAAA,UAAA,IAAA,kCAAA,KAAA,QAAA;;AAIF,eAAA,sBAAA,SAAA;AACE,KAAA;AACE,UAAA,MAAA,OAAA,WAAA,kBAAA,CAAA,EAAA,SAAA,QAAA;;AAEA,SAAA;;;AAIJ,SAAA,wBAAA,WAAA,OAAA,SAAA;AACE,QAAA,OAAA,WAAA,aAAA,UAAA,OAAA,GAAA,QAAA,KAAA;;AAKF,eAAA,yBAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDE,SAAA,IAAA,oCAAA,KAAA,QAAA,GAAA,KAAA,QAAA,IAAA,UAAA,QAAA;AACA,QAAA;;AAKF,eAAA,kCAAA,MAAA;AAYE,KAAA;;;;;;;AAQE,QAAA,MAAA,UAAA,WAAA,EAAA,EAAA,WAAA,MAAA,CAAA;AAEA,QAAA,GAAA,YAAA,EAAA,OAAA,MAAA,CAAA;AACA,QAAA,GAAA,OAAA,aAAA,oBAAA,EAAA,EAAA,OAAA,MAAA,CAAA;AACA,QAAA,GAAA,OAAA,aAAA,wBAAA,EAAA,EAAA,OAAA,MAAA,CAAA;;AAIA,MAAA,KAAA,oBAAA,KAAA;;AAII,kBAAA,mBAAA,KAAA,MAAA,YAAA,CAAA;;AAEA,WAAA,KAAA,8DAAA,KAAA,QAAA,GAAA,KAAA,QAAA,KAAA,YAAA;;;;;;;;;;;;AA0BJ,MAAA,qBAAA;;;;;;;;AACE,WAAA,IAAA,+CAAA,KAAA,QAAA,OAAA,KAAA,QAAA,kCAAA;;AAEA,OAAA,aAAA,WAAA,aAAA,qBAAA,OAAA;AACE,QAAA;;AAEE,SAAA,SAAA;AACE,cAAA,gBAAA,KAAA;AACA,cAAA,cAAA,KAAA;AACA,cAAA,mBAAA;AACA,cAAA,yBAAA;AACA,cAAA,mBAAA,IAAA,KAAA,KAAA,KAAA,GAAA,oBAAA,CAAA,aAAA;AACA,aAAA,QAAA;AACA,aAAA,QAAA;AACA,YAAA,OAAA,WAAA,eAAA,QAAA,CAAA;;;AAGJ,WAAA;;;;;;;AAKF,WAAA,KAAA,sCAAA,KAAA,QAAA,mDAAA,cAAA;AAKA,OAAA,aAAA,qBAAA,SAAA,aAAA,OAAA,SAAA,qBAAA,CAAA,OAAA,OAAA,WAAA,UAAA,cAAA,CAAA;;;;;;;;;;;;;;;;;;AAwBF,OAAA,IAAA,UAAA,IAAA,WAAA,EAAA,KAAA;AAEI,SAAA,MAAA,SAAA,KAAA,SAAA,UAAA,aAAA;AACA;;AAEA,OAAA,CAAA,+BAAA,KAAA,QAAA,CAAA,OAAA;AACA,OAAA,MAAA,sBAAA,QAAA,EAAA;AACE,YAAA,KAAA,kBAAA,QAAA,uHAAA;AAIA,WAAA;;;;;;;AAOF,OAAA,UAAA,KAAA,EAAA,OAAA;AACA,WAAA,KAAA,kBAAA,QAAA,wDAAA,MAAA,IAAA;AAGA,SAAA,IAAA,SAAA,YAAA;AAAuC,eAAA,SAAA,MAAA;;;AAG3C,MAAA;;AAEE,sBAAA;;;;;;;AAIF,SAAA;;;;;;AAEA,SAAA;;;;;;;;AAuBJ,eAAA,6BAAA,QAAA;;;;;;;;;;;;;;;AAgBI,MAAA,CAAA,OAAA,QAAA,KAAA;;AAGI,SAAA,aAAA,OAAA,kBAAA,mBAAA,QAAA,GAAA,OAAA,SAAA,OAAA,UAAA;;AAEA,WAAA,KAAA,mCAAA,QAAA,oBAAA,OAAA,iBAAA,IAAA,UAAA;;AAGJ,SAAA;;;AAIF,KAAA,gBAAA,SAAA,EAAA,SAAA,KAAA,sCAAA,OAAA,QAAA,QAAA,gBAAA,OAAA,8BAAA;AAGA,QAAA;;AAKF,MAAA,qBAAA,SAAA,OAAA,cAAA,yBAAA,KAAA,CAAA;;;;;;AAcA,MAAA,8BAAA,SAAA,OAAA,cAAA,kCAAA,KAAA,CAAA;;;;;;;;;;AAyBA,eAAA,8BAAA,SAAA,OAAA,EAAA,EAAA;;;;;AAQE,KAAA,CAAA,YAAA,QAAA;;;;;AAKA,KAAA;;AAEE,WAAA,MAAA,4BAAA,aAAA,EAAA,gBAAA,MAAA,CAAA;;AAEA,SAAA;;;;;AAKF,KAAA,CAAA,QAAA,aAAA,CAAA,OAAA,YAAA,QAAA;;;;;;;;AAUA,KAAA;AACE,aAAA,IAAA,IAAA,MAAA,OAAA,WAAA,kBAAA,CAAA,CAAA;;AAIA,oBAAA;;;;AAKF,MAAA,MAAA,WAAA,kBAAA;;;;AAIE,MAAA,WAAA,wBAAA,WAAA,OAAA,QAAA,CAAA,CAAA;AACA,MAAA,SAAA,IAAA,WAAA,CAAA;AAUA,MAAA,iBAAA;AACE,OAAA,CAAA,gBAAA;AACA,OAAA;;AAEE,UAAA,OAAA,WAAA,eAAA,sBAAA,UAAA,SAAA,CAAA,CAAA;;;AAGJ,oBAAA,KAAA,QAAA;;AAEF,KAAA,kBAAA,WAAA,EAAA,QAAA;;;;;;;;;;;;;;;;;;;AAyBA,SAAA,IAAA,kBAAA,UAAA;AACA,uBAAA;;;;;;AACA,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WCxaqE;sBACT;aAEG;kBACqB;kBAGA;aAcH;YACjC;6BAIsB;AAExE,MAAM,YAAYI,YAAUC,OAAK;AAMjC,MAAa,2BAA2B,OAAU;AAKlD,MAAM,uBAAuB,cAA8BC,OAAK,WAAW,eAAe;AAC1F,MAAM,wBAAwB,cAA8BA,OAAK,WAAW,YAAY;AAExF,eAAe,qBAAqB,SAAiB,WAAoC;AACvF,KAAI;EACF,MAAM,EAAE,oCAAoC,kCAAkC,MAAM,OAAO;AAE3F,MADc,8BAA8B,SAAS,UAC5C,CAAC,MAAK,SAAQ,KAAK,WAAW,EAAE;GACvC,MAAM,EAAE,kCAAkC,MAAM,OAAO;GACvD,MAAM,SAAS,MAAM,8BAA8B,SAAS,UAAU;AACtE,UAAO,SACH,WAAW,OAAO,CAAC,OAAO,OAAO,CAAC,OAAO,MAAM,CAAC,UAAU,GAAG,EAAE,GAC/D;;EAMN,MAAM,WAAW,mCAAmC,SAAS,UAAU;EACvE,MAAM,EAAE,WAAW,MAAM,UAAU;GAAC;GAAO;GAAa;GAAa;GAAO,CAAC,KAAK,IAAI,EAAE;GACtF,KAAK;GACL,UAAU;GACV,SAAS;GACV,CAAC;AACF,SAAO,OAAO,MAAM,IAAI;SAClB;AACN,SAAO;;;AAIX,SAAgB,sBAAsB,MAS3B;CACT,MAAM,eAAe,iBAAiB,KAAI,MAAK,KAAKA,OAAK,KAAK,WAAW,GAAG,EAAE,KAAK,GAAG,CAAC,KAAK,KAAK;CACjG,MAAM,kBAAkB,iBAAiB,KAAI,MAAK,oBAAoB,EAAE,mCAAmC,EAAE,gCAAgC,EAAE,WAAW,CAAC,KAAK,KAAK;CACrK,MAAM,gBAAgB,oBAAoB,KAAK,UAAU;CAEzD,MAAM,SAAS;EACb,kCAAkC,KAAK;EACvC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa,iBAAiB,OAAO;EACrC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,qBAAqB,iBAAiB,OAAO;EAC7C;EACA;EACA;EACA;EACA,UAAU,KAAK;EACf,WAAW,KAAK;EAChB,cAAc,KAAK;EACnB,KAAK,QAAQ,OAAO,KAAK,UAAU,gCAAgC,KAAK,OAAO;EAC/E,WAAW,KAAK;EAChB,qBAAqB,KAAK;EAC1B,0BAA0B;EAC1B;EACA,KAAK,eACD;GACE;GACA;GACA,KAAK;GACL;GACA;GACA,KAAK,sBAAsB,kBAAkB,KAAK,wBAAwB;GAC3E,CAAC,KAAK,KAAK,GACZ,KAAK,sBACH,qBAAqB,KAAK,wBAC1B;EACN;EACA;EACA;EACA;EACA;EACA,uCAAuC,KAAK,QAAQ,0DAA0D,KAAK,MAAM;EACzH,uCAAuC,KAAK,QAAQ,+DAA+D,KAAK,MAAM;EAC9H;EAKA;EACA;EACA;EACA;EACD,CAAC,OAAO,QAAQ,CAAC,KAAK,KAAK;CAE5B,MAAM,YAAY,OAAO,WAAW,QAAQ,QAAQ;AACpD,SAAQ,IAAI,uCAAuC,KAAK,QAAQ,IAAI,UAAU,QAAQ;AACtF,QAAO;;AAST,SAAS,sBAAsB,MASpB;CACT,MAAM,mBAAmB,qBAAqB,KAAK,UAAU;CAC7D,MAAM,SAAS;EACb,mBAAmB,KAAK,QAAQ;EAChC;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU,KAAK;EACf,WAAW,KAAK;EAChB,cAAc,KAAK;EACnB,KAAK,QAAQ,OAAO,KAAK,UAAU,gCAAgC,KAAK,OAAO;EAC/E,WAAW,KAAK;EAChB,qBAAqB,KAAK;EAC1B,uBAAuB;EACvB;EACA,KAAK,eACD;GACE;GACA;GACA,KAAK;GACL;GACA;GACA,KAAK,sBAAsB,kBAAkB,KAAK,wBAAwB;GAC3E,CAAC,KAAK,KAAK,GACZ,KAAK,sBACH,qBAAqB,KAAK,wBAC1B;EACN;EACA;EACA;EACA;EACA;EACA;EACA,6BAA6B,iBAAiB;EAC9C;EACA;EACA,uCAAuC,KAAK,QAAQ,0DAA0D,KAAK,MAAM;EACzH,uCAAuC,KAAK,QAAQ,+DAA+D,KAAK,MAAM;EAC9H;EAKA;EACA;EACA;EACA;EACD,CAAC,OAAO,QAAQ,CAAC,KAAK,KAAK;CAE5B,MAAM,YAAY,OAAO,WAAW,QAAQ,QAAQ;AACpD,SAAQ,IAAI,yCAAyC,KAAK,QAAQ,IAAI,UAAU,QAAQ;AACxF,QAAO;;AAET,eAAe,+BACb,MACiF;CACjF,MAAM,sBAAsB,KAAK,KAAK;AACtC,KAAI,CAAC,KAAK,OAAO;EACf,MAAM,UAAU,uBAAuB,KAAK,QAAQ;EACpD,MAAM,aAAa,UAAU,oBAAoB,SAAS,KAAK,QAAQ,EAAE,cAAc,KAAA;AACvF,MAAI,WAAY,QAAO;GAAE,GAAG;GAAM,OAAO;GAAY;;CAEvD,MAAM,oBAAoB,SAAS,KAAK,QAAQ,aAAa,CAAC;CAI9D,MAAM,cAAc,MAAM,2BAA2B,KAAK,QAAQ;AAClE,KAAI,YAAY,MAAM;EACpB,MAAM,UAAU,+CAA+C,KAAK,QAAQ,KAAK,YAAY;AAC7F,UAAQ,IAAI,QAAQ;AACpB,wBAAsB;GAAE,QAAQ;GAAY,OAAO;GAAQ;GAAS,SAAS,KAAK;GAAS,CAAC;AAC5F,SAAO;GAAE,SAAS;GAAO;GAAS;;AAYpC,KAAI,kBAAkB,KAAK,QAAQ,KAAK,QAAQ;AAC9C,sBAAoB,KAAK,SAAS;GAChC,cAAc;GACd,aAAa;GACb,kCAAiB,IAAI,MAAM,EAAC,aAAa;GAC1C,CAAC;EACF,MAAM,UAAU,sBAAsB,KAAK,QAAQ;AACnD,UAAQ,IAAI,kBAAkB,UAAU;AACxC,wBAAsB;GAAE,QAAQ;GAAU,OAAO;GAAQ;GAAS,SAAS,KAAK;GAAS,CAAC;AAC1F,SAAO;GAAE,SAAS;GAAM;GAAS;;AAUnC,KAAI,KAAK,OAAO;EACd,MAAM,UAAU,MAAM,OAAO,WAC3B,wBAAwB,KAAA,GAAW,KAAK,QAAQ,CAAC,KAC/C,OAAO,YAAY,OAAO,QAAQ;GAChC,QAAQ,EAAE;GACV,QAAQ,CAAC,kBAAkB;GAC5B,CAAC,CAAC,CACJ,CACF;AACD,MAAI,QAAQ,OAAO,SAAS,EAC1B,QAAO;GACL,SAAS;GACT,SAAS,+CAA+C,QAAQ,OAAO,KAAK,KAAK;GACjF,OAAO,+BAA+B,QAAQ,OAAO,KAAK,KAAK;GAChE;;AAIL,KAAI;AAEF,OADiB,KAAK,QAAQ,EAAE,GAAG,MAAM,OAAO,WAAW,kBAAkB,CAAC,EACjE,SAAS,kBAAkB,EAAE;GACxC,MAAM,WAAW,MAAM,OAAO,WAAW,WAAW,kBAAkB,CAAC;GAevE,IAAI,aAAa;GACjB,IAAI;AACJ,OAAI,CAAC,YAAY,CAAC,KAAK,MACrB,KAAI;IACF,MAAM,QAAQ,MAAM,qBAAqB,KAAK,SAAS,KAAK,UAAU;AACtE,QAAI,UAAU,UAAW,OAAM,IAAI,MAAM,6BAA6B;AACtE,mBAAe,SAAS,KAAK,QAAQ,aAAa,CAAC,UAAU;IAC7D,MAAM,mBAAmB,kBAAkB,kBAAkB,EAAE;AAG/D,QAAI,CAAC,oBAAoB,qBAAqB,cAAc;AAC1D,kBAAa;AACb,aAAQ,IACN,kBAAkB,kBAAkB,oBAAoB,oBAAoB,UAAU,cAAc,aAAa,iCAClH;;YAEI,UAAU;AACjB,YAAQ,KACN,kCAAkC,kBAAkB,kDACpD,SACD;;GAWL,IAAI,eAAe;AACnB,OAAI,CAAC,YAAY,CAAC,KAAK,SAAS,CAAC,WAC/B,KAAI;IACF,MAAM,SAAS,oBAAoB,KAAK,QAAQ;IAChD,MAAM,WAAW,QAAQ,iBAAiB,YACrC,QAAQ,iBAAiB,aACzB,QAAQ,iBAAiB;IAM9B,MAAM,eAAe,CAAC,CAAC,QAAQ,sBACzB,CAAC,OAAO,mBAAmB,KAAK,MAAM,OAAO,kBAAkB,GAAG,IAAI,KAAK,OAAO,gBAAgB,CAAC,SAAS;IAMlH,IAAI,gBAAgB;AACpB,QAAI,aACF,KAAI;KACF,MAAM,YAAYA,OAAK,KAAK,WAAW,aAAa,UAAU,aAAa;AAC3E,qBAAgB,WAAW,qBAAqB,UAAU,CAAC,IACtD,WAAW,oBAAoB,UAAU,CAAC;YACzC;IAEV,MAAM,eAAe,yBAAyB,kBAAkB,CAC7D,KAAI,OAAMC,kBAAsB,GAAG,CAAC,CACpC,QAAO,UAAS,UAAU,QAAQ,UAAU,KAAA,EAAU;IACzD,MAAM,iBAAiB,6BAA6B,KAAK,SAAS,UAAU,EAAE,EAAE,aAAa;AAC7F,oBAAgB,YAAY,iBAAiB,CAAC,eAAe,WAAW;AACxE,QAAI,aACF,SAAQ,IACN,kBAAkB,kBAAkB,6BAA6B,QAAQ,aAAa,kEACvF;aACQ,SACT,SAAQ,IACN,kBAAkB,kBAAkB,2BAA2B,QAAQ,aAAa,kGACrF;YAEI,WAAW;AAClB,YAAQ,KAAK,2EAA2E,KAAK,QAAQ,IAAI,UAAU;;AAIvH,OAAI,CAAC,YAAY,CAAC,KAAK,SAAS,CAAC,cAAc,CAAC,cAAc;AAQ5D,SAPoB,eAChB,MAAM,+BAA+B,KAAK,SAAS;KACnD,OAAO;KACP,eAAe,KAAK;KACpB,QAAQ;KACT,CAAC,GACA,EAAE,WAAW,OAAO,EACR,WAAW;KACzB,MAAM,UAAU,yDAAyD,KAAK;AAC9E,2BAAsB;MAAE,QAAQ;MAAU,OAAO;MAAQ;MAAS,SAAS,KAAK;MAAS,CAAC;AAC1F,YAAO;MAAE,SAAS;MAAM;MAAS;;AAEnC,YAAQ,IAAI,qCAAqC,kBAAkB,uBAAuB,KAAK,QAAQ,mBAAmB;AAC1H,WAAO;KAAE,SAAS;KAAO,SAAS,8CAA8C;KAAqB;;GAKvG,MAAM,SAAS,KAAK,QAAQ,+BACxB,WAAW,iBACX,aAAa,gBACb;AACJ,WAAQ,IAAI,kBAAkB,kBAAkB,GAAG,OAAO,sBAAsB;GAChF,MAAM,UAAU,MAAM,OAAO,WAC3B,wBAAwB,KAAA,GAAW,KAAK,QAAQ,CAAC,KAC/C,OAAO,YAAY,OAAO,QAAQ;IAChC,QAAQ,EAAE;IACV,QAAQ,CAAC,kBAAkB;IAC5B,CAAC,CAAC,CACJ,CACF;AACD,OAAI,QAAQ,OAAO,SAAS,EAC1B,QAAO;IACL,SAAS;IACT,SAAS,+CAA+C,QAAQ,OAAO,KAAK,KAAK;IACjF,OAAO,+BAA+B,QAAQ,OAAO,KAAK,KAAK;IAChE;;UAGE,KAAK;AACZ,UAAQ,KAAK,+CAA+C,KAAK,QAAQ,gBAAgB,IAAI;;CAM/F,MAAM,qBAAqB,kCAAkC,KAAK,QAAQ;AAC1E,KAAI,uBAAuB,eAAe,uBAAuB,WAAW;EAC1E,MAAM,eAAe;EAIrB,MAAM,UAAU,6BAHD,uBAAuB,cAClC,uBAAuB,aAAa,4CACpC,wBAAwB,aAAa;AAEzC,sBAAoB,KAAK,SAAS;GAChC,cAAc;GACd,aAAa;GACd,CAAC;AACF,SAAO;GAAE,SAAS;GAAO,OAAO;GAAM;GAAS;;CAGjD,MAAM,OAAO,MAAM,oBACjB,KAAK,SACL,KAAK,WACL,QACA,2BAA2B,CAC5B;AACD,KAAI,KAAK,OAAO;EACd,MAAM,UAAU,6BAA6B,KAAK,UAAU;AAC5D,sBAAoB,KAAK,SAAS;GAChC,cAAc;GACd,aAAa;GACd,CAAC;AACF,SAAO;GAAE,SAAS;GAAO,OAAO;GAAM;GAAS;;AAKjD,KAAI;EACF,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAC9C,QAAM,OAAO,WAAW,qBAAqB,KAAK,UAAU,CAAC;SACvD;AAcR,MAAI,MALsB,+BAA+B,KAAK,SAAS;EACrE,OAAO,kBAAkB,kBAAkB,EAAE;EAC7C,eAAe,KAAK;EACpB,QAAQ;EACT,CAAC,EACc,WAAW;EACzB,MAAM,UAAU,yDAAyD,KAAK;AAC9E,wBAAsB;GAAE,QAAQ;GAAU,OAAO;GAAQ;GAAS,SAAS,KAAK;GAAS,CAAC;AAC1F,SAAO;GAAE,SAAS;GAAM;GAAS;;AAGnC,KAAI;EACF,MAAM,gBAAgB,oBAAoB,KAAK,QAAQ;AACvD,sBAAoB,KAAK,SAAS;GAChC,cAAc;GACd,kCAAiB,IAAI,MAAM,EAAC,aAAa;GACzC,GAAG,2CAA2C,cAAc;GAC7D,CAAC;UACK,KAAK;AACZ,UAAQ,MAAM,qDAAqD,KAAK,QAAQ,IAAI,IAAI;AACxF,SAAO;GACL,SAAS;GACT,SAAS;GACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACxD;;AAGH,KAAI;EACF,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,qBAAmB;GAAE,MAAM;GAAe,YAAY;GAAgB,SAAS,KAAK;GAAS,CAAC;SACxF;AAIR,KAAI;EACF,MAAM,EAAE,UAAU,gBAAgB,eAAe,mBAAmB,wBAAwB,aAAa,uBAAuB,MAAM,OAAO;EAC7I,MAAM,iBAAiB,MAAM,OAAO,WAAW,cAAc,SAAS,KAAK,QAAQ,aAAa,GAAG,CAAC;EACpG,MAAM,YAAY,KAAK,cAAc,QAAQ,gBAAgB,iBAAiB;EAS9E,MAAM,QAAQ,MAAM,qBAAqB,KAAK,SAAS,KAAK,UAAU;EACtE,MAAM,QAAQ,UAAU,YACpB,SAAS,KAAK,QAAQ,aAAa,CAAC,UAAU,UAC9C,SAAS,KAAK,QAAQ,aAAa,CAAC;EACxC,MAAM,YAAYD,OAAK,KAAK,WAAW,aAAa,UAAU,MAAM;EACpE,IAAI;EACJ,IAAI;AACJ,MAAI;GACF,MAAM,WAAW,MAAM,OAAO,WAAW,mBAAmB;IAC1D;IACA,SAAS,KAAK;IACd,WAAW,KAAK;IAChB,QAAQ,KAAK;IACd,CAAC,CAAC;AACH,yBAAsB,SAAS;AAC/B,kBAAe,mBAAmB,SAAS;AAC3C,WAAQ,IAAI,0CAA0C,oBAAoB,IAAI,SAAS,aAAa,OAAO,SAAS;WAC7G,QAAQ;AACf,WAAQ,KAAK,oDAAoD,KAAK,QAAQ,qDAAqD,OAAO;;EAG5I,MAAM,aAAa,wBAAwB,KAAK,QAAQ;EAExD,MAAM,SAAS,aACX,sBAAsB;GAAE,GAAG;GAAM;GAAO;GAAW;GAAqB;GAAc,CAAC,GACvF,sBAAsB;GAAE,GAAG;GAAM;GAAO;GAAW;GAAqB;GAAc,CAAC;EAE3F,MAAM,wBAAwB,qBAC5B,6BAA6B;GAC3B,SAAS,KAAK;GACd,WAAW,KAAK;GAChB;GACA;GACA;GACA,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,OAAO,GAAG,EAAE;GAC3C,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,SAAS,GAAG,EAAE;GACjD;GACD,CAAC;EAQJ,MAAM,gBAAgB,SAAS,KAAK,QAAQ,aAAa,CAAC;EAC1D,MAAM,cAAc,kBAAkB,cAAc;AASpD,MARwB,qBAAqB;GAC3C,gBAAgB,KAAK;GACrB,kBAAkB,KAAK;GACvB,YAAY,aAAa;GACzB,cAAc,aAAa;GAC3B,eAAe,CAAC,CAAC;GACjB,iBAAiB,CAAC,CAAC,uBAAuB,cAAc;GACzD,CACkB,EAAE;AACnB,WAAQ,IAAI,oDAAoD,KAAK,QAAQ,2DAA2D;GACxI,MAAM,eAAe,MAAM,YAAY,eAAe,OAAO;AAC7D,OAAI,aAAa,SAAS;AACxB,QAAI;KAEF,MAAM,UAAU,kBAAkB,cAAc;AAChD,SAAI,SAAS;AACX,cAAQ,cAAc;AAEtB,cAAQ,mBAAmB,IAAI,KAAK,KAAK,KAAK,GAAG,yBAAyB,CAAC,aAAa;AACxF,YAAM,OAAO,WAAW,eAAe,QAAQ,CAAC;;YAE5C;AACR,QAAI,YAAY;AACd,SAAI,aAAa,gBAAgB,OAAO;MAItC,MAAM,WAAW,MAAM,8BAA8B,KAAK,SAAS;OACjE,QAAQ;OACR,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,OAAO,GAAG,EAAE;OAC3C,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,SAAS,GAAG,EAAE;OAClD,CAAC;AACF,UAAI,CAAC,SAAS,QACZ,QAAO;OACL,SAAS;OACT,SAAS,gEAAgE,SAAS;OAClF,OAAO,SAAS;OACjB;WAGH,OAAM,qBAAqB,cAAc;AAE3C,YAAO;MAAE,SAAS;MAAM,SAAS,8CAA8C;MAAiB;;AAElG,WAAO;KAAE,SAAS;KAAM,SAAS,uCAAuC;KAAiB;;AAE3F,WAAQ,KAAK,2CAA2C,cAAc,qCAAqC,aAAa,QAAQ;;AAIlI,MAAI,eAAe,uBAAuB,cAAc,CACtD,KAAI;AAAE,SAAM,mBAAmB,KAAK,SAAS,EAAE,YAAY,UAAU,CAAC;WAC/D,SAAS;AAAE,WAAQ,KAAK,2EAA2E,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,QAAQ,GAAG;;EAE3K,MAAM,MAAM,MAAM,SAAS,KAAK,SAAS,UAAU;GACjD,WAAW,KAAK;GAChB;GACA,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,OAAO,GAAG,EAAE;GAC3C,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,SAAS,GAAG,EAAE;GACjD,GAAI,YAAY,EAAE,WAAW,MAAM,GAAG,EAAE;GACxC,WAAW;GACZ,CAAC;AAKF,MAAI,cAAc;AAElB,MAAI,mBAAmB,IAAI,KAAK,KAAK,KAAK,GAAG,yBAAyB,CAAC,aAAa;AACpF,MAAI;AACF,SAAM,OAAO,WAAW,eAAe,IAAI,CAAC;WACrC,SAAS;AAChB,WAAQ,KAAK,mDAAmD,IAAI,GAAG,IAAI,QAAQ;;AAErF,MAAI,YAAY;AACd,SAAM,qBAAqB,IAAI,GAAG;AAClC,WAAQ,IAAI,6DAA6D,KAAK,QAAQ,IAAI,IAAI,KAAK;AACnG,yBAAsB;IAAE,QAAQ;IAAU,OAAO;IAAQ,SAAS,6BAA6B,KAAK,QAAQ,IAAI,IAAI;IAAM,SAAS,KAAK;IAAS,CAAC;AAClJ,UAAO;IACL,SAAS;IACT,SAAS,0BAA0B,IAAI;IACxC;;AAGH,UAAQ,IAAI,wDAAwD,KAAK,QAAQ,IAAI,IAAI,KAAK;AAC9F,wBAAsB;GAAE,QAAQ;GAAU,OAAO;GAAQ,SAAS,2BAA2B,KAAK,QAAQ,IAAI,IAAI;GAAM,SAAS,KAAK;GAAS,CAAC;AAEhJ,SAAO;GACL,SAAS;GACT,SAAS,wBAAwB,IAAI;GACtC;UACM,KAAK;AACZ,UAAQ,MAAM,kDAAkD,KAAK,QAAQ,IAAI,IAAI;AAQrF,MAAI;GACF,MAAM,SAAS,kBAAkB,kBAAkB;GACnD,MAAM,kBAAkB,QAAQ,YAAY,KAAK,MAAM,OAAO,UAAU,GAAG;AAG3E,OAF8B,WAAW,QAAQ,WAAW,KAAA,KACvD,OAAO,SAAS,gBAAgB,IAAI,mBAAmB,sBAAsB,KACvD;AAEzB,SAAI,MADmB,OAAO,WAAW,kBAAkB,CAAC,CAAC,YAAY,EAAE,CAAa,EAC3E,SAAS,kBAAkB,CACtC,OAAM,OAAO,WAAW,YAAY,kBAAkB,CAAC,CAAC,YAAY,GAAG;AAEzE,WAAO,SAAS;IAChB,MAAM,EAAE,mBAAmB,MAAM,OAAO;AACxC,UAAM,OAAO,WAAW,eAAe,OAAO,CAAC,CAAC,YAAY,GAAG;;UAE3D;AACR,sBAAoB,KAAK,SAAS;GAChC,cAAc;GACd,aAAa,6BAA6B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GAC3F,CAAC;AACF,SAAO;GACL,SAAS;GACT,SAAS;GACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACxD;;;AAoBL,eAAe,+BACb,YACA,SACiD;CACjD,MAAM,SAAmB,EAAE;CAC3B,MAAM,SAAmB,EAAE;CAC3B,IAAI;AAEJ,KAAI;AACF,gBAAc,MAAM,OAAO,WAAW,kBAAkB,CAAC;UAClD,KAAK;AACZ,UAAQ,KAAK,wEAAwE,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;AACtI,SAAO;GACL;GACA,QAAQ,CAAC,SAAS,QAAQ,aAAa,CAAC,SAAS;GAClD;;CAGH,MAAM,iBAAiB,YAAY,QAAO,MAAK,wBAAwB,GAAG,YAAY,QAAQ,CAAC;AAC/F,OAAM,QAAQ,IACZ,eAAe,IAAI,OAAO,gBAAgB;AACxC,MAAI;AACF,SAAM,OAAO,WAAW,YAAY,YAAY,CAAC;AACjD,WAAQ,IAAI,0CAA0C,cAAc;AACpE,UAAO,KAAK,YAAY;WACjB,KAAK;AACZ,WAAQ,IAAI,0BAA0B,YAAY,mCAAmC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACxI,UAAO,KAAK,YAAY;;GAE1B,CACH;AACD,QAAO;EAAE;EAAQ;EAAQ;;AAC1B,eAAe,+BAAgF;CAC9F,MAAM,SAAmB,EAAE;CAC3B,MAAM,SAAmB,EAAE;CAE3B,IAAI;AACJ,KAAI;AACF,gBAAc,MAAM,OAAO,WAAW,kBAAkB,CAAC;UAClD,KAAK;AACZ,UAAQ,KAAK,sEAAsE,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CAAC;AACpI,SAAO;GAAE;GAAQ;GAAQ;;CAG3B,MAAM,iBAAiB;EACrB;EACA;EACA;EACA;EACD;CAED,MAAM,iBAAiB,YAAY,QAAO,MAAK,eAAe,MAAK,MAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AACnF,KAAI,eAAe,WAAW,EAC5B,QAAO;EAAE;EAAQ;EAAQ;AAG3B,SAAQ,IAAI,0BAA0B,eAAe,OAAO,oCAAoC;AAEhG,OAAM,QAAQ,IACZ,eAAe,IAAI,OAAO,gBAAgB;AACxC,MAAI;AACF,SAAM,OAAO,WAAW,YAAY,YAAY,CAAC;AACjD,WAAQ,IAAI,wCAAwC,cAAc;AAClE,UAAO,KAAK,YAAY;WACjB,KAAK;AACZ,WAAQ,IAAI,0BAA0B,YAAY,mCAAmC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACxI,UAAO,KAAK,YAAY;;GAE1B,CACH;AAED,QAAO;EAAE;EAAQ;EAAQ;;;;;;;AAgB3B,MAAM,0BAA0B,wBAAgG;AAEhI,MAAa,2BACX,SAEA,OAAO,cAAc;CACnB,MAAM,MAAM,KAAK,QAAQ,aAAa;AACtC,KAAI,wBAAwB,WAAW,IAAI,CACzC,SAAQ,IAAI,wDAAwD,IAAI,8CAA8C;AAExH,QAAO,wBAAwB,IAC7B,WACM,yBAAyB,WAAW,+BAA+B,KAAK,CAAC,CAChF;EACD;;;;;;AAOJ,MAAa,2BACX,YACA,YAEA,OAAO,cAAc,+BAA+B,YAAY,QAAQ,CAAC;;;;;AAM3E,MAAa,8BACX,OAAO,cAAc,8BAA8B,CAAC;;;;;;;;AAoBtD,SAAgB,kBAAkB,SAA0B;CAC1D,MAAM,aAAa,QAAQ,aAAa;CACxC,MAAM,SAAS,SAAS,WAAW;CACnC,MAAM,cAAcC,kBAAsB,SAAS,WAAW,SAAS,EAAE;AACzE,QAAO,yBAAyB,OAAO,CAAC,MAAM,OAAO;EACnD,MAAM,UAAU,GAAG,MAAM,OAAO,OAAO;AACvC,MAAI,CAAE,iBAAuC,SAAS,QAAQ,CAAE,QAAO;AACvE,SAAO,CAAC,eAAeA,kBAAsB,GAAG,EAAE,gBAAgB;GAClE;;AAGJ,SAAgB,kBAAkB,SAA8B;AAC9D,KAAI,SAAS;EACX,MAAM,UAAU,uBAAuB,QAAQ;EAC/C,MAAM,YAAY,UAAU,oBAAoB,SAAS,QAAQ,EAAE,aAAa,KAAA;AAChF,MAAI,cAAc,WAAW,cAAc,UAAU,cAAc,OACjE,QAAO;;CAIX,MAAM,aAAaC,gBAAgB,CAAC,OAAO,OAAO,QAAQ;AAC1D,QAAO,eAAe,UAAU,eAAe,SAAS,aAAa;;;;;;;;AASvE,SAAgB,wBAAwB,SAA2B;AACjE,QAAO,kBAAkB,QAAQ,KAAK;;;;;;;;;AAUxC,eAAsB,0BACpB,YACA,SACkD;CAClD,MAAM,aAAa,MAAM,+BAA+B,YAAY,QAAQ;CAC5E,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,WAAW,yBAAyB,SAAS,QAAQ,aAAa,CAAC,SAAS,EAAE;AACvF,QAAM,YAAY,QAAQ;AAC1B,UAAQ,KAAK,QAAQ;;AAEvB,QAAO;EAAE,QAAQ,WAAW;EAAQ;EAAS"}
|
|
1
|
+
{"version":3,"file":"review-agent-DRWVU8fP.js","names":["execAsync","promisify","exec","execAsync","execAsync","promisify","exec","join","join","loadYamlConfig","dirname","promisify","exec","join","getAgentStateFileSync","loadYamlConfig"],"sources":["../../src/lib/cloister/stub-ui-patterns.ts","../../src/lib/cloister/lint-stub-ui.ts","../../src/lib/cloister/coderabbit-ingestion.ts","../../src/lib/cloister/review-context.ts","../../src/lib/agents/removal.ts","../../src/lib/cloister/review-resume-decision.ts","../../src/lib/cloister/review-convoy.ts","../../src/lib/cloister/review-agent.ts"],"sourcesContent":["/**\n * Stub UI pattern catalog (PAN-1500 / PAN-1454 pattern 4).\n *\n * This module defines a deterministic, regex-based catalog of code shapes that\n * strongly suggest a PR is merging a UI stub: a new tab, mode, or routed view\n * whose data layer is faked, whose handlers no-op, or whose copy apologizes for\n * missing functionality. The catalog is best-effort, not exhaustive — extend it\n * when new stub shapes escape post-merge audits.\n *\n * The globs and patterns are consumed by `scanStubUi()` in\n * `src/lib/cloister/lint-stub-ui.ts`, which restricts inspection to added (+)\n * lines in frontend UI affordance files. Reviewers see the emitted findings on\n * the review context manifest and must block unless the diff also shows one of\n * the three valid mitigations: a feature flag guarding the affordance off,\n * removal of the affordance from the user-facing surface, or a non-stub\n * implementation calling real data.\n */\n\nexport type StubUiSeverity = 'block' | 'advisory';\n\nexport interface StubUiFinding {\n patternId: StubUiPatternId;\n patternLabel: string;\n filePath: string;\n lineNumber: number;\n addedLine: string;\n severity: StubUiSeverity;\n}\n\nexport interface StubUiPattern {\n id: StubUiPatternId;\n label: string;\n regex: RegExp;\n severity: StubUiSeverity;\n}\n\n/**\n * Micromatch-compatible globs naming the file types that can introduce\n * user-facing UI affordances. The scanner applies additional status logic:\n * - Added (A) files under `src/dashboard/frontend/src/components/` are in scope.\n * - Modified (M) files are in scope only when they match the `*Tab.tsx`,\n * `*Mode.tsx`, or `*View.tsx` suffix globs, because modifications to generic\n * components are unlikely to introduce brand-new affordances.\n */\nexport const STUB_UI_FILE_GLOBS = [\n 'src/dashboard/frontend/src/components/**/*Tab.tsx',\n 'src/dashboard/frontend/src/components/**/*Mode.tsx',\n 'src/dashboard/frontend/src/components/**/*View.tsx',\n 'src/dashboard/frontend/src/components/**/*.tsx',\n] as const;\n\n/**\n * Stable pattern identifiers. Downstream beads (scanner, prompt) can reference\n * these by name via `typeof STUB_UI_PATTERNS[number]['id']`.\n */\nexport type StubUiPatternId =\n | 'coming-soon-copy'\n | 'empty-array-return'\n | 'null-return'\n | 'use-state-false'\n | 'new-tab-or-route'\n | 'segmented-control-noop'\n | 'noop-handler';\n\n/**\n * Regex catalog of stub shapes. Each pattern is applied to individual added\n * lines, so patterns must not rely on multi-line context.\n */\nexport const STUB_UI_PATTERNS: StubUiPattern[] = [\n {\n id: 'coming-soon-copy',\n label: 'Copy suggests the feature is not implemented (\"coming soon\" / \"not yet implemented\")',\n // Ignore lines that begin with a // comment so TODO/notes don't drown out\n // real user-facing copy. JSX text and quoted strings are still caught.\n regex: /^(?!\\s*\\/\\/).*?\\b(?:[Cc]oming\\s+soon|[Nn]ot\\s+yet\\s+implemented)\\b/,\n severity: 'block',\n },\n {\n id: 'empty-array-return',\n label: 'Hook/function returns an empty array',\n regex: /^\\s*return\\s*\\[\\s*\\]\\s*;/,\n severity: 'block',\n },\n {\n id: 'null-return',\n label: 'Hook/function returns null',\n regex: /^\\s*return\\s+null\\s*;/,\n severity: 'block',\n },\n {\n id: 'use-state-false',\n label: 'Tab/mode state initialized to false with no other writer suspected',\n regex: /\\buseState\\s*\\(\\s*false\\s*\\)/,\n severity: 'advisory',\n },\n {\n id: 'new-tab-or-route',\n label: 'New Tab, TabPanel, or Route JSX entry added',\n regex: /<(?:Tab|TabPanel|Route)\\b/,\n severity: 'advisory',\n },\n {\n id: 'segmented-control-noop',\n label: 'Segmented-control or mode onChange handler is a no-op arrow function',\n // Matches both JSX prop form `onChange={() => {}}` and object form\n // `onChange: () => {},`. The outer braces are optional.\n regex: /onChange\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n {\n id: 'noop-handler',\n label: 'Explicit onClick/onSelect/onSubmit handler is a no-op arrow function',\n // Matches JSX prop form `onClick={() => {}}` and object form\n // `onClick: () => {},`. Outer braces are optional.\n regex: /on(?:Click|Select|Submit)\\s*[:=]\\s*\\{?\\s*\\(\\s*\\)\\s*=>\\s*(?:\\{\\s*\\}|null|undefined)\\s*\\}?/,\n severity: 'block',\n },\n];\n\n/**\n * Convert a micromatch-style glob (supporting `*` and `**`) to a RegExp.\n * This is intentionally minimal: it covers the globs exported above and avoids\n * adding a dependency on `micromatch` for a small, stable set of patterns.\n */\nfunction globToRegex(glob: string): RegExp {\n let pattern = '';\n let i = 0;\n while (i < glob.length) {\n const c = glob[i];\n if (c === '*' && glob[i + 1] === '*') {\n if (glob[i + 2] === '/') {\n pattern += '(?:.*/)?';\n i += 3;\n continue;\n }\n pattern += '.*';\n i += 2;\n continue;\n }\n if (c === '*') {\n pattern += '[^/]*';\n i += 1;\n continue;\n }\n if (/[.+^${}()|[\\]\\\\]/.test(c)) {\n pattern += `\\\\${c}`;\n i += 1;\n continue;\n }\n pattern += c;\n i += 1;\n }\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * Test whether a repository-relative file path matches any of the stub-UI\n * file globs. This is the public entry point used by the scanner.\n */\nexport function matchesStubUiFileGlob(filePath: string): boolean {\n for (const glob of STUB_UI_FILE_GLOBS) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n\n/**\n * Status-aware scope check. Added component files are in scope because they may\n * introduce brand-new affordances; modified files are inspected only when their\n * basename suggests they are UI affordance files (*Tab/*Mode/*View).\n */\nexport function isStubUiFileInScope(filePath: string, status: 'A' | 'M' | string): boolean {\n if (status === 'A') {\n return matchesStubUiFileGlob(filePath);\n }\n // Modified files: only the explicit affordance suffixes.\n const affordanceGlobs = STUB_UI_FILE_GLOBS.slice(0, 3);\n for (const glob of affordanceGlobs) {\n if (globToRegex(glob).test(filePath)) return true;\n }\n return false;\n}\n","/**\n * Stub-UI scanner (PAN-1500).\n *\n * Reads the PR diff for a workspace and emits structured findings for added\n * (+) lines that match the STUB_UI_PATTERNS catalog. The scanner restricts\n * inspection to frontend UI affordance files and degrades gracefully: any\n * internal failure returns an empty array and logs a non-fatal warning.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport {\n STUB_UI_PATTERNS,\n isStubUiFileInScope,\n type StubUiFinding,\n} from './stub-ui-patterns.js';\n\nexport type { StubUiFinding };\n\nconst execAsync = promisify(exec);\n\ninterface ChangedFileEntry {\n path: string;\n status: string;\n}\n\nasync function listChangedFiles(workspace: string, diffBase: string): Promise<ChangedFileEntry[]> {\n const { stdout } = await execAsync(\n `git diff --name-status \"${diffBase}\"...HEAD`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n const entries: ChangedFileEntry[] = [];\n for (const line of stdout.split('\\n')) {\n if (!line.trim()) continue;\n const parts = line.split('\\t');\n const status = parts[0]?.[0] ?? 'M';\n const path = parts[parts.length - 1] ?? '';\n if (!path) continue;\n entries.push({ path, status });\n }\n return entries;\n}\n\n/**\n * Parse a unified-diff hunk header and return the destination starting line.\n * Headers look like `@@ -3,5 +8,9 @@` or `@@ -1 +1 @@`.\n */\nfunction parseHunkHeader(line: string): number | null {\n const match = line.match(/@@\\s+-\\d+(?:,\\d+)?\\s+\\+(\\d+)(?:,\\d+)?\\s+@@/);\n if (!match) return null;\n return parseInt(match[1], 10);\n}\n\n/**\n * Read the diff for a single file and yield every added line with its\n * destination line number. Uses `-U0` so there are no context lines to skip.\n */\nasync function* addedLinesForFile(\n workspace: string,\n diffBase: string,\n filePath: string,\n): AsyncGenerator<{ lineNumber: number; content: string }> {\n const { stdout } = await execAsync(\n `git diff -U0 \"${diffBase}\"...HEAD -- \"${filePath}\"`,\n { cwd: workspace, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n\n let currentLine = 0;\n\n for (const rawLine of stdout.split('\\n')) {\n if (rawLine.startsWith('@@')) {\n const parsed = parseHunkHeader(rawLine);\n if (parsed !== null) {\n currentLine = parsed;\n }\n continue;\n }\n\n if (rawLine.startsWith('+++')) continue;\n if (rawLine.startsWith('---')) continue;\n\n if (rawLine.startsWith('+')) {\n const content = rawLine.slice(1);\n yield { lineNumber: currentLine, content };\n currentLine += 1;\n continue;\n }\n\n if (rawLine.startsWith(' ')) {\n currentLine += 1;\n continue;\n }\n\n // Deleted lines do not advance the destination line counter.\n }\n}\n\n/**\n * Scan the PR diff in `workspace` against `diffBase` for stub-UI patterns.\n *\n * Returns a list of findings; never throws. Internal failures (git errors,\n * malformed hunks, missing workspace) log a warning and return [].\n */\nexport async function scanStubUi(workspace: string, diffBase: string): Promise<StubUiFinding[]> {\n try {\n const changedFiles = await listChangedFiles(workspace, diffBase);\n const findings: StubUiFinding[] = [];\n\n for (const { path, status } of changedFiles) {\n if (!isStubUiFileInScope(path, status)) continue;\n\n for await (const { lineNumber, content } of addedLinesForFile(workspace, diffBase, path)) {\n for (const pattern of STUB_UI_PATTERNS) {\n if (pattern.regex.test(content)) {\n findings.push({\n patternId: pattern.id,\n patternLabel: pattern.label,\n filePath: path,\n lineNumber,\n addedLine: content.trimEnd(),\n severity: pattern.severity,\n });\n }\n }\n }\n }\n\n return findings;\n } catch (err) {\n console.warn(\n `[scanStubUi] Failed to scan workspace ${workspace} against ${diffBase}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return [];\n }\n}\n","/**\n * CodeRabbit review ingestion (PAN-2374).\n *\n * Quota invariant: CodeRabbit reviews PRs only sometimes. An empty finding\n * array means any of: no findings, quota miss, CodeRabbit absent, or fetch\n * error. Callers MUST treat all four identically — this module is advisory\n * and must never gate anything.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { lookupPullRequestNumberForBranch } from '../github-pr-lookup.js';\nimport { postPrComment } from './review-verdict-feedback.js';\n\nconst execAsync = promisify(exec);\n\nconst CODERABBIT_BOT_LOGIN = 'coderabbitai[bot]';\n\nexport interface CodeRabbitFinding {\n path?: string;\n line?: number;\n body: string;\n url?: string;\n}\n\n/**\n * Canonical reply template when a CodeRabbit finding has been addressed.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'addressed', finding.body)`\n */\nexport const CODERABBIT_REPLY_ADDRESSED = (finding: string): string =>\n `Thanks for the finding — addressed: ${finding}`;\n\n/**\n * Canonical reply template when a CodeRabbit finding is rejected.\n *\n * Calling convention: `postCodeRabbitReply(prUrl, 'rejected', finding.body, rationale)`\n */\nexport const CODERABBIT_REPLY_REJECTED = (finding: string, rationale: string): string =>\n `Thanks for the finding — after review we're not applying this: ${finding}. Rationale: ${rationale}`;\n\n/**\n * Reply to a CodeRabbit finding on the PR thread.\n *\n * @param prUrl - The GitHub PR URL.\n * @param disposition - Whether the finding was addressed or rejected.\n * @param finding - The finding text (usually `finding.body`).\n * @param rationale - Required when disposition is 'rejected'; explains why.\n * @returns true if the comment was posted, false on any error (never throws).\n *\n * This is a best-effort reply path and MUST NOT read or write review status.\n */\nexport async function postCodeRabbitReply(\n prUrl: string,\n disposition: 'addressed' | 'rejected',\n finding: string,\n rationale?: string,\n): Promise<boolean> {\n try {\n const body =\n disposition === 'addressed'\n ? CODERABBIT_REPLY_ADDRESSED(finding)\n : CODERABBIT_REPLY_REJECTED(finding, rationale ?? 'no rationale provided');\n return await postPrComment(prUrl, body);\n } catch {\n return false;\n }\n}\n\nasync function resolveOwnerRepo(workspace: string): Promise<{ owner: string; repo: string } | null> {\n try {\n const { stdout } = await execAsync('gh repo view --json owner,name', {\n cwd: workspace,\n encoding: 'utf-8',\n timeout: 15000,\n });\n const parsed = JSON.parse(stdout) as { owner?: { login?: string }; name?: string };\n const owner = parsed.owner?.login;\n const repo = parsed.name;\n if (!owner || !repo) return null;\n return { owner, repo };\n } catch {\n return null;\n }\n}\n\nasync function fetchFindingsFromReviews(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/reviews?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n state?: string;\n html_url?: string;\n }>;\n return arr\n .filter(r => r.user?.login === CODERABBIT_BOT_LOGIN && r.state !== 'DISMISSED')\n .map(r => ({\n body: r.body ?? '',\n url: r.html_url,\n }));\n}\n\nasync function fetchFindingsFromComments(\n owner: string,\n repo: string,\n prNumber: number,\n): Promise<CodeRabbitFinding[]> {\n const { stdout } = await execAsync(\n `gh api \"repos/${owner}/${repo}/pulls/${prNumber}/comments?per_page=100\"`,\n { encoding: 'utf-8', timeout: 15000, maxBuffer: 8 * 1024 * 1024 },\n );\n const arr = JSON.parse(stdout) as Array<{\n user?: { login?: string } | null;\n body?: string | null;\n html_url?: string;\n path?: string;\n line?: number | null;\n }>;\n return arr\n .filter(c => c.user?.login === CODERABBIT_BOT_LOGIN)\n .map(c => ({\n path: c.path,\n line: typeof c.line === 'number' ? c.line : undefined,\n body: c.body ?? '',\n url: c.html_url,\n }));\n}\n\nexport async function fetchCodeRabbitFindings(opts: {\n workspace: string;\n branch: string;\n}): Promise<CodeRabbitFinding[]> {\n try {\n const ownerRepo = await resolveOwnerRepo(opts.workspace);\n if (!ownerRepo) return [];\n const { owner, repo } = ownerRepo;\n\n const prNumber = await lookupPullRequestNumberForBranch(owner, repo, opts.branch);\n if (!prNumber) return [];\n\n const [reviewFindings, commentFindings] = await Promise.all([\n fetchFindingsFromReviews(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n fetchFindingsFromComments(owner, repo, prNumber).catch(() => [] as CodeRabbitFinding[]),\n ]);\n\n return [...reviewFindings, ...commentFindings];\n } catch (err) {\n console.warn(\n `[coderabbit-ingestion] fetch failed — skipping: ${err instanceof Error ? err.message : String(err)}`,\n );\n return [];\n }\n}\n","/**\n * Review Context Manifest Builder (PAN-1059)\n *\n * Builds a shared `.pan/review/<runId>/context.json` before spawning\n * any sub-reviewers. All four convoy agents read this file instead of\n * independently running `git diff` and reading every changed file,\n * eliminating ~4× redundant I/O and the token cost that goes with it.\n */\n\nimport { exec } from 'child_process';\nimport { existsSync } from 'fs';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { findPlanSync, readPlan } from '../xbrief/io.js';\nimport { scanStubUi, type StubUiFinding } from './lint-stub-ui.js';\nimport { fetchCodeRabbitFindings, type CodeRabbitFinding } from './coderabbit-ingestion.js';\nimport { findXBriefByIssueSync } from '../xbrief/lifecycle-io.js';\nimport { getDevrootPathSync } from '../config.js';\nimport { FsError } from '../errors.js';\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\n\nconst execAsync = promisify(exec);\n\nexport const REVIEW_LARGE_CHANGESET_FILES = 25;\nexport const REVIEW_LARGE_CHANGESET_LINES = 1500;\n\n// The manifest no longer embeds raw diff text (PAN-1125).\n// Reviewers receive a concise inline summary in their spawn prompt and read\n// individual files on demand. The manifest carries metadata only: stat,\n// changedFiles, acceptanceCriteria, and policyNotes.\n\nconst RISK_HIGH = 5;\nconst RISK_MED = 3;\nconst RISK_LOW = 1;\n\n// Patterns that raise a file's risk score\nconst HIGH_RISK_PATTERNS = [\n /auth/i, /password/i, /token/i, /secret/i, /crypt/i,\n /permission/i, /privilege/i, /admin/i, /acl/i, /rbac/i,\n /payment/i, /billing/i, /stripe/i,\n /sql/i, /query/i, /inject/i,\n /exec\\b/i, /spawn/i, /shell/i, /eval\\b/i,\n];\n\nconst MED_RISK_PATTERNS = [\n /config/i, /env/i, /setting/i, /migration/i,\n /middleware/i, /route/i, /api/i,\n];\n\nconst LOW_RISK_PATTERNS = [\n /test/i, /spec/i, /mock/i, /fixture/i, /stub/i,\n /\\.md$/, /\\.txt$/, /\\.json$/, /README/i,\n];\n\nexport interface ChangedFile {\n path: string;\n status: 'M' | 'A' | 'D' | 'R' | 'C' | 'U';\n additions: number;\n deletions: number;\n riskScore: number;\n}\n\nexport interface ReviewItemTrace {\n itemId: string;\n title: string;\n traces: string[];\n}\n\n/** Per-repo git context for polyrepo workspaces (PAN-2948). Additive — absent\n * or single-entry for monorepo workspaces, so existing consumers are untouched. */\nexport interface ReviewRepoContext {\n repoKey: string;\n branch: string;\n headSha: string;\n diffBase: string;\n fileCount: number;\n}\n\nexport interface ReviewContextManifest {\n runId: string;\n issueId: string;\n generatedAt: string;\n branch: string;\n headSha: string;\n /** Present when the workspace is polyrepo: one entry per sub-repo reviewed. */\n repos?: ReviewRepoContext[];\n diff: {\n stat: string;\n truncated: boolean;\n };\n changedFiles: ChangedFile[];\n largeChangeset: {\n fileCount: number;\n changedLines: number;\n isLarge: boolean;\n };\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n policyNotes: string[];\n stubUiFindings: StubUiFinding[];\n codeRabbitFindings: CodeRabbitFinding[];\n manifestPath: string;\n}\n\nfunction riskScore(filePath: string): number {\n if (LOW_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_LOW;\n if (HIGH_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_HIGH;\n if (MED_RISK_PATTERNS.some(p => p.test(filePath))) return RISK_MED;\n return 2; // default: between low and medium\n}\n\nasync function getHeadSha(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git rev-parse HEAD', { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return 'unknown';\n }\n}\n\nasync function getCurrentBranch(cwd: string): Promise<string> {\n try {\n const { stdout } = await execAsync('git branch --show-current', { cwd, encoding: 'utf-8' });\n return stdout.trim() || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport async function getDiffBase(cwd: string, targetBranch = 'main'): Promise<string> {\n try {\n const { stdout } = await execAsync(`git merge-base origin/${targetBranch} HEAD`, { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n try {\n const { stdout } = await execAsync(`git merge-base ${targetBranch} HEAD`, { cwd, encoding: 'utf-8' });\n return stdout.trim();\n } catch {\n return targetBranch;\n }\n }\n}\n\nexport async function getChangedFiles(cwd: string, base: string, pathPrefix = ''): Promise<ChangedFile[]> {\n // --name-status gives us the status letter + path\n let nameStatus = '';\n try {\n const { stdout } = await execAsync(\n `git diff --name-status \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n nameStatus = stdout;\n } catch {\n return [];\n }\n\n // --numstat gives us additions + deletions per file\n const numstatMap = new Map<string, { additions: number; deletions: number }>();\n try {\n const { stdout } = await execAsync(\n `git diff --numstat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n for (const line of stdout.split('\\n')) {\n const parts = line.split('\\t');\n if (parts.length >= 3) {\n const additions = parseInt(parts[0], 10) || 0;\n const deletions = parseInt(parts[1], 10) || 0;\n // Binary files show '-'; treat as 0\n numstatMap.set(parts[2], { additions, deletions });\n }\n }\n } catch {\n // numstat failure is non-fatal; additions/deletions default to 0\n }\n\n const files: ChangedFile[] = [];\n for (const line of nameStatus.split('\\n')) {\n if (!line.trim()) continue;\n const [statusChar, ...pathParts] = line.split('\\t');\n const path = pathParts[pathParts.length - 1] ?? '';\n if (!path) continue;\n\n const statusLetter = (statusChar?.[0] ?? 'M') as ChangedFile['status'];\n const counts = numstatMap.get(path) ?? { additions: 0, deletions: 0 };\n const prefixedPath = `${pathPrefix}${path}`;\n\n files.push({\n path: prefixedPath,\n status: statusLetter,\n additions: counts.additions,\n deletions: counts.deletions,\n riskScore: riskScore(prefixedPath),\n });\n }\n\n // Sort descending by risk score so reviewers see hotspots first\n return files.sort((a, b) => b.riskScore - a.riskScore);\n}\n\nexport async function getDiffStat(cwd: string, base: string): Promise<{ stat: string; truncated: boolean }> {\n let stat = '';\n\n try {\n const { stdout } = await execAsync(\n `git diff --stat \"${base}\"...HEAD`,\n { cwd, encoding: 'utf-8', maxBuffer: 4 * 1024 * 1024 },\n );\n stat = stdout.trim() || 'No changes';\n } catch {\n stat = 'Unable to compute diff stat';\n }\n\n // We no longer embed raw diff text in the manifest (PAN-1125).\n // Reviewers read individual files via Read/Grep as needed.\n return { stat, truncated: true };\n}\n\ninterface PlanReviewRequirements {\n acceptanceCriteria: string[];\n nonGoals: string[];\n traces: ReviewItemTrace[];\n}\n\nasync function extractPlanReviewRequirements(workspace: string, issueId: string): Promise<PlanReviewRequirements> {\n // Try workspace-local spec first\n const planPath = findPlanSync(workspace);\n if (planPath) {\n try {\n const doc = await Effect.runPromise(readPlan(planPath));\n return {\n acceptanceCriteria: flattenAC(doc),\n nonGoals: flattenNonGoals(doc),\n traces: flattenTraces(doc),\n };\n } catch {\n // Fall through to lifecycle lookup\n }\n }\n\n // Try project-root lifecycle directories\n try {\n const projectRoot = getDevrootPathSync();\n if (!projectRoot) return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n const found = findXBriefByIssueSync(projectRoot, issueId);\n if (found) {\n return {\n acceptanceCriteria: flattenAC(found.document),\n nonGoals: flattenNonGoals(found.document),\n traces: flattenTraces(found.document),\n };\n }\n } catch {\n // Non-fatal\n }\n\n return { acceptanceCriteria: [], nonGoals: [], traces: [] };\n}\n\ninterface PanItem {\n id?: string;\n title?: string;\n acceptanceCriteria?: string[];\n subItems?: Array<{ title?: string; description?: string }>;\n metadata?: Record<string, unknown>;\n}\n\nfunction flattenAC(doc: { plan?: { items?: PanItem[] } }): string[] {\n const acs: string[] = [];\n for (const item of doc?.plan?.items ?? []) {\n // Planning agent writes acceptanceCriteria directly on items\n if (Array.isArray(item.acceptanceCriteria)) {\n acs.push(...item.acceptanceCriteria);\n }\n // Standard xBRIEF v0.5 sub-items\n for (const sub of item.subItems ?? []) {\n const text = sub.title ?? sub.description ?? '';\n if (text) acs.push(text);\n }\n }\n return acs;\n}\n\nfunction flattenNonGoals(doc: { plan?: { narratives?: Record<string, string | undefined> } }): string[] {\n const raw = doc.plan?.narratives?.NonGoals?.trim();\n if (!raw || raw.toLowerCase() === 'none') return [];\n return raw\n .split('\\n')\n .map(line => line.trim().replace(/^[-*]\\s*/, '').trim())\n .filter(Boolean);\n}\n\nfunction flattenTraces(doc: { plan?: { items?: PanItem[] } }): ReviewItemTrace[] {\n const traces: ReviewItemTrace[] = [];\n for (const item of doc.plan?.items ?? []) {\n const raw = item.metadata?.traces;\n if (!Array.isArray(raw)) continue;\n const itemTraces = raw.filter((trace): trace is string => typeof trace === 'string' && trace.trim().length > 0);\n if (itemTraces.length === 0) continue;\n traces.push({\n itemId: item.id ?? '<unknown>',\n title: item.title ?? '',\n traces: itemTraces,\n });\n }\n return traces;\n}\n\nasync function readPolicyNotes(workspace: string): Promise<string[]> {\n const notes: string[] = [];\n\n // Pull CRITICAL rules from CLAUDE.md\n const claudeMdPath = join(workspace, 'CLAUDE.md');\n if (existsSync(claudeMdPath)) {\n try {\n const content = await readFile(claudeMdPath, 'utf-8');\n const criticalLines = content\n .split('\\n')\n .filter(l => l.startsWith('## CRITICAL') || l.startsWith('**NEVER') || l.startsWith('**CRITICAL'))\n .map(l => l.replace(/^#+\\s*/, '').replace(/^\\*+/, '').trim())\n .filter(Boolean);\n notes.push(...criticalLines.slice(0, 10));\n } catch {\n // Non-fatal\n }\n }\n\n return notes;\n}\n\nexport interface BuildReviewContextOpts {\n runId: string;\n issueId: string;\n workspace: string;\n branch?: string;\n}\n\n/**\n * Build and persist the review context manifest for a review run.\n *\n * Returns the manifest object and its path on disk.\n * Throws if the workspace directory does not exist.\n */\n/**\n * Format a concise Tier-1 inline summary from manifest fields.\n *\n * This summary is embedded directly into each convoy reviewer's spawn prompt\n * so they receive scope, priority, and constraints without reading a large\n * manifest file first (PAN-1125).\n */\nexport function formatTier1Summary(\n manifest: Pick<\n ReviewContextManifest,\n | 'issueId' | 'branch' | 'headSha' | 'repos' | 'changedFiles' | 'acceptanceCriteria' | 'nonGoals' | 'traces' | 'policyNotes' | 'stubUiFindings' | 'codeRabbitFindings' | 'diff' | 'largeChangeset'\n >,\n): string {\n const lines: string[] = [];\n\n lines.push(`Issue: ${manifest.issueId}`);\n lines.push(`Branch: ${manifest.branch}`);\n lines.push(`Head: ${manifest.headSha}`);\n\n if (manifest.repos && manifest.repos.length > 1) {\n lines.push('Polyrepo workspace — changed-file paths are prefixed with the sub-repo name:');\n for (const repo of manifest.repos) {\n lines.push(` ${repo.repoKey}: ${repo.branch} @ ${repo.headSha.slice(0, 8)} (${repo.fileCount} files changed)`);\n }\n }\n\n const highRisk = manifest.changedFiles.filter((f) => f.riskScore >= 5);\n const medRisk = manifest.changedFiles.filter((f) => f.riskScore >= 3 && f.riskScore < 5);\n const lowRisk = manifest.changedFiles.filter((f) => f.riskScore < 3);\n lines.push(\n `Files changed: ${manifest.changedFiles.length} (${highRisk.length} high-risk, ${medRisk.length} medium, ${lowRisk.length} low)`,\n );\n\n if (manifest.changedFiles.length > 0) {\n lines.push('');\n lines.push('Changed files (risk-ranked):');\n for (const f of manifest.changedFiles.slice(0, 15)) {\n const riskLabel = f.riskScore >= 5 ? 'HIGH' : f.riskScore >= 3 ? 'MED' : 'LOW';\n lines.push(` ${f.path.padEnd(40)} (+${f.additions}/-${f.deletions}) risk: ${riskLabel}`);\n }\n if (manifest.changedFiles.length > 15) {\n lines.push(` ... and ${manifest.changedFiles.length - 15} more (see manifest)`);\n }\n }\n\n if (manifest.largeChangeset?.isLarge) {\n lines.push('');\n lines.push(`LARGE CHANGESET: ${manifest.largeChangeset.fileCount} files, ${manifest.largeChangeset.changedLines} changed lines.`);\n lines.push('Read the highest-risk files first, then medium-risk. Read only changed regions, not whole files.');\n lines.push('If you approach the context limit, write a report on what you reviewed and flag any uncovered high-risk files as a BLOCKING coverage gap.');\n lines.push('Do not read everything and overflow; do not approve changes you have not reviewed.');\n }\n\n if (manifest.acceptanceCriteria.length > 0) {\n lines.push('');\n lines.push('Acceptance criteria:');\n for (const [i, ac] of manifest.acceptanceCriteria.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${ac}`);\n }\n if (manifest.acceptanceCriteria.length > 7) {\n lines.push(` ... and ${manifest.acceptanceCriteria.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.nonGoals.length > 0) {\n lines.push('');\n lines.push('NonGoals / must-not constraints:');\n for (const [i, nonGoal] of manifest.nonGoals.slice(0, 7).entries()) {\n lines.push(` ${i + 1}. ${nonGoal}`);\n }\n if (manifest.nonGoals.length > 7) {\n lines.push(` ... and ${manifest.nonGoals.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.traces.length > 0) {\n lines.push('');\n lines.push('Requirement traces:');\n for (const trace of manifest.traces.slice(0, 7)) {\n lines.push(` ${trace.itemId}: ${trace.traces.join(', ')}`);\n }\n if (manifest.traces.length > 7) {\n lines.push(` ... and ${manifest.traces.length - 7} more (see manifest)`);\n }\n }\n\n if (manifest.policyNotes.length > 0) {\n lines.push('');\n lines.push('Policy notes:');\n for (const note of manifest.policyNotes.slice(0, 5)) {\n lines.push(` - ${note}`);\n }\n if (manifest.policyNotes.length > 5) {\n lines.push(` ... and ${manifest.policyNotes.length - 5} more (see manifest)`);\n }\n }\n\n if (manifest.stubUiFindings.length > 0) {\n lines.push('');\n lines.push('Stub UI findings (BLOCKING if unmitigated):');\n for (const finding of manifest.stubUiFindings.slice(0, 10)) {\n lines.push(` - ${finding.patternLabel} @ ${finding.filePath}:${finding.lineNumber}`);\n }\n if (manifest.stubUiFindings.length > 10) {\n lines.push(` ... and ${manifest.stubUiFindings.length - 10} more (see manifest)`);\n }\n }\n\n if (manifest.codeRabbitFindings.length > 0) {\n lines.push('');\n lines.push('CodeRabbit findings (ADVISORY — non-gating; context, not acceptance criteria):');\n for (const finding of manifest.codeRabbitFindings.slice(0, 10)) {\n const location = finding.path ? `${finding.path}${typeof finding.line === 'number' ? `:${finding.line}` : ''}` : 'general';\n const bodyPreview = finding.body.slice(0, 200);\n lines.push(` - ${location} ${bodyPreview}${finding.body.length > 200 ? '...' : ''}`);\n }\n if (manifest.codeRabbitFindings.length > 10) {\n lines.push(` ... and ${manifest.codeRabbitFindings.length - 10} more (see manifest)`);\n }\n }\n\n lines.push('');\n lines.push(`Diff stat: ${manifest.diff.stat}`);\n\n return lines.join('\\n');\n}export async function buildReviewContextPromise(opts: BuildReviewContextOpts): Promise<ReviewContextManifest> {\n const { runId, issueId, workspace } = opts;\n\n if (!existsSync(workspace)) {\n throw new Error(`Workspace directory does not exist: ${workspace}`);\n }\n\n // PAN-2948: a polyrepo workspace root is a one-commit wrapper repo whose\n // .gitignore excludes the code sub-repos — diffing it always yields an empty\n // manifest. Resolve the actual repo roots and build per-repo, aggregating\n // with repo-prefixed paths. Monorepo resolves to one root at the workspace.\n const roots = resolveWorkspaceRepoRootsSync(issueId, workspace);\n const isPolyrepo = roots.some(root => root.isPolyrepo);\n\n const perRepo = await Promise.all(roots.map(async root => {\n const prefix = isPolyrepo ? `${root.repoKey}/` : '';\n const [headSha, branch, diffBase] = await Promise.all([\n getHeadSha(root.dir),\n getCurrentBranch(root.dir),\n getDiffBase(root.dir, root.targetBranch),\n ]);\n const [changedFiles, diffStat, stubUiFindings] = await Promise.all([\n getChangedFiles(root.dir, diffBase, prefix),\n getDiffStat(root.dir, diffBase),\n scanStubUi(root.dir, diffBase).catch((err) => {\n console.warn(`[buildReviewContext] scanStubUi failed for ${root.repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n return [] as StubUiFinding[];\n }),\n ]);\n return {\n root,\n headSha,\n branch,\n diffBase,\n changedFiles,\n diffStat,\n stubUiFindings: prefix\n ? stubUiFindings.map(finding => ({ ...finding, filePath: `${prefix}${finding.filePath}` }))\n : stubUiFindings,\n };\n }));\n\n // Primary repo drives the top-level branch/headSha: the first root with\n // changes (for MYN that's fe), falling back to the first root.\n const primary = perRepo.find(repo => repo.changedFiles.length > 0) ?? perRepo[0];\n const headSha = primary.headSha;\n // Polyrepo: opts.branch describes the wrapper (\"master\") — the primary\n // sub-repo's branch is the one reviewers should see.\n const currentBranch = isPolyrepo ? primary.branch : (opts.branch ?? primary.branch);\n const changedFiles = perRepo\n .flatMap(repo => repo.changedFiles)\n .sort((a, b) => b.riskScore - a.riskScore);\n const diff = isPolyrepo\n ? {\n stat: perRepo\n .filter(repo => repo.changedFiles.length > 0)\n .map(repo => `── ${repo.root.repoKey} ──\\n${repo.diffStat.stat}`)\n .join('\\n') || 'No changes',\n truncated: true,\n }\n : primary.diffStat;\n const stubUiFindings = perRepo.flatMap(repo => repo.stubUiFindings);\n const repoContexts: ReviewRepoContext[] = perRepo.map(repo => ({\n repoKey: repo.root.repoKey,\n branch: repo.branch,\n headSha: repo.headSha,\n diffBase: repo.diffBase,\n fileCount: repo.changedFiles.length,\n }));\n\n const [planRequirements, policyNotes, codeRabbitFindings] = await Promise.all([\n extractPlanReviewRequirements(workspace, issueId),\n readPolicyNotes(workspace),\n fetchCodeRabbitFindings({ workspace, branch: currentBranch }).catch((err) => {\n console.warn(`[buildReviewContext] CodeRabbit ingestion failed: ${err instanceof Error ? err.message : String(err)}`);\n return [] as CodeRabbitFinding[];\n }),\n ]);\n\n const fileCount = changedFiles.length;\n const changedLines = changedFiles.reduce((sum, f) => sum + f.additions + f.deletions, 0);\n const largeChangeset = {\n fileCount,\n changedLines,\n isLarge: fileCount > REVIEW_LARGE_CHANGESET_FILES || changedLines > REVIEW_LARGE_CHANGESET_LINES,\n };\n\n const manifestDir = join(workspace, PAN_DIRNAME, 'review', runId);\n await mkdir(manifestDir, { recursive: true });\n const manifestPath = join(manifestDir, 'context.json');\n\n const manifest: ReviewContextManifest = {\n runId,\n issueId,\n generatedAt: new Date().toISOString(),\n branch: currentBranch,\n headSha,\n ...(isPolyrepo ? { repos: repoContexts } : {}),\n diff,\n changedFiles,\n largeChangeset,\n acceptanceCriteria: planRequirements.acceptanceCriteria,\n nonGoals: planRequirements.nonGoals,\n traces: planRequirements.traces,\n policyNotes,\n stubUiFindings,\n codeRabbitFindings,\n manifestPath,\n };\n\n await writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');\n\n return manifest;\n}\n\n// ─── Effect variant (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link buildReviewContext}. Wraps the Promise-based\n * implementation in `Effect.tryPromise` so callers in Effect pipelines can\n * compose it with typed error channels. The git probes inside\n * {@link buildReviewContext} are best-effort — they fall back to sentinel\n * strings instead of failing — so the only error this Effect can surface is\n * the workspace-not-found {@link FsError}.\n */\nexport const buildReviewContext = (\n opts: BuildReviewContextOpts,\n): Effect.Effect<ReviewContextManifest, FsError> =>\n Effect.tryPromise({\n try: () => buildReviewContextPromise(opts),\n catch: (cause) =>\n new FsError({\n path: opts.workspace,\n operation: 'buildReviewContext',\n cause,\n }),\n });\n","import { join } from 'node:path';\n\nimport {\n markRetainedTranscripts,\n removeAgentStateDir,\n type RemoveAgentStateDirResult,\n} from './state-dir-removal.js';\nimport { getAgentStateSync } from './agent-state.js';\nimport {\n ensureAgentTombstoneSync,\n listAllAgentsSync,\n removeAgentRecordSync,\n type AgentTombstoneIdentity,\n} from '../overdeck/agents.js';\nimport { getOverdeckHome } from '../paths.js';\n\nfunction resolveTombstoneIdentity(agentId: string): AgentTombstoneIdentity | null {\n const source = listAllAgentsSync().find((agent) => agent.id === agentId) ?? getAgentStateSync(agentId);\n if (!source || source.id !== agentId) return null;\n const fields = [source.issueId, source.role, source.workspace, source.harness, source.model];\n if (fields.some((value) => typeof value !== 'string' || value.length === 0)) return null;\n return {\n id: agentId,\n issueId: source.issueId,\n role: source.role,\n workspace: source.workspace,\n harness: source.harness,\n model: source.model,\n } as AgentTombstoneIdentity;\n}\n\n/**\n * Remove runtime residue while retaining every JSONL transcript and its registry\n * linkage. The row becomes a stopped tombstone until configured retention expires\n * the final transcript; a fully removed directory permits immediate row removal.\n */\nexport async function removeAgent(agentId: string): Promise<RemoveAgentStateDirResult> {\n const identity = resolveTombstoneIdentity(agentId);\n if (identity === null) {\n throw new Error(`removeAgent: cannot preserve transcript linkage for ${agentId}`);\n }\n ensureAgentTombstoneSync(identity);\n\n const agentsDir = join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsDir, agentId);\n const result = await removeAgentStateDir(agentDir, agentsDir);\n if (result.removedDir) removeAgentRecordSync(agentId);\n else await markRetainedTranscripts(agentDir);\n return result;\n}\n","/**\n * PAN-1862 — the resume-vs-fresh decision for a review agent, as a PURE function so it is locked\n * by tests and shared by both quick review (the parent) and convoy (the sub-reviewers).\n *\n * RESUME (preserve the prior review's context — files read, findings raised — so a re-review\n * checks the fix instead of re-researching the whole diff) when there is a saved, resumable\n * session AND the requested harness/model is unchanged. Fresh-spawn ONLY when the harness or\n * model actually changed (it's a different agent then) or there is no resumable session. A change\n * is detected only when BOTH the requested and saved values are known and differ — an unspecified\n * request (use role default) or an unknown saved value never forces a wipe.\n */\nexport function reviewResumeDecision(params: {\n requestedModel?: string;\n requestedHarness?: string;\n savedModel?: string;\n savedHarness?: string;\n hasSavedState: boolean;\n hasSavedSession: boolean;\n}): boolean {\n const modelChanged =\n !!params.requestedModel && !!params.savedModel && params.requestedModel !== params.savedModel;\n const harnessChanged =\n !!params.requestedHarness && !!params.savedHarness && params.requestedHarness !== params.savedHarness;\n return params.hasSavedState && params.hasSavedSession && !modelChanged && !harnessChanged;\n}\n","/**\n * PAN-1862: the review CONVOY — sub-reviewer prompts and spawning, plus\n * missing-reviewer recovery.\n *\n * Split out of review-agent.ts (which keeps the review entry point, the\n * synthesis/self-review prompts, mode resolution, and session teardown).\n * This module must NEVER import review-agent.ts — review-agent imports (and\n * re-exports) from here, and a reverse edge would close a cycle.\n *\n * The four convoy sub-roles are security, correctness, performance, and\n * requirements (REVIEW_SUB_ROLES, review-monitor.ts); the synthesis parent is\n * the fifth review role. See docs/REVIEW-AGENT-ARCHITECTURE.md.\n */\n\nimport { mkdir, readFile, rm } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport { Effect } from 'effect';\nimport { listSessionNames } from '../tmux.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\nimport { formatTier1Summary, type ReviewContextManifest } from './review-context.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nimport { reviewResumeDecision } from './review-resume-decision.js';\nimport { readIssueRecordSync, resolveProjectForIssue } from '../pan-dir/record.js';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { AGENTS_DIR, packageRoot } from '../paths.js';\nimport type { RuntimeName } from '../runtimes/types.js';\n\n\n/**\n * Read a convoy sub-role prompt template from the overdeck install.\n *\n * Sub-role prompts are harness-agnostic templates owned by Overdeck. The\n * orchestrator reads them from its own install (packageRoot/roles/) and\n * inlines the body into the spawn message — they never live in the agent's\n * workspace, and they are never loaded via the Claude-specific `--agent` flag.\n * That keeps the same prompt content driving Claude Code, Pi, Codex, or any\n * future harness, and prevents a work agent from ambiently discovering its\n * own reviewer prompts in the workspace tree.\n */\nasync function readConvoySubRoleTemplate(subRole: string): Promise<string> {\n const path = join(packageRoot, 'roles', `review-${subRole}.md`);\n return readFile(path, 'utf-8');\n}\n\n\n\nconst REVIEWER_TIMEOUT_MS = 20 * 60 * 1000;\nconst REVIEWER_SPAWN_BACKOFF_DELAYS_MS = [100, 250, 500, 1_000, 2_000] as const;\n\nfunction reviewerAgentId(issueId: string, subRole: ReviewSubRole): string {\n return `agent-${issueId.toLowerCase()}-review-${subRole}`;\n}\n\nfunction isReviewerStateWriteContention(error: unknown, agentId: string): boolean {\n const message = error instanceof Error ? error.message : String(error);\n return message.includes(`agents-db:${agentId}`)\n && /database is locked|SQLITE_BUSY/i.test(message);\n}\n\nasync function reviewerSessionIsLive(agentId: string): Promise<boolean> {\n try {\n return (await Effect.runPromise(listSessionNames())).includes(agentId);\n } catch {\n return false;\n }\n}\n\nfunction reviewerAgentOutputPath(workspace: string, runId: string, subRole: ReviewSubRole): string {\n return join(workspace, PAN_DIRNAME, 'review', runId, `${subRole}.md`);\n}\n\n\n\nasync function buildConvoyPromptPromise(opts: {\n issueId: string;\n subRole: string;\n outputPath: string;\n synthesisAgentId: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): Promise<string> {\n const template = await readConvoySubRoleTemplate(opts.subRole);\n const prompt = [\n `REVIEW TASK for ${opts.issueId} — ${opts.subRole.toUpperCase()} REVIEW:`,\n '',\n `Issue: ${opts.issueId}`,\n `Sub-role: ${opts.subRole}`,\n '',\n 'Output file — write your full findings here when done:',\n ` ${opts.outputPath}`,\n '',\n opts.tier1Summary\n ? [\n 'Shared review context (read this first; do not run git diff yourself):',\n '─────────────────────────────────────────────────────────────',\n opts.tier1Summary,\n '─────────────────────────────────────────────────────────────',\n '',\n opts.contextManifestPath\n ? `Full manifest (read on demand for additional detail): ${opts.contextManifestPath}`\n : '',\n ].join('\\n')\n : opts.contextManifestPath\n ? [\n 'Context manifest (read this first; do not run git diff yourself):',\n ` ${opts.contextManifestPath}`,\n 'The manifest contains per-file risk ranking and acceptance criteria.',\n ].join('\\n')\n : 'No context manifest available. Write a blocked reviewer report explaining that the shared review context is missing.',\n '',\n '─────────────────────────────────────────────────────────────',\n 'REVIEW METHODOLOGY (inlined from roles/review-' + opts.subRole + '.md):',\n '─────────────────────────────────────────────────────────────',\n '',\n template.trim(),\n '',\n '─────────────────────────────────────────────────────────────',\n '',\n 'Write exactly one final report to the output file shown above, then stop',\n 'and wait. You do NOT need to signal synthesis or run any pan command —',\n 'when you finish your turn with the report written, Overdeck detects it',\n 'and signals the synthesis agent REVIEWER_READY automatically. Your only',\n 'job is to write the report file, then stop.',\n 'Only the output file is consumed by synthesis; your chat response is not the review report.',\n ].filter(Boolean).join('\\n');\n\n const sizeBytes = Buffer.byteLength(prompt, 'utf-8');\n console.log(`[review-agent] Convoy prompt for ${opts.issueId}/${opts.subRole}: ${sizeBytes} bytes`);\n return prompt;\n}\n\n\n\nasync function spawnReviewSubRoleForIssuePromise(opts: {\n issueId: string;\n workspace: string;\n subRole: ReviewSubRole;\n runId: string;\n outputPath?: string;\n contextManifestPath?: string;\n synthesisAgentId?: string;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}): Promise<{ success: boolean; message: string; error?: string; sessionId?: string }> {\n try {\n const { saveAgentState, spawnRun, getAgentStateSync, getLatestSessionIdSync, resumeAgent, stopAgent } = await import('../agents.js');\n const cfg = loadYamlConfig().config;\n const outputPath = opts.outputPath ?? reviewerAgentOutputPath(opts.workspace, opts.runId, opts.subRole);\n const synthesisAgentId = opts.synthesisAgentId ?? `agent-${opts.issueId.toLowerCase()}-review`;\n const model = opts.model ?? resolveModel('review', opts.subRole, cfg, opts.subRole ? undefined : `review:${opts.issueId}`);\n const reviewerDir = join(AGENTS_DIR, reviewerAgentId(opts.issueId, opts.subRole));\n\n await mkdir(dirname(outputPath), { recursive: true });\n // These are named runtime marker files below the agent-dir deletion door.\n await rm(outputPath, { force: true }); // PAN-3357: not a dir removal\n await rm(join(reviewerDir, 'reviewer-signaled'), { force: true }); // PAN-3357: not a dir removal\n await rm(join(reviewerDir, 'reviewer-launcher.pid'), { force: true }); // PAN-3357: not a dir removal\n\n // Build Tier-1 inline summary from manifest when available (PAN-1125)\n let tier1Summary: string | undefined;\n if (opts.contextManifestPath) {\n try {\n const manifestRaw = await readFile(opts.contextManifestPath, 'utf-8');\n const manifest = JSON.parse(manifestRaw) as ReviewContextManifest;\n tier1Summary = formatTier1Summary(manifest);\n } catch (manifestErr) {\n console.warn(`[review-agent] Failed to read manifest for Tier-1 summary (${opts.issueId}/${opts.subRole}):`, manifestErr);\n }\n }\n\n const prompt = await Effect.runPromise(buildConvoyPrompt({\n issueId: opts.issueId,\n subRole: opts.subRole,\n outputPath,\n synthesisAgentId,\n contextManifestPath: opts.contextManifestPath,\n tier1Summary,\n }));\n\n // PAN-1862: convoy sub-reviewers RESUME by default too — same rule as quick review. Each\n // lane keeps its prior round's context so a re-review checks the fix instead of re-reading\n // the whole diff. Fresh-spawn only on a harness/model change or when no session exists.\n const reviewerAgent = reviewerAgentId(opts.issueId, opts.subRole);\n const savedReviewer = getAgentStateSync(reviewerAgent);\n const canResumeReviewer = reviewResumeDecision({\n requestedModel: opts.model ?? model,\n requestedHarness: opts.harness,\n savedModel: savedReviewer?.model,\n savedHarness: savedReviewer?.harness,\n hasSavedState: !!savedReviewer,\n hasSavedSession: !!getLatestSessionIdSync(reviewerAgent),\n });\n if (canResumeReviewer) {\n console.log(`[review-agent] Resuming convoy sub-reviewer ${opts.subRole} for ${opts.issueId} — preserving context (PAN-1862)`);\n const resumeResult = await resumeAgent(reviewerAgent, prompt);\n if (resumeResult.success && resumeResult.messageDelivered !== false) {\n try {\n const resumed = getAgentStateSync(reviewerAgent);\n if (resumed) {\n resumed.reviewSubRole = opts.subRole;\n resumed.reviewRunId = opts.runId;\n resumed.reviewOutputPath = outputPath;\n resumed.reviewSynthesisAgentId = synthesisAgentId;\n resumed.reviewDeadlineAt = new Date(Date.now() + REVIEWER_TIMEOUT_MS).toISOString();\n delete resumed.reviewMonitorSignaled;\n delete resumed.reviewRetryAttempt;\n await Effect.runPromise(saveAgentState(resumed));\n }\n } catch { /* non-fatal */ }\n return { success: true, message: `Review ${opts.subRole} resumed (session preserved): ${reviewerAgent}`, sessionId: reviewerAgent };\n }\n const resumeError = resumeResult.messageDelivered === false\n ? 'continue prompt was not confirmed in the resumed session'\n : resumeResult.error;\n console.warn(`[review-agent] Convoy sub-reviewer ${opts.subRole} resume failed; falling back to a fresh session: ${resumeError}`);\n // PAN-2743: Codex reviewers intentionally remain alive at their prompt after\n // finishing a cycle. A successful process resume whose prompt did not land is\n // also live but unusable. Stop either live collision before the fresh spawn;\n // other resume failures already imply no live session and need no teardown.\n if (resumeResult.messageDelivered === false || resumeResult.error?.includes('it appears healthy')) {\n await Effect.runPromise(stopAgent(reviewerAgent));\n }\n }\n\n const reviewDeadlineAt = new Date(Date.now() + REVIEWER_TIMEOUT_MS).toISOString();\n const spawnOptions = {\n workspace: opts.workspace,\n subRole: opts.subRole,\n prompt,\n model,\n harness: opts.harness,\n // Persist every signal-routing field before tmux launch. A later\n // running-state cache write may contend, but the reviewer can still\n // finish and the Stop-hook can still deliver REVIEWER_READY.\n reviewRunId: opts.runId,\n reviewSynthesisAgentId: synthesisAgentId,\n reviewOutputPath: outputPath,\n reviewDeadlineAt,\n allowHost: opts.allowHost ?? false,\n startedBy: 'review-convoy' as const,\n };\n const agentId = reviewerAgentId(opts.issueId, opts.subRole);\n let run: Awaited<ReturnType<typeof spawnRun>>;\n for (let attempt = 0; ; attempt += 1) {\n try {\n run = await spawnRun(opts.issueId, 'review', spawnOptions);\n break;\n } catch (err) {\n if (!isReviewerStateWriteContention(err, agentId)) throw err;\n if (await reviewerSessionIsLive(agentId)) {\n console.warn(\n `[review-agent] ${agentId} launched but its running-state cache write was contended; `\n + 'waiting for its report instead of emitting REVIEWER_FAILED',\n );\n return {\n success: true,\n message: `Review ${opts.subRole} launched with deferred cache reconciliation: ${agentId}`,\n sessionId: agentId,\n };\n }\n const delay = REVIEWER_SPAWN_BACKOFF_DELAYS_MS[attempt];\n if (delay === undefined) throw err;\n console.warn(\n `[review-agent] ${agentId} state write was contended before launch; retrying in ${delay}ms`,\n );\n await new Promise<void>((resolve) => { setTimeout(resolve, delay); });\n }\n }\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({ type: 'reviewer_started', issueId: opts.issueId, role: opts.subRole, sessionName: run.id });\n } catch {\n // Non-fatal\n }\n return { success: true, message: `Review ${opts.subRole} spawned: ${run.id}`, sessionId: run.id };\n } catch (err) {\n return {\n success: false,\n message: `Failed to spawn review ${opts.subRole}`,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n\nexport interface ConvoyLaunchParams {\n issueId: string;\n workspace: string;\n runId: string;\n synthesisAgentId: string;\n /** Recovery provides only lanes with no report and no live session. */\n subRoles?: ReviewSubRole[];\n contextManifestPath?: string;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}\n\n/** Launch all four independent reviewers for one review run. */\nexport async function launchConvoyReviewersPromise(params: ConvoyLaunchParams): Promise<Array<{ success: boolean; message: string }>> {\n const reviewerResults = await Promise.all((params.subRoles ?? REVIEW_SUB_ROLES).map(async (subRole) => {\n const outputPath = reviewerAgentOutputPath(params.workspace, params.runId, subRole);\n\n const result = await Effect.runPromise(spawnReviewSubRoleForIssue({\n issueId: params.issueId,\n workspace: params.workspace,\n subRole,\n runId: params.runId,\n outputPath,\n contextManifestPath: params.contextManifestPath,\n synthesisAgentId: params.synthesisAgentId,\n ...(params.model ? { model: params.model } : {}),\n ...(params.harness ? { harness: params.harness } : {}),\n allowHost: params.allowHost ?? false,\n }));\n if (!result.success) {\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(params.synthesisAgentId, `REVIEWER_FAILED ${subRole} ${result.error ?? result.message}`);\n } catch (signalErr) {\n console.warn(`[review-agent] Failed to signal ${subRole} spawn failure to ${params.synthesisAgentId}:`, signalErr);\n }\n }\n return result;\n }));\n\n const failedReviewers = reviewerResults.filter(r => !r.success);\n if (failedReviewers.length > 0) {\n console.warn(`[review-agent] Convoy launched for ${params.issueId}, but ${failedReviewers.length} reviewer(s) failed to spawn`);\n }\n return reviewerResults;\n}\n\n\n\nexport const buildConvoyPrompt = (opts: {\n issueId: string;\n subRole: string;\n outputPath: string;\n synthesisAgentId: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): Effect.Effect<string> => Effect.promise(() => buildConvoyPromptPromise(opts));\n\n/**\n * Effect variant of {@link spawnReviewSubRoleForIssue}. The Promise version\n * already aggregates errors into the structured result shape, so the Effect\n * form lifts via `Effect.promise`.\n */\nexport const spawnReviewSubRoleForIssue = (opts: {\n issueId: string;\n workspace: string;\n subRole: ReviewSubRole;\n runId: string;\n outputPath?: string;\n contextManifestPath?: string;\n synthesisAgentId?: string;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}): Effect.Effect<{ success: boolean; message: string; error?: string; sessionId?: string }> =>\n Effect.promise(() => spawnReviewSubRoleForIssuePromise(opts));\n\n/**\n * Effect variant of {@link spawnReviewRoleForIssue}. The Promise version\n * returns a structured result instead of throwing, so the Effect form lifts\n * via `Effect.promise`.\n */\n\n\n/**\n * Re-launch only convoy lanes whose report and session are both absent for the\n * current parent run. It is idempotent, so a repeated recovery request is a no-op.\n */\nexport async function recoverMissingConvoyReviewers(\n issueId: string,\n opts: { source?: string; model?: string; harness?: RuntimeName } = {},\n): Promise<{ success: boolean; message: string; launched?: number }> {\n const normalized = issueId.toUpperCase();\n const parentId = `agent-${normalized.toLowerCase()}-review`;\n const { saveAgentState, getAgentStateSync } = await import('../agents.js');\n const parentState = getAgentStateSync(parentId);\n if (!parentState) {\n return { success: false, message: `No review parent state for ${normalized} — cannot recover reviewers` };\n }\n\n let parent: typeof parentState | null;\n try {\n const { resolveReviewParentRunState } = await import('./review-run-recovery.js');\n parent = await resolveReviewParentRunState(parentState, { persistCurrent: true });\n } catch (error) {\n return {\n success: false,\n message: `Could not persist active review run state for ${normalized}: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n if (!parent?.workspace || !parent.reviewRunId) {\n return { success: false, message: `Review parent for ${normalized} is missing workspace/runId state — cannot launch the convoy` };\n }\n const workspace = parent.workspace;\n const runId = parent.reviewRunId;\n\n // Reviewer evidence is per lane: one completed report or live session never\n // proves that a sibling reviewer launched.\n let sessions = new Set<string>();\n let livenessProbeOk = true;\n try {\n sessions = new Set(await Effect.runPromise(listSessionNames()));\n } catch {\n // Liveness probe failed — tmux cannot answer, so the state row's live claim\n // is the only signal left and keeps its conservative vote below.\n livenessProbeOk = false;\n }\n\n const { existsSync } = await import('node:fs');\n const reviewersToLaunch: ReviewSubRole[] = [];\n for (const subRole of REVIEW_SUB_ROLES) {\n const reviewerId = reviewerAgentId(normalized, subRole);\n const reviewer = getAgentStateSync(reviewerId);\n const stateClaimsLive = reviewer?.status === 'running' || reviewer?.status === 'starting';\n if (existsSync(reviewerAgentOutputPath(workspace, runId, subRole))) continue;\n if (sessions.has(reviewerId)) continue;\n // tmux is the liveness oracle (docs/AGENT-STATE-PLANES.md): a probe that\n // answered \"no session\" outranks a state.json row still claiming\n // running/starting. Rows go stale whenever liveness reconciliation cannot\n // run (deacon freeze, boot --no-resume) or a reviewer exits without a\n // stopped event; trusting the claim no-oped the convoy launch and stranded\n // the synthesis parent waiting for reviewers that would never exist\n // (PAN-3545; PAN-3511 cycle 3, 2026-08-04). Heal the stale row here, at the\n // signal — event-driven, no patrol required. Probe failure keeps the row's\n // conservative vote: better a skipped launch than a duplicate reviewer.\n if (stateClaimsLive) {\n if (!livenessProbeOk) continue;\n try {\n const { markAgentStoppedState } = await import('../agents/agent-state.js');\n await Effect.runPromise(saveAgentState(markAgentStoppedState(reviewer!, 'system')));\n } catch { /* best-effort heal — the launch must not fail over bookkeeping */ }\n }\n reviewersToLaunch.push(subRole);\n }\n if (reviewersToLaunch.length === 0) {\n return { success: true, message: `Convoy already launched for ${normalized} run ${runId} — no-op` };\n }\n\n // The per-issue review-model override has to be re-read here: this path is entered from a\n // signal, not from the parent's spawn opts, so nothing carries `model` in. Read the record\n // rather than `parent.model` — that is the parent's *resolved* model, and forwarding it\n // would override each sub-role's own configured model even with no override set.\n const project = resolveProjectForIssue(normalized);\n const issueReviewModel = project ? readIssueRecordSync(project, normalized)?.reviewModel : undefined;\n const reviewModel = opts.model ?? issueReviewModel;\n const results = await launchConvoyReviewersPromise({\n issueId: normalized,\n workspace,\n runId,\n synthesisAgentId: parentId,\n subRoles: reviewersToLaunch,\n contextManifestPath: parent.reviewContextManifestPath,\n ...(reviewModel ? { model: reviewModel } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n allowHost: parent.hostOverride ?? false,\n });\n\n const launched = results.filter(r => r.success).length;\n const message = `Convoy recovery for ${normalized}${opts.source ? ` (${opts.source})` : ''}: launched ${launched}/${reviewersToLaunch.length} missing reviewer(s)`;\n console.log(`[review-agent] ${message}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: normalized });\n return { success: launched === reviewersToLaunch.length, message, launched };\n}\n","/**\n * Review role entry point.\n *\n * PAN-1048 review feedback 007: every legacy convoy helper has been retired.\n * The bash/tmux coordinator path (dispatchParallelReview /\n * spawnReviewCoordinatorSession / runParallelReview) was deleted in R6, and\n * the round-7 cleanup additionally removed the supporting cast that the\n * coordinator used to drive — spawnSingleReviewer, waitForReviewer,\n * archiveReviewerRound, parseReviewSynthesis, parseAgentOutput,\n * selectCompletedReviewers, getReviewAgents, getFilesChangedFromPR,\n * buildReviewFeedbackBody, parseReviewerTemplate, resolvePromptTemplatePath\n * (and the resolveTemplatePath alias), resolveReviewerModel,\n * reviewResultToReviewStatus, reviewerRetryBackoffMs,\n * isRetryableReviewerFailure, the ReviewContext / ReviewResult /\n * ReviewerTemplate / ReviewerRoundArtifact / ReviewerOutcome /\n * ReviewerWaitResult / ReviewerFailureReason / ReviewHistoryEntry types,\n * and DEFAULT_REVIEW_AGENTS / REVIEW_TIMEOUT_MS / MAX_REVIEWER_TIMEOUT_RETRIES\n * / REVIEWER_TIMEOUT_RETRY_BACKOFF_MS / REVIEW_HISTORY_DIR / REVIEW_HISTORY_FILE\n * / SPECIALISTS_DIR constants. None of these had any production caller after R6;\n * their tests went with them.\n *\n * Every active review surface — POST /api/review/:issueId/trigger, the\n * reactive scheduler review branch, and the dashboard kanban \"Review again\"\n * button — flows through\n * spawnReviewRoleForIssue → spawnRun(issueId, 'review'). The review role\n * launches four isolated review sub-role sessions via `pan review spawn-reviewer`,\n * then writes the report and signals the verdict via Overdeck's CLI inside\n * the role itself (see roles/review.md).\n *\n * Surface area kept:\n * - spawnReviewRoleForIssue — the only review entry point\n * - killAllReviewerSessions — kill the canonical reviewer sessions\n * for one issue (merge-agent +\n * dashboard cancel/abort routes)\n * - killAllReviewSessions — kill ALL review sessions on shutdown\n * (pan down)\n */\n\nimport { exec } from 'child_process';\nimport { createHash } from 'node:crypto';\nimport { existsSync } from 'fs';\nimport { mkdir, readFile, rm, writeFile } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { killSession, listSessionNames, isPaneDead } from '../tmux.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { removeAgent } from '../agents/removal.js';\nimport { listAgentIdsByPrefixSync } from '../overdeck/agents.js';\nimport { getAgentStateSync as getAgentStateFileSync } from '../agents/agent-state.js';\nimport { getReviewStatusSync, setReviewStatusSync } from '../review-status.js';\nimport { clearSupersededReviewInfrastructureFailure } from '../review-verdict-guards.js';\nimport { loadConfigSync as loadYamlConfig, type ReviewMode } from '../config-yaml.js';\nimport { buildReviewContext, formatTier1Summary, type ReviewContextManifest } from './review-context.js';\nimport { buildRealConflictGateDeps, getCachedConflictGateMergeability, resolveConflictGate } from './conflict-gate.js';\nimport { createPromiseCoalescer } from './in-flight-guard.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nimport { reviewResumeDecision } from './review-resume-decision.js';\nimport { evaluateReviewConvoyLiveness } from './review-convoy-liveness.js';\nimport { isReviewSessionForIssue } from './specialists-registry.js';\nimport { convergeRowFromVerdictOfRecord } from './verdict-restore.js';\nimport {\n recoverMissingConvoyReviewers,\n launchConvoyReviewersPromise,\n} from './review-convoy.js';\nimport { shouldSkipDispatchAsMerged } from './merge-verification.js';\nimport { readIssueRecordSync, resolveProjectForIssue } from '../pan-dir/record.js';\nimport { PAN_DIRNAME } from '../pan-dir/types.js';\nimport { AGENTS_DIR, packageRoot, sessionFilePath } from '../paths.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { withReviewLifecycleGuard } from '../review-lifecycle-guard.js';\n\nconst execAsync = promisify(exec);\n// PAN-1531: review-temp stash helpers removed.\n\n// PAN-2584: liveness budget for the review PARENT (discovery + convoy + synthesis).\n// Sub-reviewers get their own 20-minute deadlines in review-convoy.ts; the parent\n// needs headroom for all three phases. Enforced by checkStalledReviewParents.\nexport const PARENT_REVIEW_TIMEOUT_MS = 45 * 60 * 1000;\n// Review now runs against the committed diff only. The dirty-worktree gate\n// at pan done time (and the same gate added to /api/review/:id/request)\n// guarantees the worktree is clean before specialists see the diff.\n\nconst reviewSynthesisPath = (reviewDir: string): string => join(reviewDir, 'synthesis.md');\nconst selfReviewReportPath = (reviewDir: string): string => join(reviewDir, 'review.md');\n\nasync function deriveReviewRunHead8(issueId: string, workspace: string): Promise<string> {\n try {\n const { resolvePrimaryWorkspaceRepoDirSync, resolveWorkspaceRepoRootsSync } = await import('../project-repos.js');\n const roots = resolveWorkspaceRepoRootsSync(issueId, workspace);\n if (roots.some(root => root.isPolyrepo)) {\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n const anchor = await snapshotWorkspaceHeadsPromise(issueId, workspace);\n return anchor\n ? createHash('sha1').update(anchor).digest('hex').substring(0, 8)\n : 'unknown';\n }\n\n // PAN-3037: the primary code repo is resolved through the shared helper —\n // the polyrepo wrapper's HEAD never moves, so a wrapper-derived runId would\n // mismatch every live run and kill a healthy convoy on each dispatch.\n const probeDir = resolvePrimaryWorkspaceRepoDirSync(issueId, workspace);\n const { stdout } = await execAsync(['git', 'rev-parse', '--short=8', 'HEAD'].join(' '), {\n cwd: probeDir,\n encoding: 'utf-8',\n timeout: 10_000,\n });\n return stdout.trim() || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nexport function buildReviewRolePrompt(opts: {\n issueId: string;\n workspace: string;\n branch: string;\n prUrl?: string;\n runId: string;\n reviewDir: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): string {\n const subRoleFiles = REVIEW_SUB_ROLES.map(r => ` ${join(opts.reviewDir, `${r}.md`)}`).join('\\n');\n const expectedSignals = REVIEW_SUB_ROLES.map(r => ` REVIEWER_READY ${r} <outputPath> or REVIEWER_FAILED ${r} <reason> or REVIEWER_TIMEOUT ${r} <reason>`).join('\\n');\n const synthesisPath = reviewSynthesisPath(opts.reviewDir);\n const runningDesc = 'the four convoy reviewers (security, correctness, performance, requirements)';\n const prompt = [\n `STANDBY — REVIEW SYNTHESIS for ${opts.issueId}`,\n '',\n `Do NOT do anything yet. The Overdeck server has already spawned ${runningDesc}`,\n 'and they are running in parallel right now. Your work begins only once they finish.',\n '',\n `You will receive exactly one \\`pan tell\\` signal per RUNNING sub-role as each`,\n 'reviewer finishes — these are delivered to you as user messages:',\n expectedSignals,\n '',\n `Until all ${REVIEW_SUB_ROLES.length} terminal signal(s) have arrived: do nothing. Do not read the`,\n 'reviewer output files, do not run git, do not inspect tmux sessions, do not',\n 'poll anything. Just wait — the reviewers notify you when they finish, and',\n 'Deacon is the failsafe if one never starts or never completes. Acting early',\n 'wastes tokens reviewing nothing.',\n '',\n 'STALE-SIGNAL GUARD (PAN-3549): terminal signals from a dead prior attempt of',\n 'this same branch can replay into your session when you resume — every signal',\n 'carries its deadline in its text. A signal is current only if its deadline is',\n \"NEWER than this dispatch's context manifest. Before counting any\",\n 'REVIEWER_TIMEOUT or REVIEWER_FAILED signal, compare: run',\n '`stat -c %y <manifest path>` and discard any signal whose deadline is older.',\n 'Stale signals describe lanes that no longer exist; the current lanes are',\n 'still running and their signals arrive later.',\n '',\n `Once you have all ${REVIEW_SUB_ROLES.length} terminal signal(s), follow roles/review.md exactly to`,\n 'read the reports, synthesize the verdict,',\n 'write the synthesis report, and signal the status.',\n '',\n '── Review context ──',\n `Issue: ${opts.issueId}`,\n `Branch: ${opts.branch}`,\n `Workspace: ${opts.workspace}`,\n opts.prUrl ? `PR: ${opts.prUrl}` : `PR: (resolve via: gh pr view ${opts.branch})`,\n `Run ID: ${opts.runId}`,\n `Review directory: ${opts.reviewDir}`,\n `Synthesis output file: ${synthesisPath}`,\n '',\n opts.tier1Summary\n ? [\n 'Shared review context:',\n '─────────────────────────────────────────────────────────────',\n opts.tier1Summary,\n '─────────────────────────────────────────────────────────────',\n '',\n opts.contextManifestPath ? `Full manifest: ${opts.contextManifestPath}` : '',\n ].join('\\n')\n : opts.contextManifestPath\n ? `Context manifest: ${opts.contextManifestPath}`\n : 'Context manifest: (missing — block review per roles/review.md)',\n '',\n 'Convoy reviewer output files (read each one ONLY after its REVIEWER_READY signal):',\n subRoleFiles,\n '',\n 'After writing the synthesis report, signal the verdict with Overdeck CLI:',\n ` pan admin specialists done review ${opts.issueId} --status passed --notes \"<one-line summary>\" --run-id \"${opts.runId}\"`,\n ` pan admin specialists done review ${opts.issueId} --status blocked --notes \"<one-line top blocker>\" --run-id \"${opts.runId}\"`,\n '',\n // PAN-2007: do NOT tell the agent to `exit`. The session is kept alive through\n // the pipeline (KEEP_SPECIALIST_SESSIONS_ALIVE) so it can be reused for the next\n // review cycle without a cold re-spawn. Exiting before the signal command is\n // what stranded reviews at reviewStatus=reviewing.\n 'After running the signal command above, STOP and wait — do not exit, do not run',\n 'any further commands. The session stays open for the next review cycle.',\n '',\n 'Reactive Cloister dispatches the test role after review passes. Never queue tests yourself and never edit code.',\n ].filter(Boolean).join('\\n');\n\n const sizeBytes = Buffer.byteLength(prompt, 'utf-8');\n console.log(`[review-agent] Synthesis prompt for ${opts.issueId}: ${sizeBytes} bytes`);\n return prompt;\n}\n\n// PAN-1981 (quick path to production): the review role agent reviews the diff\n// ITSELF — no convoy, no synthesis. `buildReviewRolePrompt` above (the synthesis\n// \"stand by, wait for the convoy\" prompt) is kept for when we restore the convoy\n// as an opt-in (#1982 fast-follow); for now the review agent gets this self-review\n// prompt instead. We will decide convoy-vs-self-review (and better per-harness\n// message transmission) in the fast-follow.\nfunction buildSelfReviewPrompt(opts: {\n issueId: string;\n workspace: string;\n branch: string;\n prUrl?: string;\n runId: string;\n reviewDir: string;\n contextManifestPath?: string;\n tier1Summary?: string;\n}): string {\n const reviewReportPath = selfReviewReportPath(opts.reviewDir);\n const prompt = [\n `CODE REVIEW for ${opts.issueId} — you are the sole reviewer; review the change yourself.`,\n '',\n 'Review the diff for this branch yourself, across ALL dimensions in one pass:',\n 'correctness/logic, security, requirements/acceptance-criteria, and performance.',\n 'Do NOT spawn or wait for any sub-reviewers — there is no convoy; you do the',\n 'whole review yourself and emit the verdict.',\n '',\n '── Review context ──',\n `Issue: ${opts.issueId}`,\n `Branch: ${opts.branch}`,\n `Workspace: ${opts.workspace}`,\n opts.prUrl ? `PR: ${opts.prUrl}` : `PR: (resolve via: gh pr view ${opts.branch})`,\n `Run ID: ${opts.runId}`,\n `Review directory: ${opts.reviewDir}`,\n `Review output file: ${reviewReportPath}`,\n '',\n opts.tier1Summary\n ? [\n 'Shared review context (risk-ranked changed files + acceptance criteria):',\n '─────────────────────────────────────────────────────────────',\n opts.tier1Summary,\n '─────────────────────────────────────────────────────────────',\n '',\n opts.contextManifestPath ? `Full manifest: ${opts.contextManifestPath}` : '',\n ].join('\\n')\n : opts.contextManifestPath\n ? `Context manifest: ${opts.contextManifestPath}`\n : 'Context manifest: (missing — inspect the diff directly: git diff origin/main...HEAD)',\n '',\n 'How to review:',\n '1. Read the diff — use the manifest risk ranking, `git diff` the high-risk files,',\n ' and read the surrounding code as needed.',\n '2. Evaluate correctness, security, requirements/AC, and performance. Use the',\n ' severity + verdict vocabulary in roles/review.md.',\n `3. Write your findings to ${reviewReportPath}.`,\n '',\n 'Then signal the verdict with the Overdeck CLI (exactly one):',\n ` pan admin specialists done review ${opts.issueId} --status passed --notes \"<one-line summary>\" --run-id \"${opts.runId}\"`,\n ` pan admin specialists done review ${opts.issueId} --status blocked --notes \"<one-line top blocker>\" --run-id \"${opts.runId}\"`,\n '',\n // PAN-2007: do NOT tell the agent to `exit`. The session is kept alive through\n // the pipeline (KEEP_SPECIALIST_SESSIONS_ALIVE) so it can be reused for the next\n // review cycle without a cold re-spawn. Exiting before the signal command is\n // what stranded reviews at reviewStatus=reviewing.\n 'After running the signal command above, STOP and wait — do not exit, do not run',\n 'any further commands. The session stays open for the next review cycle.',\n '',\n 'Reactive Cloister dispatches the test role after review passes. Never queue tests yourself and never edit code.',\n ].filter(Boolean).join('\\n');\n\n const sizeBytes = Buffer.byteLength(prompt, 'utf-8');\n console.log(`[review-agent] Self-review prompt for ${opts.issueId}: ${sizeBytes} bytes`);\n return prompt;\n}\nasync function spawnReviewRoleForIssuePromise(\n opts: { issueId: string; workspace: string; branch: string; prUrl?: string; model?: string; harness?: RuntimeName; force?: boolean; allowHost?: boolean },\n): Promise<{ success: boolean; message: string; error?: string; gated?: boolean }> {\n const dispatchStartedAtMs = Date.now();\n if (!opts.model) {\n const project = resolveProjectForIssue(opts.issueId);\n const issueModel = project ? readIssueRecordSync(project, opts.issueId)?.reviewModel : undefined;\n if (issueModel) opts = { ...opts, model: issueModel };\n }\n const reviewSessionName = `agent-${opts.issueId.toLowerCase()}-review`;\n\n // PAN-2420: GitHub-authoritative guard. Do not waste time on conflict-gate\n // checks or context builds for a PR that GitHub already reports merged.\n const mergedGuard = await shouldSkipDispatchAsMerged(opts.issueId);\n if (mergedGuard.skip) {\n const message = `[review-agent] Skipping review dispatch for ${opts.issueId} — ${mergedGuard.reason}`;\n console.log(message);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message, issueId: opts.issueId });\n return { success: false, message };\n }\n\n // PAN-1862 (FR-14): review mode 'none' — skip the AI review entirely. This sits at\n // the single review entry point so the trigger route, host auto-dispatch, and every\n // Deacon re-dispatch site all honor it without per-call-site logic. The pre-review\n // verification gate (typecheck/lint/test floor) has already run by the time any\n // caller reaches here — 'none' skips only the AI review, never the quality floor.\n // reviewSpawnedAt is stamped so the durable reviewRequestedAt intent counts as\n // serviced (otherwise needsReviewDispatch would re-fire this skip every read).\n // Setting reviewStatus 'skipped' advances the lifecycle exactly like an approved\n // review (the setReviewStatusSync write path emits review.approved for it).\n if (resolveReviewMode(opts.issueId) === 'none') {\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'skipped',\n reviewNotes: 'Review mode: none — AI review skipped by configuration; verification gate still enforced',\n reviewSpawnedAt: new Date().toISOString(),\n });\n const message = `Review skipped for ${opts.issueId} (mode=none) — advancing to test`;\n console.log(`[review-agent] ${message}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: opts.issueId });\n return { success: true, message };\n }\n\n // Idempotency: if a review role agent for this issue already has an alive\n // tmux pane, treat the current dispatch as a no-op. spawnRun has its own\n // session-exists check but it throws — we want soft \"already running\"\n // semantics so callers can keep their existing success-path messaging.\n //\n // Force mode (human override from dashboard) kills the old session and\n // respawns so the review runs against current HEAD, not stale state.\n if (opts.force) {\n const stopped = await Effect.runPromise(\n killAllReviewerSessions(undefined, opts.issueId).pipe(\n Effect.catch(() => Effect.succeed({\n killed: [],\n failed: [reviewSessionName],\n })),\n ),\n );\n if (stopped.failed.length > 0) {\n return {\n success: false,\n message: `Review replacement aborted — could not stop ${stopped.failed.join(', ')}`,\n error: `Review sessions still live: ${stopped.failed.join(', ')}`,\n };\n }\n }\n\n try {\n const sessions = opts.force ? [] : await Effect.runPromise(listSessionNames());\n if (sessions.includes(reviewSessionName)) {\n const paneDead = await Effect.runPromise(isPaneDead(reviewSessionName));\n\n // A synthesis agent that has finished its verdict does NOT terminate:\n // its role prompt tells it to \"exit\", but it runs `Bash(exit)` which\n // only exits a subshell — the Claude process stays idle-alive with a\n // live pane. So \"pane alive\" does NOT mean \"actively reviewing\", and the\n // old guard would skip re-dispatch forever, jamming the issue at\n // review=reviewing with no convoy actually running (PAN-1131).\n //\n // Disambiguate via the run id: every review run is keyed to a HEAD sha\n // (runId = agent-<issue>-review-<head8>). If the existing synthesis\n // session was started for a different HEAD than the one we are about to\n // review, it is a stale leftover — kill the convoy and respawn. Only a\n // session whose runId matches the *current* HEAD is genuinely the\n // review-in-progress we should defer to.\n let staleRunId = false;\n let currentRunId: string | undefined;\n if (!paneDead && !opts.force) {\n try {\n const head8 = await deriveReviewRunHead8(opts.issueId, opts.workspace);\n if (head8 === 'unknown') throw new Error('workspace HEAD unavailable');\n currentRunId = `agent-${opts.issueId.toLowerCase()}-review-${head8}`;\n const synthReviewRunId = getAgentStateSync(reviewSessionName)?.reviewRunId;\n // A missing identity cannot prove that the live pane covers the\n // current obligation, so legacy/unknown sessions are stale too.\n if (!synthReviewRunId || synthReviewRunId !== currentRunId) {\n staleRunId = true;\n console.log(\n `[review-agent] ${reviewSessionName} is stale — runId ${synthReviewRunId ?? 'missing'} != current ${currentRunId}; killing convoy and respawning`,\n );\n }\n } catch (probeErr) {\n console.warn(\n `[review-agent] Could not probe ${reviewSessionName} runId, falling back to pane-alive idempotency:`,\n probeErr,\n );\n }\n }\n\n // PAN-1131 residual + PAN-2579: a runId-matching live pane is only \"actively\n // reviewing\" while this cycle's verdict is UNRECORDED. Once the verdict is\n // terminal, the session is warm-idle (kept alive by the warm-by-default\n // lifecycle) — a re-dispatch request must NOT be swallowed by the guard, or\n // the issue jams at a stale verdict with a live-but-finished reviewer. Fall\n // through to the respawn path below: it kills the convoy tmux and the spawn\n // machinery resumes the saved session with its context intact (warm reuse).\n let finishedIdle = false;\n if (!paneDead && !opts.force && !staleRunId) {\n try {\n const status = getReviewStatusSync(opts.issueId);\n const terminal = status?.reviewStatus === 'passed'\n || status?.reviewStatus === 'blocked'\n || status?.reviewStatus === 'failed';\n // Warm-reuse ONLY for a genuinely un-serviced newer request (same\n // ISO-string comparison as needsReviewDispatch). A terminal verdict\n // with NO newer request means this call is a stale duplicate dispatch\n // racing the verdict (the PAN-399 shape) — skip below and leave the\n // verdict alone rather than re-entering 'reviewing'.\n const newerRequest = !!status?.reviewRequestedAt\n && (!status.reviewSpawnedAt || Date.parse(status.reviewRequestedAt) > new Date(status.reviewSpawnedAt).getTime());\n // PAN-2584: a lost verdict leaves the status non-terminal while the\n // reviewer already wrote its report for this exact HEAD — that session\n // is finished, not reviewing. Report-on-disk for the current runId is\n // terminal evidence too; without it a newer request deadlocks behind\n // the guard forever.\n let reportWritten = false;\n if (currentRunId) {\n try {\n const reviewDir = join(opts.workspace, PAN_DIRNAME, 'review', currentRunId);\n reportWritten = existsSync(selfReviewReportPath(reviewDir))\n || existsSync(reviewSynthesisPath(reviewDir));\n } catch { /* probe failure — fall back to verdict-only evidence */ }\n }\n const reviewAgents = listAgentIdsByPrefixSync(reviewSessionName)\n .map(id => getAgentStateFileSync(id))\n .filter(state => state !== null && state !== undefined);\n const convoyLiveness = evaluateReviewConvoyLiveness(opts.issueId, status ?? {}, reviewAgents);\n finishedIdle = (terminal || reportWritten || !convoyLiveness.active) && newerRequest;\n if (finishedIdle) {\n console.log(\n `[review-agent] ${reviewSessionName} is finished-idle (verdict ${status?.reviewStatus}, newer request pending) — warm-reusing for the new review cycle`,\n );\n } else if (terminal) {\n console.log(\n `[review-agent] ${reviewSessionName} has a terminal verdict (${status?.reviewStatus}) and no newer request — treating this dispatch as a stale duplicate; leaving the verdict intact`,\n );\n }\n } catch (statusErr) {\n console.warn(`[review-agent] Could not probe review status for finished-idle check on ${opts.issueId}:`, statusErr);\n }\n }\n\n if (!paneDead && !opts.force && !staleRunId && !finishedIdle) {\n const convergence = currentRunId\n ? await convergeRowFromVerdictOfRecord(opts.issueId, {\n runId: currentRunId,\n workspacePath: opts.workspace,\n writer: 'dispatch-converge',\n })\n : { converged: false };\n if (convergence.converged) {\n const message = `Review dispatch converged from the verdict of record: ${opts.issueId}`;\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: opts.issueId });\n return { success: true, message };\n }\n console.log(`[review-agent] Idempotency guard: ${reviewSessionName} already running for ${opts.issueId} — skipping spawn`);\n return { success: false, message: `Review dispatch skipped — already running: ${reviewSessionName}` };\n }\n // Session pane is dead, force mode, stale runId, or finished-idle — kill the\n // convoy tmux and respawn (the spawn path resumes the saved session, so a\n // warm reviewer keeps its context).\n const reason = opts.force ? 'force-killed for re-review'\n : paneDead ? 'pane is dead'\n : staleRunId ? 'stale runId'\n : 'finished-idle (warm reuse for new cycle)';\n console.log(`[review-agent] ${reviewSessionName} ${reason} — respawning convoy`);\n const stopped = await Effect.runPromise(\n killAllReviewerSessions(undefined, opts.issueId).pipe(\n Effect.catch(() => Effect.succeed({\n killed: [],\n failed: [reviewSessionName],\n })),\n ),\n );\n if (stopped.failed.length > 0) {\n return {\n success: false,\n message: `Review replacement aborted — could not stop ${stopped.failed.join(', ')}`,\n error: `Review sessions still live: ${stopped.failed.join(', ')}`,\n };\n }\n }\n } catch (err) {\n console.warn(`[review-agent] Idempotency check failed for ${opts.issueId}, proceeding:`, err);\n }\n\n // Fast synchronous cache check for the gated case. If the probe cache says\n // the branch has conflicts (or we cannot verify mergeability), fail fast\n // without shelling out to git on the awaited request path.\n const cachedMergeability = getCachedConflictGateMergeability(opts.issueId);\n if (cachedMergeability === 'conflicts' || cachedMergeability === 'unknown') {\n const targetBranch = 'main';\n const reason = cachedMergeability === 'conflicts'\n ? `merge conflict with ${targetBranch} must be resolved before review dispatch`\n : `mergeability against ${targetBranch} could not be verified; deferring review conservatively`;\n const message = `Review dispatch deferred: ${reason}`;\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'pending',\n reviewNotes: message,\n });\n return { success: false, gated: true, message };\n }\n\n const gate = await resolveConflictGate(\n opts.issueId,\n opts.workspace,\n 'main',\n buildRealConflictGateDeps(),\n );\n if (gate.gated) {\n const message = `Review dispatch deferred: ${gate.reason ?? 'merge conflict must be resolved first'}`;\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'pending',\n reviewNotes: message,\n });\n return { success: false, gated: true, message };\n }\n\n // Clear feedback from any previous review cycle so the work agent only\n // sees current-cycle feedback when it reads .pan/feedback/.\n try {\n const { archiveFeedbackFiles } = await import('./feedback-writer.js');\n await Effect.runPromise(archiveFeedbackFiles(opts.workspace));\n } catch {\n // Non-fatal: archiving is best-effort\n }\n\n // PAN-1531: review-temp stash machinery removed. Reviewers see only the\n // committed diff because the dirty-worktree gate refuses pan done /\n // pan review request before reaching here. If callers somehow bypass the\n // gate, uncommitted scratch becomes visible in the review — that's the\n // correct fail-loud behavior, not a reason to silently stash.\n const convergence = await convergeRowFromVerdictOfRecord(opts.issueId, {\n runId: getAgentStateSync(reviewSessionName)?.reviewRunId,\n workspacePath: opts.workspace,\n writer: 'dispatch-converge',\n });\n if (convergence.converged) {\n const message = `Review dispatch converged from the verdict of record: ${opts.issueId}`;\n emitActivityEntrySync({ source: 'review', level: 'info', message, issueId: opts.issueId });\n return { success: true, message };\n }\n\n try {\n const currentStatus = getReviewStatusSync(opts.issueId);\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'reviewing',\n reviewSpawnedAt: new Date().toISOString(),\n ...clearSupersededReviewInfrastructureFailure(currentStatus),\n });\n } catch (err) {\n console.error(`[review-agent] Failed to set reviewing status for ${opts.issueId}:`, err);\n return {\n success: false,\n message: 'Failed to initialize review status',\n error: err instanceof Error ? err.message : String(err),\n };\n }\n\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({ type: 'task_queued', specialist: 'review-agent', issueId: opts.issueId });\n } catch {\n // Non-fatal\n }\n\n try {\n const { spawnRun, saveAgentState, getAgentState, getAgentStateSync, getLatestSessionIdSync, resumeAgent, wipeAgentStateDirs } = await import('../agents.js');\n const workAgentState = await Effect.runPromise(getAgentState(`agent-${opts.issueId.toLowerCase()}`));\n const allowHost = opts.allowHost === true || workAgentState?.hostOverride === true;\n\n // Build the shared context manifest before spawning so all reviewers\n // read one pre-built diff+AC object instead of each running git diff\n // independently (PAN-1059).\n //\n // Include the shared workspace-head digest in runId so re-reviews of the\n // same issue get separate directories. Monorepos retain their short HEAD;\n // polyrepos hash the full composite anchor so any sub-repo move changes it.\n const head8 = await deriveReviewRunHead8(opts.issueId, opts.workspace);\n const runId = head8 !== 'unknown'\n ? `agent-${opts.issueId.toLowerCase()}-review-${head8}`\n : `agent-${opts.issueId.toLowerCase()}-review`;\n const reviewDir = join(opts.workspace, PAN_DIRNAME, 'review', runId);\n let contextManifestPath: string | undefined;\n let tier1Summary: string | undefined;\n try {\n const manifest = await Effect.runPromise(buildReviewContext({\n runId,\n issueId: opts.issueId,\n workspace: opts.workspace,\n branch: opts.branch,\n }));\n contextManifestPath = manifest.manifestPath;\n tier1Summary = formatTier1Summary(manifest);\n console.log(`[review-agent] Context manifest built: ${contextManifestPath} (${manifest.changedFiles.length} files)`);\n } catch (ctxErr) {\n console.warn(`[review-agent] Context manifest build failed for ${opts.issueId} — reviewers will block on missing shared context:`, ctxErr);\n }\n\n const fullReview = isExtendedReviewEnabled(opts.issueId);\n\n const prompt = fullReview\n ? buildReviewRolePrompt({ ...opts, runId, reviewDir, contextManifestPath, tier1Summary })\n : buildSelfReviewPrompt({ ...opts, runId, reviewDir, contextManifestPath, tier1Summary });\n\n const spawnConvoyReviewers = (synthesisAgentId: string) =>\n launchConvoyReviewersPromise({\n issueId: opts.issueId,\n workspace: opts.workspace,\n runId,\n synthesisAgentId,\n contextManifestPath,\n ...(opts.model ? { model: opts.model } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n allowHost,\n });\n\n // PAN-1862: RESUME the saved review session by default. The review agent keeps the prior\n // review's context (the files it read, the findings it raised), so a re-review checks the\n // fix instead of re-researching the entire diff from scratch — the token-cost problem this\n // was set out to fix. Fresh-spawn ONLY when the harness/model actually changed (it's a\n // different agent then) or there is no resumable saved session. The resume delivery is\n // resilient (supervisor → tmux fallback, PAN-1988).\n const reviewAgentId = `agent-${opts.issueId.toLowerCase()}-review`;\n const savedReview = getAgentStateSync(reviewAgentId);\n const canResumeReview = reviewResumeDecision({\n requestedModel: opts.model,\n requestedHarness: opts.harness,\n savedModel: savedReview?.model,\n savedHarness: savedReview?.harness,\n hasSavedState: !!savedReview,\n hasSavedSession: !!getLatestSessionIdSync(reviewAgentId),\n });\n if (canResumeReview) {\n console.log(`[review-agent] Resuming saved review session for ${opts.issueId} — model/harness unchanged, preserving context (PAN-1862)`);\n const resumeResult = await resumeAgent(reviewAgentId, prompt);\n if (resumeResult.success) {\n try {\n // Keep the idempotency guard's HEAD-staleness detection honest for the resumed run.\n const resumed = getAgentStateSync(reviewAgentId);\n if (resumed) {\n resumed.reviewRunId = runId;\n // PAN-2584: arm the parent's liveness deadline for this cycle.\n resumed.reviewDeadlineAt = new Date(Date.now() + PARENT_REVIEW_TIMEOUT_MS).toISOString();\n await Effect.runPromise(saveAgentState(resumed));\n }\n } catch { /* non-fatal */ }\n if (fullReview) {\n if (savedReview?.reviewRunId === runId) {\n // PAN-3368: this is recovery of the current run, not a new review cycle.\n // Re-dispatch only lanes with neither a live session nor a report; completed\n // siblings stay intact and the synthesis parent can proceed immediately.\n const recovery = await recoverMissingConvoyReviewers(opts.issueId, {\n source: 'same-run parent resume',\n ...(opts.model ? { model: opts.model } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n });\n if (!recovery.success) {\n return {\n success: false,\n message: `Convoy review resumed, but missing reviewer recovery failed: ${recovery.message}`,\n error: recovery.message,\n };\n }\n } else {\n await spawnConvoyReviewers(reviewAgentId);\n }\n return { success: true, message: `Convoy review resumed (session preserved): ${reviewAgentId}` };\n }\n return { success: true, message: `Review resumed (session preserved): ${reviewAgentId}` };\n }\n console.warn(`[review-agent] Review resume failed for ${reviewAgentId}; falling back to a fresh session: ${resumeResult.error}`);\n }\n // Fresh review: wipe any stale review state (harness/model changed, or the resume above\n // failed) so the new session does not inherit a mismatched saved session id.\n if (savedReview || getLatestSessionIdSync(reviewAgentId)) {\n try { await wipeAgentStateDirs(opts.issueId, { rolePrefix: 'review' }); }\n catch (wipeErr) { console.warn(`[review-agent] review state wipe before fresh spawn failed (non-fatal): ${wipeErr instanceof Error ? wipeErr.message : String(wipeErr)}`); }\n }\n const run = await spawnRun(opts.issueId, 'review', {\n workspace: opts.workspace,\n prompt,\n ...(opts.model ? { model: opts.model } : {}),\n ...(opts.harness ? { harness: opts.harness } : {}),\n ...(allowHost ? { allowHost: true } : {}),\n startedBy: 'review-agent',\n });\n // Persist the runId on the synthesis agent's own state so the idempotency\n // guard above can tell a genuinely-running review (runId matches current\n // HEAD) from a finished-but-idle leftover (runId from an older HEAD) — see\n // PAN-1131. Sub-reviewers already persist this; the synthesis agent did not.\n run.reviewRunId = runId;\n // PAN-2584: arm the parent's liveness deadline for this cycle.\n run.reviewDeadlineAt = new Date(Date.now() + PARENT_REVIEW_TIMEOUT_MS).toISOString();\n try {\n await Effect.runPromise(saveAgentState(run));\n } catch (saveErr) {\n console.warn(`[review-agent] Could not persist reviewRunId on ${run.id}:`, saveErr);\n }\n if (fullReview) {\n await spawnConvoyReviewers(run.id);\n console.log(`[review-agent] Review role (convoy synthesis) spawned for ${opts.issueId}: ${run.id}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message: `Convoy review spawned for ${opts.issueId}: ${run.id}`, issueId: opts.issueId });\n return {\n success: true,\n message: `Convoy review spawned: ${run.id}`,\n };\n }\n\n console.log(`[review-agent] Review role (self-review) spawned for ${opts.issueId}: ${run.id}`);\n emitActivityEntrySync({ source: 'review', level: 'info', message: `Self-review spawned for ${opts.issueId}: ${run.id}`, issueId: opts.issueId });\n\n return {\n success: true,\n message: `Self-review spawned: ${run.id}`,\n };\n } catch (err) {\n console.error(`[review-agent] Failed to spawn review role for ${opts.issueId}:`, err);\n // PAN-3674: tear down the half-started runtime this dispatch created so a\n // later resume does not attach to a wedge — on 2026-08-13 the PAN-3668\n // orchestrator's app-server host sat alive-but-socketless in its pane for\n // 5h after a readiness timeout, invisible to every liveness reader (its\n // state row still said 'starting'). Only tear down what THIS dispatch\n // created: an older startedAt means a pre-existing session that must not\n // be killed here.\n try {\n const orphan = getAgentStateSync(reviewSessionName);\n const orphanStartedMs = orphan?.startedAt ? Date.parse(orphan.startedAt) : Number.NaN;\n const createdByThisDispatch = orphan !== null && orphan !== undefined\n && Number.isFinite(orphanStartedMs) && orphanStartedMs >= dispatchStartedAtMs - 5_000;\n if (createdByThisDispatch) {\n const sessions = await Effect.runPromise(listSessionNames()).catch(() => [] as string[]);\n if (sessions.includes(reviewSessionName)) {\n await Effect.runPromise(killSession(reviewSessionName)).catch(() => {});\n }\n orphan.status = 'error';\n const { saveAgentState } = await import('../agents.js');\n await Effect.runPromise(saveAgentState(orphan)).catch(() => {});\n }\n } catch { /* teardown is best-effort; the status write below still lands */ }\n setReviewStatusSync(opts.issueId, {\n reviewStatus: 'failed',\n reviewNotes: `Review role spawn failed: ${err instanceof Error ? err.message : String(err)}`,\n });\n return {\n success: false,\n message: 'Failed to spawn review role',\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Kill all canonical reviewer sessions for one issue.\n *\n * PAN-915: this is no longer called per-round. Canonical reviewer sessions\n * persist across review rounds via PAN-830's `remain-on-exit on` so each\n * round resumes the same Claude process via `sendKeysAsync` — preserving\n * the reviewer's accumulated context (codebase patterns, prior findings,\n * decisions made during earlier rounds). This function is now invoked from\n * terminal lifecycle events: merge complete, reset, cancel, deep-wipe, and\n * explicit `pan review abort`.\n *\n * Matches the parent review role, convoy children, and legacy coordinator\n * sessions so callers do not need to know which review phase has started.\n */\nexport { isReviewSessionForIssue };\n\nasync function killAllReviewerSessionsPromise(\n projectKey: string | undefined,\n issueId: string,\n): Promise<{ killed: string[]; failed: string[] }> {\n const killed: string[] = [];\n const failed: string[] = [];\n let allSessions: readonly string[];\n\n try {\n allSessions = await Effect.runPromise(listSessionNames());\n } catch (err) {\n console.warn('[review-agent] Failed to list tmux sessions during reviewer cleanup:', err instanceof Error ? err.message : String(err));\n return {\n killed,\n failed: [`agent-${issueId.toLowerCase()}-review`],\n };\n }\n\n const sessionsToKill = allSessions.filter(s => isReviewSessionForIssue(s, projectKey, issueId));\n await Promise.all(\n sessionsToKill.map(async (sessionName) => {\n try {\n await Effect.runPromise(killSession(sessionName));\n console.log(`[review-agent] Killed reviewer session ${sessionName}`);\n killed.push(sessionName);\n } catch (err) {\n console.log(`[review-agent] Session ${sessionName} already gone or failed to kill: ${err instanceof Error ? err.message : String(err)}`);\n failed.push(sessionName);\n }\n }),\n );\n return { killed, failed };\n}async function killAllReviewSessionsPromise(): Promise<{ killed: string[]; failed: string[] }> {\n const killed: string[] = [];\n const failed: string[] = [];\n\n let allSessions: readonly string[];\n try {\n allSessions = await Effect.runPromise(listSessionNames());\n } catch (err) {\n console.warn('[review-agent] Failed to list tmux sessions during review cleanup:', err instanceof Error ? err.message : String(err));\n return { killed, failed };\n }\n\n const reviewPatterns = [\n /^agent-[a-z0-9-]+-review(?:-(?:security|correctness|performance|requirements))?$/i,\n /^review-coordinator-/,\n /^specialist-.+-review-/,\n /^review-[A-Z0-9]+-\\d+-\\d+/, // legacy: review-PAN-999-1713456789000-correctness\n ];\n\n const sessionsToKill = allSessions.filter(s => reviewPatterns.some(p => p.test(s)));\n if (sessionsToKill.length === 0) {\n return { killed, failed };\n }\n\n console.log(`[review-agent] Killing ${sessionsToKill.length} review session(s) during shutdown`);\n\n await Promise.all(\n sessionsToKill.map(async (sessionName) => {\n try {\n await Effect.runPromise(killSession(sessionName));\n console.log(`[review-agent] Killed review session ${sessionName}`);\n killed.push(sessionName);\n } catch (err) {\n console.log(`[review-agent] Session ${sessionName} already gone or failed to kill: ${err instanceof Error ? err.message : String(err)}`);\n failed.push(sessionName);\n }\n }),\n );\n\n return { killed, failed };\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link buildConvoyPrompt}. Template reads are the only\n * fallible step; any failure here is fatal and propagates via Effect's defect\n * channel through `Effect.promise`.\n */\n// PAN-2695: dispatch has multiple legitimate callers (request route, deacon\n// reconcile, dispatch reconcile) that can fire near-simultaneously. An\n// uncoalesced second invocation sees the first's milliseconds-old agent state,\n// takes the PAN-1862 resume path against a parent that is still booting, and\n// kills it with the synthesis kickoff undelivered. Coalesce per issue: a\n// concurrent caller awaits the in-flight dispatch's result instead of re-entering.\nconst reviewDispatchCoalescer = createPromiseCoalescer<{ success: boolean; message: string; error?: string; gated?: boolean }>();\n\nexport const spawnReviewRoleForIssue = (\n opts: { issueId: string; workspace: string; branch: string; prUrl?: string; model?: string; harness?: RuntimeName; force?: boolean; allowHost?: boolean },\n): Effect.Effect<{ success: boolean; message: string; error?: string; gated?: boolean }> =>\n Effect.promise(() => {\n const key = opts.issueId.toUpperCase();\n if (reviewDispatchCoalescer.isInFlight(key)) {\n console.log(`[review-agent] Review dispatch already in flight for ${key} — coalescing concurrent dispatch (PAN-2695)`);\n }\n return reviewDispatchCoalescer.run(\n key,\n () => withReviewLifecycleGuard(key, () => spawnReviewRoleForIssuePromise(opts)),\n );\n });\n\n/**\n * Effect variant of {@link killAllReviewerSessions}. Session-kill failures are\n * already aggregated into the `failed` array — this wrapper preserves that\n * contract.\n */\nexport const killAllReviewerSessions = (\n projectKey: string | undefined,\n issueId: string,\n): Effect.Effect<{ killed: string[]; failed: string[] }> =>\n Effect.promise(() => killAllReviewerSessionsPromise(projectKey, issueId));\n\n/**\n * Effect variant of {@link killAllReviewSessions}. Same aggregation semantics\n * as the Promise version.\n */\nexport const killAllReviewSessions = (): Effect.Effect<{ killed: string[]; failed: string[] }> =>\n Effect.promise(() => killAllReviewSessionsPromise());\n\n// PAN-1862 resume-vs-fresh decision lives in its own pure module (review-resume-decision.ts) so\n// it is unit-testable without importing this heavy file. Re-exported for external callers.\nexport { reviewResumeDecision } from './review-resume-decision.js';\n// PAN-1862: convoy machinery lives in review-convoy.ts; re-exported here so the\n// deacon modules, tests, and the CLI keep one stable import surface.\nexport {\n buildConvoyPrompt,\n spawnReviewSubRoleForIssue,\n recoverMissingConvoyReviewers,\n} from './review-convoy.js';\n\n/**\n * Is the issue carrying leftover EXTENDED-review (convoy) sub-reviewer agents from a\n * prior cycle? PAN-2697: full-convoy review runs today, so \"any sub-reviewer exists\"\n * (the old quick-mode assumption) false-flagged every legitimate convoy — and the\n * always-on review supervisor matched the prefix too. A sub-reviewer is stale only\n * when its reviewRunId differs from the parent's active run.\n */\nexport function isReviewStaleSync(issueId: string): boolean {\n const issueLower = issueId.toLowerCase();\n const prefix = `agent-${issueLower}-review-`;\n const parentRunId = getAgentStateFileSync(`agent-${issueLower}-review`)?.reviewRunId;\n return listAgentIdsByPrefixSync(prefix).some((id) => {\n const subRole = id.slice(prefix.length);\n if (!(REVIEW_SUB_ROLES as readonly string[]).includes(subRole)) return false;\n return !parentRunId || getAgentStateFileSync(id)?.reviewRunId !== parentRunId;\n });\n}\n\nexport function resolveReviewMode(issueId?: string): ReviewMode {\n if (issueId) {\n const project = resolveProjectForIssue(issueId);\n const issueMode = project ? readIssueRecordSync(project, issueId)?.reviewMode : undefined;\n if (issueMode === 'quick' || issueMode === 'full' || issueMode === 'none') {\n return issueMode;\n }\n }\n\n const configMode = loadYamlConfig().config.roles?.review?.mode;\n return configMode === 'full' || configMode === 'none' ? configMode : 'quick';\n}\n\n/**\n * Is EXTENDED (convoy) review enabled for this issue?\n *\n * `resolveReviewMode` is the single source of truth: per-issue record override\n * beats merged project/global config, and quick remains the default.\n */\nexport function isExtendedReviewEnabled(issueId?: string): boolean {\n return resolveReviewMode(issueId) === 'full';\n}\n\n/**\n * Tear down an issue's entire review fleet — the `agent-<id>-review` parent plus any\n * extended-mode sub-reviewers. Kills every review tmux session, then removes each agent\n * through the canonical transcript-preserving removal path. Does NOT reset review_status —\n * the caller composes that (see the\n * POST /api/review/:id/purge route). Returns what was killed and removed.\n */\nexport async function purgeReviewAgentsForIssue(\n projectKey: string | undefined,\n issueId: string,\n): Promise<{ killed: string[]; removed: string[] }> {\n const killResult = await killAllReviewerSessionsPromise(projectKey, issueId);\n const removed: string[] = [];\n for (const agentId of listAgentIdsByPrefixSync(`agent-${issueId.toLowerCase()}-review`)) {\n await removeAgent(agentId);\n removed.push(agentId);\n }\n return { killed: killResult.killed, removed };\n}\n"],"mappings":"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"}
|