@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":"merge-agent-Drufc7tU.js","names":["resolve","dirname","_completedPostMerge","_postMergeInFlight","writeFile"],"sources":["../src/lib/cloister/merge-agent-step0.ts","../src/lib/cloister/merged-docker-cleanup-worker.ts","../src/lib/cloister/post-merge-lifecycle-lock.ts","../src/lib/cloister/sync-main-git.ts","../src/lib/cloister/sync-main-workspace.ts","../src/lib/overdeck/git-activity.ts","../src/lib/git-activity.ts","../src/lib/git/operations.ts","../src/lib/cloister/merge-agent.ts"],"sourcesContent":["import type { DeployWindowAssessment } from '../deploy/deploy-window.js';\nimport type { BuildStaleness } from '../deploy/staleness.js';\n\ninterface Step0Dependencies {\n readonly computeStaleness: () => Promise<BuildStaleness>;\n readonly getWindowAssessment: () => Promise<DeployWindowAssessment>;\n readonly recordIntent: typeof import('../deploy/deploy-queue.js').recordDeployIntent;\n readonly log: (message: string) => void;\n}\n\nexport async function shouldRestartForPostMerge(\n repoRoot: string,\n dependencies: Partial<Step0Dependencies> = {},\n): Promise<boolean> {\n const computeStaleness = dependencies.computeStaleness ?? (async () => {\n const [{ getBuildInfo }, { computeBuildStaleness }] = await Promise.all([\n import('../deploy/build-info.js'),\n import('../deploy/staleness.js'),\n ]);\n return computeBuildStaleness({ repoRoot, buildCommit: getBuildInfo().buildCommit });\n });\n const staleness = await computeStaleness();\n\n if (staleness.status === 'fresh') {\n (dependencies.log ?? console.log)(\n 'Running build already contains origin/main build inputs — skipping deploy restart',\n );\n return false;\n }\n\n const getWindowAssessment = dependencies.getWindowAssessment ?? (async () =>\n (await import('../deploy/deploy-window.js')).getDeployWindowAssessment());\n const assessment = await getWindowAssessment();\n if (assessment.reason) {\n const recordIntent = dependencies.recordIntent\n ?? (await import('../deploy/deploy-queue.js')).recordDeployIntent;\n await recordIntent({\n requestedBy: 'merge-step0',\n reason: assessment.reason,\n blockedBy: [],\n });\n (dependencies.log ?? console.log)(\n `Deploy window unsafe (${assessment.reason}) — deferring deploy to the staleness patrol`,\n );\n return false;\n }\n\n return true;\n}\n","import { emitActivityEntrySync } from '../activity-logger.js';\nimport { resolveCanonicalReviewStatus } from './review-status-source.js';\n\nconst RETRY_BASE_MS = 60_000;\nconst RETRY_MAX_MS = 15 * 60_000;\n\ninterface CleanupEntry {\n issueId: string;\n attempts: number;\n nextAttemptAt: number;\n running: boolean;\n mergeVerified: boolean;\n}\n\ninterface EnqueueOptions {\n /** Trust the merge-agent's just-completed merge verification for the first retry. */\n mergeVerified?: boolean;\n}\n\nconst queue = new Map<string, CleanupEntry>();\nlet workerPromise: Promise<void> | null = null;\n\nfunction retryDelayMs(attempts: number): number {\n return Math.min(RETRY_BASE_MS * 2 ** Math.max(0, attempts - 1), RETRY_MAX_MS);\n}\n\nfunction nextEligibleEntry(): CleanupEntry | null {\n const now = Date.now();\n return [...queue.values()].find((entry) => !entry.running && entry.nextAttemptAt <= now) ?? null;\n}\n\nfunction recordSuccess(issueId: string, steps: string[]): void {\n const message = `Removed merged-issue Docker stack/network for ${issueId}: ${steps.join('; ')}`;\n console.log(`[deacon] ${message}`);\n try {\n emitActivityEntrySync({ source: 'cloister', level: 'info', issueId, message: `[deacon] ${message}` });\n } catch (error) {\n console.warn(`[deacon] Could not record merged Docker cleanup activity for ${issueId}: ${error}`);\n }\n}\n\nfunction recordFailure(entry: CleanupEntry, reason: string): void {\n entry.attempts += 1;\n entry.nextAttemptAt = Date.now() + retryDelayMs(entry.attempts);\n entry.running = false;\n console.warn(\n `[deacon] Merged-issue Docker cleanup failed for ${entry.issueId}; ` +\n `retry ${entry.attempts} after ${new Date(entry.nextAttemptAt).toISOString()}: ${reason}`,\n );\n}\n\nasync function drainQueue(): Promise<void> {\n const { teardownWorkspaceDockerByNamePromise } = await import('../workspace-manager/docker.js');\n for (let entry = nextEligibleEntry(); entry; entry = nextEligibleEntry()) {\n entry.running = true;\n const eligibility = resolveCanonicalReviewStatus(entry.issueId);\n if (eligibility.status?.mergeStatus === 'merged') {\n entry.mergeVerified = false;\n } else if (entry.mergeVerified) {\n // The merge-agent verified the merge immediately before enqueueing. Consume\n // that proof once so a failed status write cannot cancel the first retry.\n entry.mergeVerified = false;\n } else if (!eligibility.available) {\n recordFailure(entry, 'canonical merge status unavailable');\n continue;\n } else {\n queue.delete(entry.issueId);\n console.log(`[deacon] Cancelled merged Docker cleanup for ${entry.issueId} — issue is no longer merged`);\n continue;\n }\n try {\n const result = await teardownWorkspaceDockerByNamePromise(entry.issueId.toLowerCase());\n if (result.networkRemoved) {\n queue.delete(entry.issueId);\n recordSuccess(entry.issueId, result.steps);\n } else {\n recordFailure(entry, result.steps.join('; '));\n }\n } catch (error) {\n recordFailure(entry, error instanceof Error ? error.message : String(error));\n }\n }\n}\n\nfunction startWorker(): void {\n if (workerPromise || !nextEligibleEntry()) return;\n workerPromise = drainQueue()\n .catch((error) => {\n const reason = error instanceof Error ? error.message : String(error);\n for (const entry of queue.values()) {\n if (!entry.running && entry.nextAttemptAt <= Date.now()) recordFailure(entry, reason);\n }\n })\n .finally(() => {\n workerPromise = null;\n if (nextEligibleEntry()) startWorker();\n });\n}\n\nexport function reconcileMergedDockerCleanupQueue(eligibleIssueIds: string[]): string[] {\n const eligible = new Set(\n eligibleIssueIds.map((issueId) => issueId.trim().toUpperCase()).filter(Boolean),\n );\n for (const [issueId, entry] of queue) {\n if (!entry.running && !eligible.has(issueId)) queue.delete(issueId);\n }\n return [...eligible].flatMap((issueId) => {\n const action = enqueueMergedDockerCleanup(issueId);\n return action ? [action] : [];\n });\n}\n\nexport function enqueueMergedDockerCleanup(\n issueId: string,\n options?: EnqueueOptions,\n): string | null {\n const normalizedIssueId = issueId.trim().toUpperCase();\n if (!normalizedIssueId) return null;\n const existing = queue.get(normalizedIssueId);\n if (!existing) {\n queue.set(normalizedIssueId, {\n issueId: normalizedIssueId,\n attempts: 0,\n nextAttemptAt: 0,\n running: false,\n mergeVerified: options?.mergeVerified === true,\n });\n startWorker();\n return `Queued merged-issue Docker cleanup for ${normalizedIssueId}`;\n }\n if (options?.mergeVerified) existing.mergeVerified = true;\n startWorker();\n return null;\n}\n\nexport async function waitForMergedDockerCleanupIdleForTests(): Promise<void> {\n while (workerPromise) await workerPromise;\n}\n\nexport async function resetMergedDockerCleanupWorkerForTests(): Promise<void> {\n await waitForMergedDockerCleanupIdleForTests();\n queue.clear();\n}\n\nexport function getMergedDockerCleanupStateForTests(issueId: string): CleanupEntry | null {\n const entry = queue.get(issueId.toUpperCase());\n return entry ? { ...entry } : null;\n}\n","import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\n\nimport { parseIssueIdSync } from '../issue-id.js';\nimport { OVERDECK_HOME } from '../paths.js';\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isMissing(error: unknown): boolean {\n return (error as NodeJS.ErrnoException)?.code === 'ENOENT';\n}\n\nfunction isAlreadyExists(error: unknown): boolean {\n return (error as NodeJS.ErrnoException)?.code === 'EEXIST';\n}\n\nexport async function acquirePostMergeLifecycleLock(\n issueId: string,\n): Promise<(() => Promise<void>) | null> {\n const trimmedIssueId = issueId.trim();\n const parsedIssueId = parseIssueIdSync(trimmedIssueId);\n if (!parsedIssueId) {\n throw new Error(`Invalid issue ID for post-merge lifecycle lock: ${issueId}`);\n }\n\n const lockDir = resolve(OVERDECK_HOME, 'locks', 'post-merge-lifecycle');\n const lockPath = resolve(lockDir, `${parsedIssueId.normalized.toUpperCase()}.lock`);\n if (dirname(lockPath) !== lockDir) {\n throw new Error(`Post-merge lifecycle lock escaped its lock directory: ${issueId}`);\n }\n await mkdir(lockDir, { recursive: true });\n\n for (let attempt = 0; attempt < 2; attempt += 1) {\n try {\n await writeFile(lockPath, String(process.pid), { flag: 'wx', mode: 0o600 });\n let released = false;\n return async () => {\n if (released) return;\n released = true;\n try {\n await unlink(lockPath);\n } catch (error) {\n if (!isMissing(error)) throw error;\n }\n };\n } catch (error) {\n if (!isAlreadyExists(error)) throw error;\n let ownerPid: number;\n try {\n ownerPid = Number.parseInt((await readFile(lockPath, 'utf-8')).trim(), 10);\n } catch (readError) {\n if (isMissing(readError)) continue;\n return null;\n }\n if (!Number.isInteger(ownerPid) || ownerPid <= 0 || isProcessAlive(ownerPid)) return null;\n try {\n await unlink(lockPath);\n } catch (unlinkError) {\n if (!isMissing(unlinkError)) return null;\n }\n }\n }\n return null;\n}\n\nexport async function withPostMergeLifecycleLock<T>(\n issueId: string,\n run: () => Promise<T>,\n): Promise<T> {\n const release = await acquirePostMergeLifecycleLock(issueId);\n if (!release) throw new Error(`postMergeLifecycle already running in another process for ${issueId}`);\n try {\n return await run();\n } finally {\n await release();\n }\n}\n","import { exec, type ChildProcess, type ExecException } from 'child_process';\nimport { Effect } from 'effect';\nimport { cleanupStaleLocks } from '../git-utils.js';\n\nexport const GIT_OPERATION_HEADS = [\n 'MERGE_HEAD',\n 'REBASE_HEAD',\n 'CHERRY_PICK_HEAD',\n 'REVERT_HEAD',\n] as const;\n\nexport class SyncGitCommandTimeoutError extends Error {\n readonly killed = true;\n\n constructor(\n public readonly command: string,\n public readonly budgetMs: number,\n ) {\n super(`${command} timed out after ${budgetMs / 1_000}s`);\n this.name = 'SyncGitCommandTimeoutError';\n }\n}\n\nexport class SyncGitCommandAbortError extends Error {\n readonly code = 'ABORT_ERR';\n readonly killed = true;\n\n constructor(public readonly command: string) {\n super(`${command} was cancelled`);\n this.name = 'SyncGitCommandAbortError';\n }\n}\n\nexport class UnsafeSyncMainStateError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UnsafeSyncMainStateError';\n }\n}\n\ninterface SyncGitExecOptions {\n cwd: string;\n timeout: number;\n signal?: AbortSignal;\n}\n\nconst execDetached = exec as unknown as (\n command: string,\n options: { cwd: string; encoding: BufferEncoding; detached: boolean; timeout: number },\n callback: (error: ExecException | null, stdout: string, stderr: string) => void,\n) => ChildProcess;\n\nfunction killCommandTree(child: ChildProcess | undefined): void {\n if (!child?.pid) return;\n try {\n if (process.platform === 'win32') child.kill('SIGKILL');\n else process.kill(-child.pid, 'SIGKILL');\n } catch {\n try { child.kill('SIGKILL'); } catch { /* process already exited */ }\n }\n}\n\nexport function runSyncGitCommand(\n command: string,\n options: SyncGitExecOptions,\n): Promise<{ stdout: string; stderr: string }> {\n if (options.signal?.aborted) return Promise.reject(new SyncGitCommandAbortError(command));\n\n return new Promise((resolve, reject) => {\n let child: ChildProcess | undefined;\n let termination: 'timeout' | 'abort' | undefined;\n const finishTermination = (kind: 'timeout' | 'abort') => {\n if (termination) return;\n termination = kind;\n killCommandTree(child);\n };\n const timeout = setTimeout(() => finishTermination('timeout'), options.timeout);\n const onAbort = () => finishTermination('abort');\n options.signal?.addEventListener('abort', onAbort, { once: true });\n\n child = execDetached(command, {\n cwd: options.cwd,\n encoding: 'utf-8',\n detached: process.platform !== 'win32',\n timeout: options.timeout,\n }, (error, stdout, stderr) => {\n clearTimeout(timeout);\n options.signal?.removeEventListener('abort', onAbort);\n if (termination === 'abort') {\n reject(Object.assign(new SyncGitCommandAbortError(command), { stdout, stderr }));\n } else if (termination === 'timeout') {\n reject(Object.assign(new SyncGitCommandTimeoutError(command, options.timeout), { stdout, stderr }));\n } else if (error?.killed) {\n reject(Object.assign(new SyncGitCommandTimeoutError(command, options.timeout), { stdout, stderr }));\n } else if (error) {\n reject(Object.assign(error, { stdout, stderr }));\n } else {\n resolve({ stdout, stderr });\n }\n });\n if (options.signal?.aborted) finishTermination('abort');\n });\n}\n\nexport interface OperationHeadProbeResult {\n success: boolean;\n present: string[];\n reason?: string;\n}\n\nexport async function probeGitOperationHeads(\n projectPath: string,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<OperationHeadProbeResult> {\n const present: string[] = [];\n for (const operationHead of GIT_OPERATION_HEADS) {\n try {\n await runSyncGitCommand(`git rev-parse -q --verify ${operationHead}`, {\n cwd: projectPath,\n timeout: timeoutMs,\n signal,\n });\n present.push(operationHead);\n } catch (error) {\n if (error instanceof SyncGitCommandAbortError) throw error;\n if (error instanceof SyncGitCommandTimeoutError) {\n return { success: false, present, reason: `${operationHead} probe timed out after ${timeoutMs / 1_000}s` };\n }\n if ((error as { code?: number | string } | null)?.code !== 1) {\n const message = error instanceof Error ? error.message : String(error);\n return { success: false, present, reason: `${operationHead} probe failed: ${message}` };\n }\n }\n }\n return { success: true, present };\n}\n\nexport async function ensureSyncGitQuiescent(\n projectPath: string,\n abortMerge: boolean,\n timeoutMs = 30_000,\n): Promise<void> {\n const controller = new AbortController();\n const deadline = Date.now() + timeoutMs;\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n const remaining = (stage: string): number => {\n const budget = deadline - Date.now();\n if (controller.signal.aborted || budget <= 0) {\n throw new UnsafeSyncMainStateError(\n `Git cleanup timed out after ${timeoutMs / 1_000}s while ${stage}`,\n );\n }\n return budget;\n };\n\n try {\n let lockCleanup: {\n found: string[];\n removed: string[];\n errors: Array<{ file: string; error: string }>;\n };\n try {\n lockCleanup = await Effect.runPromise(cleanupStaleLocks(projectPath, {\n signal: controller.signal,\n processProbeTimeoutMs: remaining('checking Git lock owners'),\n }));\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git lock state after cancellation: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n if (lockCleanup.errors.length > 0) {\n throw new UnsafeSyncMainStateError(\n `Could not establish Git quiescence: ${lockCleanup.errors\n .map(({ file, error }) => `${file}: ${error}`)\n .join('; ')}`,\n );\n }\n\n let before: OperationHeadProbeResult;\n try {\n before = await probeGitOperationHeads(\n projectPath,\n remaining('checking Git operation state'),\n controller.signal,\n );\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n if (!before.success) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state: ${before.reason}`,\n );\n }\n const nonMergeHeads = before.present.filter((head) => head !== 'MERGE_HEAD');\n if (nonMergeHeads.length > 0\n || (before.present.includes('MERGE_HEAD') && !abortMerge)) {\n throw new UnsafeSyncMainStateError(\n `Git operation remains active after cancellation: ${before.present.join(', ')}`,\n );\n }\n if (before.present.includes('MERGE_HEAD')) {\n try {\n await runSyncGitCommand('git merge --abort', {\n cwd: projectPath,\n timeout: remaining('aborting the timed-out merge'),\n signal: controller.signal,\n });\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not abort timed-out sync merge: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n let after: OperationHeadProbeResult;\n try {\n after = await probeGitOperationHeads(\n projectPath,\n remaining('verifying Git cleanup'),\n controller.signal,\n );\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state after cleanup: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n if (!after.success) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state after cleanup: ${after.reason}`,\n );\n }\n if (after.present.length > 0) {\n throw new UnsafeSyncMainStateError(\n `Git operation remains active after cleanup: ${after.present.join(', ')}`,\n );\n }\n } finally {\n clearTimeout(timeout);\n }\n}\n","import { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\n\nexport interface SyncMainRepoResult {\n repoKey: string;\n success: boolean;\n alreadyUpToDate?: boolean;\n commitCount?: number;\n changedFiles?: string[];\n conflictFiles?: string[];\n reason?: string;\n skipped?: boolean;\n}\n\nexport interface SyncMainResult {\n success: boolean;\n alreadyUpToDate?: boolean;\n commitCount?: number;\n changedFiles?: string[];\n conflictFiles?: string[];\n reason?: string;\n repos?: SyncMainRepoResult[];\n}\n\ntype SyncRepo = (\n repoDir: string,\n issueId: string,\n targetBranch: string,\n signal?: AbortSignal,\n) => Promise<Omit<SyncMainRepoResult, 'repoKey' | 'skipped'>>;\n\ntype LogActivity = (action: string, details: string, issueId?: string) => void;\n\nexport async function syncMainAcrossWorkspaceRepos(\n projectPath: string,\n issueId: string,\n signal: AbortSignal | undefined,\n syncRepo: SyncRepo,\n logActivity: LogActivity,\n): Promise<SyncMainResult> {\n console.log(`[sync-main] Starting sync of main into workspace for ${issueId}`);\n logActivity('sync_main_start', `Starting sync for ${issueId}`);\n\n const roots = resolveWorkspaceRepoRootsSync(issueId, projectPath);\n const repos: SyncMainRepoResult[] = [];\n let failedRepoKey: string | undefined;\n\n for (const root of roots) {\n if (failedRepoKey) {\n repos.push({\n repoKey: root.repoKey,\n success: false,\n skipped: true,\n reason: `[${root.repoKey}] Skipped because sync failed in ${failedRepoKey}`,\n });\n continue;\n }\n\n if (root.isPolyrepo) {\n console.log(`[sync-main] [${root.repoKey}] Syncing origin/${root.targetBranch}`);\n logActivity('sync_main_repo_start', `[${root.repoKey}] Syncing origin/${root.targetBranch}`);\n }\n const result = await syncRepo(root.dir, issueId, root.targetBranch, signal);\n const prefixPaths = (paths: string[] | undefined) => root.isPolyrepo && paths\n ? paths.map(path => `${root.repoKey}/${path}`)\n : paths;\n const repoResult: SyncMainRepoResult = { ...result, repoKey: root.repoKey };\n if (result.changedFiles !== undefined) repoResult.changedFiles = prefixPaths(result.changedFiles);\n if (result.conflictFiles !== undefined) repoResult.conflictFiles = prefixPaths(result.conflictFiles);\n if (result.reason && root.isPolyrepo) repoResult.reason = `[${root.repoKey}] ${result.reason}`;\n repos.push(repoResult);\n if (!result.success) failedRepoKey = root.repoKey;\n }\n\n const completedRepos = repos.filter(repo => !repo.skipped);\n const changedFiles = completedRepos.flatMap(repo => repo.changedFiles ?? []);\n const conflictFiles = completedRepos.flatMap(repo => repo.conflictFiles ?? []);\n const countedRepos = completedRepos.filter(repo => repo.commitCount !== undefined);\n const failedRepo = completedRepos.find(repo => !repo.success);\n const aggregate: SyncMainResult = { success: !failedRepo, repos };\n\n if (completedRepos.length > 0 && completedRepos.every(repo => repo.alreadyUpToDate === true)) {\n aggregate.alreadyUpToDate = true;\n }\n if (countedRepos.length > 0) {\n aggregate.commitCount = countedRepos.reduce((sum, repo) => sum + (repo.commitCount ?? 0), 0);\n }\n if (completedRepos.some(repo => repo.changedFiles !== undefined)) aggregate.changedFiles = changedFiles;\n if (completedRepos.some(repo => repo.conflictFiles !== undefined)) aggregate.conflictFiles = conflictFiles;\n if (failedRepo?.reason) aggregate.reason = failedRepo.reason;\n\n return aggregate;\n}\n","/**\n * Overdeck door for git_operations — persistent log of git push/fetch/merge\n * events. Replaces database/git-operations-db.ts.\n *\n * Uses the hand-maintained overdeck migration as the schema source of truth.\n */\n\nimport { getOverdeckDatabaseSync } from './infra.js';\n\n// ─── Schema bootstrap ─────────────────────────────────────────────────────────\n\nfunction overdeckDb() {\n return getOverdeckDatabaseSync();\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type GitOperationType =\n | 'push'\n | 'force_push'\n | 'fetch'\n | 'merge'\n | 'rev_parse'\n | 'main_diverged'\n | 'push_attempt'\n | 'fetch_attempt'\n | 'push_rejected'\n | 'non_ff'\n | 'force_push_cmd'\n | 'retry'\n | 'remote_rejected'\n | 'push_noop';\n\nexport type GitOperationStatus = 'success' | 'failure' | 'aborted';\n\nexport interface GitOperation {\n id?: number;\n operation: GitOperationType;\n branch?: string;\n issueId?: string;\n beforeSha?: string;\n afterSha?: string;\n remoteSha?: string;\n status: GitOperationStatus;\n error?: string;\n ts: string;\n}\n\nexport interface GitOperationFilter {\n issueId?: string;\n operation?: GitOperationType;\n status?: GitOperationStatus;\n since?: string;\n limit?: number;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function appendGitOperationSync(op: Omit<GitOperation, 'id'>): number {\n const result = overdeckDb().prepare(`\n INSERT INTO git_operations (\n operation, branch, issue_id,\n before_sha, after_sha, remote_sha,\n status, error, ts\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n op.operation,\n op.branch ?? null,\n op.issueId ?? null,\n op.beforeSha ?? null,\n op.afterSha ?? null,\n op.remoteSha ?? null,\n op.status,\n op.error ?? null,\n new Date(op.ts).getTime(),\n );\n return result.lastInsertRowid as number;\n}\n\nexport function listGitOperationsSync(filter: GitOperationFilter = {}): GitOperation[] {\n const conditions: string[] = [];\n const params: (string | number)[] = [];\n\n if (filter.issueId) { conditions.push('issue_id = ?'); params.push(filter.issueId); }\n if (filter.operation) { conditions.push('operation = ?'); params.push(filter.operation); }\n if (filter.status) { conditions.push('status = ?'); params.push(filter.status); }\n if (filter.since) { conditions.push('ts >= ?'); params.push(new Date(filter.since).getTime()); }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = filter.limit ? `LIMIT ${filter.limit}` : 'LIMIT 500';\n\n const rows = overdeckDb().prepare(`\n SELECT id, operation, branch, issue_id, before_sha, after_sha, remote_sha, status, error, ts\n FROM git_operations ${where} ORDER BY ts DESC ${limit}\n `).all(...params) as Array<{\n id: number;\n operation: string;\n branch: string | null;\n issue_id: string | null;\n before_sha: string | null;\n after_sha: string | null;\n remote_sha: string | null;\n status: string;\n error: string | null;\n ts: number;\n }>;\n\n return rows.map((row) => ({\n id: row.id,\n operation: row.operation as GitOperationType,\n branch: row.branch ?? undefined,\n issueId: row.issue_id ?? undefined,\n beforeSha: row.before_sha ?? undefined,\n afterSha: row.after_sha ?? undefined,\n remoteSha: row.remote_sha ?? undefined,\n status: row.status as GitOperationStatus,\n error: row.error ?? undefined,\n ts: new Date(row.ts).toISOString(),\n }));\n}\n","/**\n * Git Activity Service (PAN-653)\n *\n * Write path and read path for git operation events persisted to the\n * git_operations SQLite table. Survives dashboard restart — unlike the\n * in-memory activityLog array which is capped at 100 and wiped on restart.\n *\n * All functions use the async-safe getDatabase() which runs under Node 22.\n * NEVER use execSync/readFileSync here — this runs in the dashboard server.\n */\n\nimport { Effect, Data } from 'effect';\nimport {\n appendGitOperationSync,\n listGitOperationsSync,\n} from './overdeck/git-activity.js';\nimport type {\n GitOperation,\n GitOperationFilter,\n GitOperationType,\n GitOperationStatus,\n} from './overdeck/git-activity.js';\n\nexport type { GitOperation, GitOperationFilter, GitOperationType, GitOperationStatus };\nexport { appendGitOperationSync, listGitOperationsSync };\n\n/** A database operation against the git_operations table failed. */\nexport class GitActivityDbError extends Data.TaggedError('GitActivityDbError')<{\n readonly operation: string;\n readonly cause: unknown;\n}> {}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect-native appendGitOperation — typed-error variant of the SQLite write.\n * Fails with GitActivityDbError if the insert throws (e.g. DB locked).\n */\nexport const appendGitOperation = (\n op: Omit<GitOperation, 'id'>,\n): Effect.Effect<number, GitActivityDbError> =>\n Effect.try({\n try: () => appendGitOperationSync(op),\n catch: (cause) => new GitActivityDbError({ operation: 'appendGitOperation', cause }),\n });\n\n/**\n * Effect-native listGitOperations — typed-error variant of the SQLite read.\n * Fails with GitActivityDbError on query failure.\n */\nexport const listGitOperations = (\n filter: GitOperationFilter = {},\n): Effect.Effect<readonly GitOperation[], GitActivityDbError> =>\n Effect.try({\n try: () => listGitOperationsSync(filter),\n catch: (cause) => new GitActivityDbError({ operation: 'listGitOperations', cause }),\n });\n","/**\n * Git Operations Helpers (PAN-653)\n *\n * Thin wrappers over raw git commands that:\n * 1. Use execAsync (never execSync) — safe for dashboard server use\n * 2. Record before/after/remote SHAs for each operation\n * 3. Emit structured git.* events to the git_operations SQLite table\n * 4. For gitPush: perform a pre-push fetch + ancestor check and throw\n * MainDivergedError if origin/main has advanced past the local ancestor\n *\n * Usage: import { gitPush, gitFetch, gitForcePush, gitMerge, gitRevParse }\n * from 'src/lib/git/operations.js'\n *\n * NEVER add execSync, readFileSync, writeFileSync here — this module is\n * imported by dashboard server code (CLAUDE.md rule).\n */\n\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { appendGitOperationSync } from '../git-activity.js';\nimport { GitError, VcsError } from '../errors.js';\n\nconst execFileAsync = promisify(execFile);\n\n// ============== Error types ==============\n\n/**\n * Thrown by gitPush when origin/main has advanced beyond the local ancestor.\n * The caller must NOT push — it should call markWorkspaceStuck instead.\n */\nexport class MainDivergedError extends Error {\n readonly localSha: string;\n readonly remoteSha: string;\n\n constructor(localSha: string, remoteSha: string) {\n super(\n `Main diverged: remote HEAD (${remoteSha.slice(0, 7)}) is not an ancestor of local HEAD (${localSha.slice(0, 7)}). Aborting push to protect the hotfix commit.`\n );\n this.name = 'MainDivergedError';\n this.localSha = localSha;\n this.remoteSha = remoteSha;\n }\n}async function gitRevParsePromise(cwd: string, ref: string): Promise<string | null> {\n const ts = new Date().toISOString();\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', ref], { cwd, encoding: 'utf-8' });\n const sha = stdout.trim();\n appendGitOperationSync({ operation: 'rev_parse', branch: ref, issueId: undefined, afterSha: sha, status: 'success', ts });\n return sha || null;\n } catch {\n return null;\n }\n}async function gitFetchPromise(\n cwd: string,\n remote = 'origin',\n branch?: string,\n opts: { issueId?: string } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n const args = branch ? ['fetch', remote, branch] : ['fetch', remote];\n try {\n await execFileAsync('git', args, { cwd, encoding: 'utf-8', timeout: 30000 });\n appendGitOperationSync({\n operation: 'fetch',\n branch: branch ?? remote,\n issueId: opts.issueId,\n status: 'success',\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'fetch',\n branch: branch ?? remote,\n issueId: opts.issueId,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}async function gitPushPromise(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n\n // Step 1: record the local HEAD before the push\n const localSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? 'unknown';\n\n // Step 2: fetch latest remote state\n await Effect.runPromise(gitFetch(cwd, remote, branch, opts));\n\n // Step 3: read the remote tracking SHA\n const remoteSha = await Effect.runPromise(gitRevParse(cwd, `${remote}/${branch}`)) ?? '';\n\n // Step 4: ancestor check — is remoteSha an ancestor of localSha?\n if (remoteSha) {\n try {\n // git merge-base --is-ancestor <commit> <commit> exits 0 if true, 1 if false\n await execFileAsync('git', ['merge-base', '--is-ancestor', remoteSha, localSha], {\n cwd,\n encoding: 'utf-8',\n timeout: 10000,\n });\n } catch (err: unknown) {\n // exit code 1 = \"not an ancestor\" (true divergence); any other code is a real git error\n if ((err as { code?: number }).code !== 1) throw err;\n appendGitOperationSync({\n operation: 'main_diverged',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n remoteSha,\n status: 'aborted',\n error: `origin/${branch} (${remoteSha.slice(0, 7)}) is not ancestor of local HEAD (${localSha.slice(0, 7)})`,\n ts,\n });\n throw new MainDivergedError(localSha, remoteSha);\n }\n }\n\n // Step 5: push\n try {\n await execFileAsync('git', ['push', remote, branch], { cwd, encoding: 'utf-8', timeout: 60000 });\n const afterSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? localSha;\n appendGitOperationSync({\n operation: 'push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n afterSha,\n remoteSha,\n status: 'success',\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n remoteSha,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}async function gitForcePushPromise(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string; reason?: string } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n const localSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? 'unknown';\n const remoteSha = await Effect.runPromise(gitRevParse(cwd, `${remote}/${branch}`)) ?? undefined;\n\n try {\n await execFileAsync('git', ['push', '--force-with-lease', remote, branch], {\n cwd,\n encoding: 'utf-8',\n timeout: 60000,\n });\n const afterSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? localSha;\n appendGitOperationSync({\n operation: 'force_push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n afterSha,\n remoteSha,\n status: 'success',\n error: opts.reason,\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'force_push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n remoteSha,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}async function gitMergePromise(\n cwd: string,\n branch: string,\n opts: { issueId?: string; noFf?: boolean } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n const beforeSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? 'unknown';\n const args = opts.noFf ? ['merge', '--no-ff', branch] : ['merge', branch];\n try {\n await execFileAsync('git', args, { cwd, encoding: 'utf-8', timeout: 60000 });\n const afterSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? beforeSha;\n appendGitOperationSync({\n operation: 'merge',\n branch,\n issueId: opts.issueId,\n beforeSha,\n afterSha,\n status: 'success',\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'merge',\n branch,\n issueId: opts.issueId,\n beforeSha,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}\n\n// ─── Effect variants (PAN-1249, additive) ────────────────────────────────────\n//\n// These wrap the existing Promise-based functions with typed Effect error\n// channels (GitError / VcsError). The underlying impl is unchanged; this is\n// an additive surface for callers that already speak Effect. MainDivergedError\n// is thrown by the underlying gitPush; the Effect variant surfaces it\n// preserved as the cause inside a VcsError tagged 'main-diverged'.\n\n/** Resolve a git ref to its SHA. Returns null if the ref does not exist. */\nexport function gitRevParse(\n cwd: string,\n ref: string,\n): Effect.Effect<string | null> {\n return Effect.promise(() => gitRevParsePromise(cwd, ref));\n}\n\n/** Fetch from a remote. */\nexport function gitFetch(\n cwd: string,\n remote = 'origin',\n branch?: string,\n opts: { issueId?: string } = {},\n): Effect.Effect<void, GitError> {\n return Effect.tryPromise({\n try: () => gitFetchPromise(cwd, remote, branch, opts),\n catch: (cause) =>\n new GitError({\n command: branch ? ['git', 'fetch', remote, branch] : ['git', 'fetch', remote],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n }),\n });\n}\n\n/**\n * Push a branch to a remote with divergence guard.\n * Fails with VcsError tagged 'main-diverged' when MainDivergedError is thrown.\n */\nexport function gitPush(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string } = {},\n): Effect.Effect<void, VcsError | GitError> {\n return Effect.tryPromise({\n try: () => gitPushPromise(cwd, remote, branch, opts),\n catch: (cause) => {\n if (cause instanceof MainDivergedError) {\n return new VcsError({\n operation: 'main-diverged',\n message: cause.message,\n cause,\n });\n }\n return new GitError({\n command: ['git', 'push', remote, branch],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n });\n },\n });\n}\n\n/** Force-push a branch (--force-with-lease). */\nexport function gitForcePush(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string; reason?: string } = {},\n): Effect.Effect<void, GitError> {\n return Effect.tryPromise({\n try: () => gitForcePushPromise(cwd, remote, branch, opts),\n catch: (cause) =>\n new GitError({\n command: ['git', 'push', '--force-with-lease', remote, branch],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n }),\n });\n}\n\n/** Merge a branch into the current branch. */\nexport function gitMerge(\n cwd: string,\n branch: string,\n opts: { issueId?: string; noFf?: boolean } = {},\n): Effect.Effect<void, GitError> {\n return Effect.tryPromise({\n try: () => gitMergePromise(cwd, branch, opts),\n catch: (cause) =>\n new GitError({\n command: opts.noFf ? ['git', 'merge', '--no-ff', branch] : ['git', 'merge', branch],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n }),\n });\n}\n","/**\n * Merge Agent - Automatic merge conflict resolution using Claude Code\n */\n\nimport { existsSync, mkdirSync, appendFileSync } from 'fs';\nimport { writeFile } from 'fs/promises';\nimport { join, dirname, basename, relative } from 'path';\nimport { fileURLToPath } from 'url';\nimport { spawn, exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { capturePane, killSession, listSessionNames, sendKeys, sessionExists } from '../tmux.js';\nimport { emitActivityEntrySync, emitActivityTtsSync } from '../activity-logger.js';\nimport { loadConfigSync } from '../config-yaml.js';\nimport { shouldRestartForPostMerge } from './merge-agent-step0.js'; import { capturePipelineStageForIssue } from '../telemetry/pipeline.js';\nimport { enqueueMergedDockerCleanup } from './merged-docker-cleanup-worker.js';\nimport { withPostMergeLifecycleLock } from './post-merge-lifecycle-lock.js';\nimport {\n completedPostMerge as _completedPostMerge,\n postMergeInFlight as _postMergeInFlight,\n resetPostMergeState,\n} from './post-merge-state.js';\nimport {\n ensureSyncGitQuiescent,\n probeGitOperationHeads,\n runSyncGitCommand,\n SyncGitCommandAbortError,\n SyncGitCommandTimeoutError,\n} from './sync-main-git.js';\nimport { syncMainAcrossWorkspaceRepos, type SyncMainRepoResult, type SyncMainResult } from './sync-main-workspace.js';\nexport type { SyncMainRepoResult, SyncMainResult } from './sync-main-workspace.js';\n\nconst execAsync = promisify(exec);\n\nconst SYNC_GIT_FETCH_TIMEOUT_MS = 60_000;\nconst SYNC_GIT_MERGE_TIMEOUT_MS = 120_000;\nconst SYNC_GIT_COMMIT_TIMEOUT_MS = 60_000;\nconst SYNC_GIT_STATUS_TIMEOUT_MS = 30_000;\n\n/**\n * Paths that must never enter a pipeline auto-commit, regardless of gitignore\n * state. These are workspace-local or machine-local state files and sync-target\n * directories; committing them pollutes feature branches and main.\n */\nexport const AUTO_COMMIT_EXCLUDED_PATHS = [\n '.pan/kickoff.md',\n '.pan/continue.json',\n '.pan/handoff-*.md',\n '.pan/spec.vbrief.json',\n '.claude/rules/',\n '.claude/skills/',\n // PAN-1899: machine-local Overdeck config copied into every workspace by\n // copyOverdeckSettingsToWorkspaceSync (config.yaml, projects.yaml,\n // settings.json). It diverges from main constantly and must never enter a\n // feature branch — that was the source of the recurring projects.yaml sync\n // conflict.\n '.overdeck/',\n];\n\nconst SYNC_MAIN_MAIN_PREFERRED_PATHS = [\n '.pan/continues',\n '.pan/specs',\n];\n\nexport function isSyncMainMainPreferredPath(relativePath: string): boolean {\n const normalized = relativePath.replace(/\\\\/g, '/');\n return SYNC_MAIN_MAIN_PREFERRED_PATHS.some(\n (prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`),\n );\n}\n\nexport function isAutoCommitExcludedPath(relativePath: string): boolean {\n const normalized = relativePath.replace(/\\\\/g, '/');\n for (const pattern of AUTO_COMMIT_EXCLUDED_PATHS) {\n if (pattern.endsWith('/')) {\n if (normalized.startsWith(pattern) || normalized === pattern.slice(0, -1)) {\n return true;\n }\n } else if (pattern.includes('*')) {\n const regex = new RegExp(\n '^' + pattern.replace(/\\./g, '\\\\.').replace(/\\*/g, '[^/]*') + '$'\n );\n if (regex.test(normalized)) return true;\n } else if (normalized === pattern) {\n return true;\n }\n }\n return false;\n}\n\nfunction parseStatusPath(line: string): string {\n // git status --porcelain lines are \"XY PATH\" or \"XY ORIG -> DEST\" for renames.\n const body = line.slice(3);\n if (line[0] === 'R' || line[1] === 'R') {\n const parts = body.split(' -> ');\n return parts[parts.length - 1];\n }\n return body;\n}\n\n/**\n * Auto-commit non-excluded workspace changes before a sync-main merge.\n * Respects .gitignore (no -f), unstages excluded paths, and leaves excluded\n * paths dirty so the sync can proceed. Returns success=false on git errors.\n */\nexport async function autoCommitWorkspaceChangesBeforeSync(\n projectPath: string,\n issueId?: string,\n signal?: AbortSignal,\n): Promise<{ success: boolean; committed: boolean; reason?: string }> {\n const run = (command: string, timeout = SYNC_GIT_STATUS_TIMEOUT_MS) =>\n runSyncGitCommand(command, { cwd: projectPath, timeout, signal });\n try {\n const operationHeads = await probeGitOperationHeads(projectPath, SYNC_GIT_STATUS_TIMEOUT_MS, signal);\n if (!operationHeads.success) return { success: false, committed: false, reason: operationHeads.reason };\n if (operationHeads.present.length > 0) {\n return { success: false, committed: false, reason: `Refusing to auto-commit while ${operationHeads.present[0]} exists; finish or abort the in-progress Git operation first` };\n }\n\n try {\n const { stdout: conflictMarkers } = await run(\"git grep -n -I -E '^(<<<<<<<( |$)|=======$|>>>>>>>( |$))' -- .\");\n if (conflictMarkers.trim()) {\n const files = [...new Set(conflictMarkers.trim().split('\\n').map((line) => line.split(':', 1)[0]))];\n return { success: false, committed: false, reason: `Refusing to auto-commit conflict markers in: ${files.join(', ')}` };\n }\n } catch (error: any) {\n if (error?.code !== 1) return { success: false, committed: false, reason: `Failed to scan for conflict markers: ${error.message}` };\n }\n\n const { stdout: statusOut } = await run('git status --porcelain');\n if (!statusOut.trim()) return { success: true, committed: false, reason: 'no uncommitted changes' };\n\n await run('git add -A');\n const resetPaths = AUTO_COMMIT_EXCLUDED_PATHS.map((p) => p.endsWith('/') ? p.slice(0, -1) : p).join(' ');\n await run(`git reset HEAD -- ${resetPaths}`);\n\n const { stdout: diffStat } = await run('git diff --cached --stat');\n if (!diffStat.trim()) return { success: true, committed: false, reason: 'only excluded/ignored changes remain' };\n\n const commitMessage = issueId ? `chore: auto-commit before sync with main (${issueId})` : 'chore: auto-commit before sync with main';\n await run(`git commit -m \"${commitMessage}\"`, SYNC_GIT_COMMIT_TIMEOUT_MS);\n return { success: true, committed: true };\n } catch (error: any) {\n if (error instanceof SyncGitCommandAbortError || error instanceof SyncGitCommandTimeoutError) {\n await ensureSyncGitQuiescent(projectPath, false);\n }\n const reason = error instanceof SyncGitCommandTimeoutError && error.command.startsWith('git commit ')\n ? `Auto-commit git commit timed out after ${SYNC_GIT_COMMIT_TIMEOUT_MS / 1_000}s`\n : `Failed to auto-commit: ${error.message}`;\n return { success: false, committed: false, reason };\n }\n}\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nimport {\n OVERDECK_HOME,\n} from '../paths.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\n\nimport { runQualityGates } from './validation.js';\nimport { loadProjectsConfigSync } from '../projects.js';\nimport { cleanupStaleLocks } from '../git-utils.js';\nimport { gitPush, MainDivergedError } from '../git/operations.js';\nimport { getReviewStatusSync, markWorkspaceStuck, setReviewStatusSync } from '../review-status.js';\nimport { appendGitOperationSync, type GitOperationType } from '../git-activity.js';\nimport { recordFeatureRegistryLifecycle } from '../registry/feature-registry-population.js';\nimport { verifyMergedBeforeLifecycle, type PostMergeLifecycleOptions } from './merge-verification.js';\n\nconst SPECIALISTS_DIR = join(OVERDECK_HOME, 'specialists');\nconst MERGE_HISTORY_DIR = join(SPECIALISTS_DIR, 'merge-agent');\nconst MERGE_HISTORY_FILE = join(MERGE_HISTORY_DIR, 'history.jsonl');\n\n/**\n * Context for a merge conflict resolution request\n */\nexport interface MergeConflictContext {\n projectPath: string;\n sourceBranch: string;\n targetBranch: string;\n conflictFiles: string[];\n issueId: string;\n testCommand?: string;\n}\n\n/**\n * Result of merge agent execution\n */\nexport interface MergeResult {\n success: boolean;\n resolvedFiles?: string[];\n failedFiles?: string[];\n testsStatus?: 'PASS' | 'FAIL' | 'SKIP';\n validationStatus?: 'PASS' | 'FAIL' | 'NOT_RUN';\n reason?: string;\n notes?: string;\n output?: string;\n}\n\n/**\n * Merge history entry\n */\ninterface MergeHistoryEntry {\n timestamp: string;\n issueId: string;\n sourceBranch: string;\n targetBranch: string;\n conflictFiles: string[];\n result: MergeResult;\n sessionId?: string;\n}\n\n/**\n * Timeout for merge agent in milliseconds (15 minutes)\n */\n/**\n * Notify TLDR daemon to reindex changed files after merge\n */\nexport async function notifyTldrDaemon(projectPath: string, _sourceBranch: string): Promise<void> {\n try {\n console.log(`[merge-agent] Notifying TLDR daemon to reindex changed files...`);\n\n // Check if TLDR daemon is available\n const venvPath = join(projectPath, '.venv');\n if (!existsSync(venvPath)) {\n console.log(`[merge-agent] No .venv found, skipping TLDR notification`);\n return;\n }\n\n // Get changed files from the merge\n const { stdout } = await execAsync(`git diff --name-only HEAD~1 HEAD`, {\n cwd: projectPath,\n encoding: 'utf-8'\n });\n\n const changedFiles = stdout\n .trim()\n .split('\\n')\n .filter(f => f.trim().length > 0)\n .filter(f => {\n // Only include source code files (skip docs, configs, etc)\n const ext = f.split('.').pop()?.toLowerCase();\n return ext && ['ts', 'js', 'tsx', 'jsx', 'py', 'java', 'go', 'rs', 'cpp', 'c', 'h'].includes(ext);\n });\n\n if (changedFiles.length === 0) {\n console.log(`[merge-agent] No source files changed, skipping TLDR notification`);\n return;\n }\n\n console.log(`[merge-agent] Found ${changedFiles.length} changed source files to reindex`);\n\n // Get TLDR daemon service\n const { getTldrDaemonServiceSync } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonServiceSync(projectPath, venvPath);\n\n // Check if daemon is running\n const status = await tldrService.getStatus();\n if (!status.running) {\n console.log(`[merge-agent] TLDR daemon not running, skipping notification`);\n return;\n }\n\n // Trigger warm to reindex (this will update the index incrementally)\n console.log(`[merge-agent] Triggering TLDR index warm...`);\n await tldrService.warm(true); // background mode\n\n console.log(`[merge-agent] ✓ TLDR daemon notified to reindex`);\n logActivity('tldr_notified', `Notified TLDR daemon to reindex ${changedFiles.length} files`);\n } catch (error: any) {\n // Non-fatal - log warning and continue\n console.warn(`[merge-agent] Failed to notify TLDR daemon: ${error.message}`);\n logActivity('tldr_notify_error', `TLDR notification failed: ${error.message}`);\n }\n}\n\n/**\n * Post-merge handoff: mark merged work as verifying on main and free runtime resources.\n *\n * Leaves the issue, workspace, xBRIEF, branches, and agent state dirs intact.\n * The explicit close-out ceremony performs final archival and destructive cleanup.\n *\n * IDEMPOTENT: Safe to call multiple times for the same issueId. Tracks completed\n * issues and returns immediately on re-entry. This is defense-in-depth against\n * the infinite loop that burned 24,626 Linear API calls (PAN-328).\n */\n\n// PAN-1531: dropLingeringPreMergeStashes removed. The pre-merge stash kind\n// is no longer created by Overdeck, so there's nothing for the post-merge\n// lifecycle to clean up. Pre-existing pre-merge:* residue in refs/stash is\n// expected and inert.\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nexport async function postMergeLifecycle(\n issueId: string,\n projectPath: string,\n sourceBranch?: string,\n options?: PostMergeLifecycleOptions,\n): Promise<void> {\n // PAN-1517: postMergeLifecycle fires only when the issue's main feature branch merges to `main`.\n // Guard 1: skip if already completed (defense-in-depth against infinite loops)\n if (_completedPostMerge.has(issueId) || getReviewStatusSync(issueId)?.mergeStep === 'merged') {\n _completedPostMerge.add(issueId);\n console.log(`[merge-agent] postMergeLifecycle already completed for ${issueId}, skipping`);\n return;\n }\n\n const inFlight = _postMergeInFlight.get(issueId);\n if (inFlight) {\n console.log(`[merge-agent] postMergeLifecycle already running for ${issueId}, joining in-flight run`);\n return inFlight;\n }\n\n const run = withPostMergeLifecycleLock(issueId, async () => {\n if (_completedPostMerge.has(issueId) || getReviewStatusSync(issueId)?.mergeStep === 'merged') {\n _completedPostMerge.add(issueId); console.log(`[merge-agent] postMergeLifecycle completed while waiting for ${issueId}, skipping`);\n return;\n }\n // Guard 2: closed-out is TERMINAL. Close-out flips the spec to completed/cancelled,\n // clears review status, and closes the tracker issue.\n // Re-running the handoff after that resurrects the review row and REOPENS\n // the closed issue — observed live on PAN-1190 (2026-06-11): the deacon's\n // stale-mergeStatus sweep saw the cleared row as \"stale\" 47 minutes after\n // close-out and the handoff reopened it into verifying-on-main forever.\n try {\n const { findSpecByIssue } = await import('../pan-dir/specs.js');\n const spec = await Effect.runPromise(findSpecByIssue(projectPath, issueId));\n if (spec && (spec.status === 'completed' || spec.status === 'cancelled')) {\n console.log(`[merge-agent] ${issueId} is closed out (spec ${spec.status}) — skipping post-merge lifecycle`);\n _completedPostMerge.add(issueId);\n return;\n }\n } catch {\n // Spec unreadable — proceed; the guard is best-effort.\n }\n\n const mergeVerification = await verifyMergedBeforeLifecycle(issueId, projectPath, sourceBranch, options);\n if (!mergeVerification.merged) {\n console.warn(`[merge-agent] Refusing post-merge lifecycle for ${issueId}: ${mergeVerification.reason}`);\n return;\n }\n console.log(`[merge-agent] Verified merge before lifecycle for ${issueId}: ${mergeVerification.reason}`);\n\n // Set mergeStatus='merged' after verifying the branch or PR actually landed.\n try {\n setReviewStatusSync(issueId, {\n mergeStatus: 'merged', mergeStep: 'post-merge-cleanup',\n readyForMerge: false,\n ...(options?.markReviewPassed ? { reviewStatus: 'passed' as const } : {}),\n });\n console.log(`[merge-agent] ✓ mergeStatus set to 'merged' for ${issueId}`);\n } catch (err: any) {\n console.warn(`[merge-agent] Could not set mergeStatus: ${err.message}`);\n }\n // Eager Docker cleanup must run before any fatal post-merge handoff step.\n let dockerRetryReason: string | null = null;\n try {\n const { teardownWorkspaceDockerByNamePromise } = await import('../workspace-manager/docker.js');\n const teardown = await teardownWorkspaceDockerByNamePromise(issueId.toLowerCase());\n if (teardown.networkRemoved) {\n console.log(`[merge-agent] ✓ Removed Docker stack/network: ${teardown.steps.join('; ')}`);\n logActivity('docker_cleanup', `Removed Docker stack/network for ${issueId}: ${teardown.steps.join('; ')}`);\n } else {\n dockerRetryReason = teardown.steps.join('; ') || 'network remained after teardown';\n }\n } catch (err) {\n dockerRetryReason = err instanceof Error ? err.message : String(err);\n }\n if (dockerRetryReason) {\n enqueueMergedDockerCleanup(issueId, { mergeVerified: true });\n console.warn(`[merge-agent] Docker cleanup queued for retry (non-fatal): ${dockerRetryReason}`);\n }\n\n // Step 0: restart only when the running build is stale and the deploy window is safe.\n if (!options?.skipDeploy) {\n const pendingFile = join(OVERDECK_HOME, 'pending-post-merge.json');\n let repoRoot = __dirname.includes('/src/')\n ? __dirname.replace(/\\/src\\/.*$/, '')\n : __dirname.replace(/\\/dist\\/.*$/, '').replace(/\\/lib\\/.*$/, '');\n // If running from a workspace (workspaces/feature-*/), resolve to the main repo root.\n // Without this, the deploy script builds and npm-links from the workspace, hijacking\n // the global `pan` CLI to point at stale workspace code.\n const wsMatch = repoRoot.match(/^(.+)\\/workspaces\\/feature-[^/]+$/);\n if (wsMatch) {\n repoRoot = wsMatch[1];\n console.log(`[merge-agent] Resolved workspace repoRoot to main repo: ${repoRoot}`);\n }\n const deployScript = join(repoRoot, 'scripts', 'post-merge-deploy.sh');\n\n if (await shouldRestartForPostMerge(repoRoot)) try {\n const pendingData = JSON.stringify({\n issueId,\n projectPath,\n sourceBranch: sourceBranch ?? '',\n timestamp: Date.now(),\n reason: 'post-merge',\n trigger: 'merge-agent',\n });\n await writeFile(pendingFile, pendingData, 'utf-8');\n console.log(`[merge-agent] Wrote pending lifecycle file: ${pendingFile}`);\n\n // Pass 'post-merge' as the reason to the deploy script so it writes the\n // restart marker. We spawn detached and return immediately — the deploy script\n // kills this server. The new server reads the pending file on boot,\n // emits lifecycle_started, and after processing emits lifecycle_complete/failed.\n const child = spawn(deployScript, [repoRoot, issueId, projectPath, sourceBranch ?? '', 'post-merge'], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n console.log(`[merge-agent] Spawned detached deploy script (pid ${child.pid}) — server will restart with new build`);\n return;\n } catch (err: any) {\n console.warn(`[merge-agent] Failed to spawn deploy script: ${err.message}. Falling through to in-process lifecycle (may fail on stale chunks).`);\n }\n }\n\n console.log(`[merge-agent] Running post-merge verify handoff for ${issueId}`);\n\n // 1. Clean up stale workflow labels and keep the legacy merged marker for history.\n // verifying-on-main is applied next and takes precedence in canonical state mapping.\n try {\n const { cleanupMergedLabels } = await import('../lifecycle/label-cleanup.js');\n const ghResolved = resolveGitHubIssueSync(issueId);\n const labelCtx = ghResolved.isGitHub\n ? { issueId, projectPath, github: { owner: ghResolved.owner, repo: ghResolved.repo, number: ghResolved.number } }\n : { issueId, projectPath };\n // PAN-1249: cleanupMergedLabels returns Effect<StepResult>; bridge to Promise.\n const labelResult = await Effect.runPromise(cleanupMergedLabels(labelCtx));\n if (labelResult.success && !labelResult.skipped) {\n console.log(`[merge-agent] ✓ ${labelResult.details?.join('; ')}`);\n logActivity('labels_cleaned', labelResult.details?.join('; ') || 'Labels cleaned');\n } else if (labelResult.skipped) {\n console.log(`[merge-agent] Label cleanup skipped: ${labelResult.details?.join('; ')}`);\n } else {\n console.warn(`[merge-agent] Label cleanup failed (non-fatal): ${labelResult.error}`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not clean labels: ${err}`);\n }\n\n try {\n await transitionIssueToVerifyingOnMain(issueId, projectPath);\n void recordFeatureRegistryLifecycle({ issueId, status: 'merged' });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Could not transition issue to verifying_on_main: ${message}`);\n try {\n setReviewStatusSync(issueId, {\n mergeStatus: 'merged', mergeStep: 'post-merge-cleanup',\n readyForMerge: false,\n mergeNotes: `Post-merge verifying_on_main transition failed: ${message}`,\n });\n } catch (statusErr: any) {\n console.warn(`[merge-agent] Could not persist verifying_on_main transition failure: ${statusErr?.message ?? statusErr}`);\n }\n announceMerge('failed', issueId, `Post-merge verifying_on_main transition failed: ${message}`);\n logActivity('merge_failed', `Post-merge verifying_on_main transition failed for ${issueId}: ${message}`);\n throw err;\n }\n\n // Release verification runs asynchronously so post-merge cleanup is not\n // delayed by a slow external deploy. Status is still reported via review-status.\n triggerPostMergeReleaseIfConfigured(issueId, projectPath).catch((err) => {\n console.warn(`[merge-agent] Async post-merge release trigger failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n });\n\n // 3. Pause work/planning/strike agents and kill their tmux panes to free resources.\n try {\n const { setAgentPaused, getAgentState } = await import('../agents.js');\n const { killSession, sessionExists } = await import('../tmux.js');\n const issueLower = issueId.toLowerCase();\n const reason = 'awaiting close-out (verify on main)';\n for (const agentId of [`agent-${issueLower}`, `planning-${issueLower}`, `strike-${issueLower}`]) {\n // Pause, then VERIFY the gate actually persisted to state.json. A server\n // restart mid-lifecycle (the PAN-1723 deploy re-runs this from\n // pending-post-merge.json) or a concurrent deacon read-modify-write on\n // state.json can silently drop the pause. An unpaused merged work agent\n // sits idle at its prompt yet still counts against the PAN-1665 work\n // ceiling, throttling dispatch for every live issue (PAN-1726). Assert\n // paused=true after the write; retry once, then fail loudly.\n const initial = await Effect.runPromise(setAgentPaused(agentId, reason, true));\n if (initial === null) {\n // No state.json for this agent — nothing to pause (e.g. planning never ran).\n continue;\n }\n let verify = await Effect.runPromise(getAgentState(agentId));\n if (verify?.paused !== true) {\n await Effect.runPromise(setAgentPaused(agentId, reason, true));\n verify = await Effect.runPromise(getAgentState(agentId));\n }\n if (verify?.paused === true) {\n console.log(`[merge-agent] ✓ Paused ${agentId}: ${reason}`);\n } else {\n console.error(\n `[merge-agent] ✗ FAILED to persist pause for ${agentId} after merge — state.json paused=${verify?.paused}. ` +\n `Idle merged work agent may hold a PAN-1665 work slot and throttle dispatch (PAN-1726).`,\n );\n logActivity('agent_pause_failed', `Could not persist pause for ${agentId} after merge — may throttle dispatch (PAN-1726)`);\n }\n if (await Effect.runPromise(sessionExists(agentId))) {\n await Effect.runPromise(killSession(agentId));\n console.log(`[merge-agent] ✓ Killed ${agentId} tmux session to free resources`);\n logActivity('agent_session_killed', `Freed resources: killed tmux session for ${agentId}`);\n }\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not pause or kill agent sessions: ${err}`);\n }\n\n // 5a. Kill canonical reviewer/synthesis sessions (PAN-915).\n // Sessions persist across review rounds to preserve reviewer context, so the\n // merge is the right moment to tear them down. Issue is done — context value\n // is zero, RSS leak risk is non-zero. Resolve projectKey from the project\n // path so we don't depend on caller-supplied config.\n try {\n const { killAllReviewerSessions } = await import('./review-agent.js');\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n const projectKey = resolved?.projectKey;\n if (projectKey) {\n const { killed } = await Effect.runPromise(killAllReviewerSessions(projectKey, issueId));\n if (killed.length > 0) {\n console.log(`[merge-agent] ✓ Killed ${killed.length} canonical reviewer session(s) for ${issueId}`);\n logActivity('reviewer_sessions_killed', `Killed ${killed.length} reviewer session(s) for ${issueId} on merge`);\n }\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not kill canonical reviewer sessions: ${err}`);\n }\n\n await killPostMergeRoleSessions(issueId);\n\n // 3c. Create a workspace-scoped memory reset marker so old review-blocker noise stays archived but out of retrieval.\n try {\n const { createResetMarker } = await import('../memory/cli.js');\n const timestamp = new Date().toISOString();\n const marker = await createResetMarker({\n projectId: basename(projectPath),\n scope: 'workspace',\n scopeId: `feature-${issueId.toLowerCase()}`,\n reason: 'post-merge cleanup',\n fromTimestamp: timestamp,\n createdAt: timestamp,\n });\n console.log(`[merge-agent] ✓ Created memory reset marker ${marker.id} for ${marker.scope}:${marker.scopeId}`);\n } catch (err) {\n console.warn(`[merge-agent] Memory reset marker creation failed (non-fatal): ${err}`);\n }\n await notifyTldrDaemon(projectPath, sourceBranch ?? '');\n await maybeSpawnPostMergeKnowledgeRetro(issueId, projectPath);\n\n setReviewStatusSync(issueId, { mergeStatus: 'merged', mergeStep: 'merged', readyForMerge: false });\n _completedPostMerge.add(issueId); void capturePipelineStageForIssue(issueId, 'merged');\n\n console.log(`[merge-agent] Post-merge handoff completed for ${issueId}. Awaiting close-out (verify on main).`);\n announceMerge('completed', issueId);\n logActivity('merge_complete', `Merged ${issueId}. Awaiting close-out (verify on main).`);\n }).finally(() => { _postMergeInFlight.delete(issueId); });\n _postMergeInFlight.set(issueId, run);\n return run;\n}\n\nexport async function triggerPostMergeReleaseIfConfigured(issueId: string, projectPath: string): Promise<void> {\n const currentStatus = getReviewStatusSync(issueId)?.releaseStatus;\n if (currentStatus && currentStatus !== 'pending') {\n console.log(`[merge-agent] Release already started or completed for ${issueId} (${currentStatus}), skipping`);\n return;\n }\n\n const { resolveProjectFromIssueSync, getProjectSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n const project = resolved ? getProjectSync(resolved.projectKey) : null;\n\n if (!project?.release) {\n setReviewStatusSync(issueId, {\n releaseStatus: 'skipped',\n releaseNotes: 'No release config found for project.',\n });\n console.log(`[merge-agent] No release config for ${issueId}; marked release skipped`);\n return;\n }\n\n const { runRelease } = await import('../release/release-engine.js');\n try {\n await runRelease(issueId, projectPath);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Post-merge release trigger failed for ${issueId}: ${message}`);\n try {\n setReviewStatusSync(issueId, {\n releaseStatus: 'failed',\n releaseNotes: `Post-merge release trigger failed: ${message}`,\n });\n } catch (statusErr: any) {\n console.warn(`[merge-agent] Could not persist release trigger failure: ${statusErr?.message ?? statusErr}`);\n }\n // Release failures are surfaced through release status; the merge itself\n // has already completed and must not be marked failed (review feedback).\n }\n}\n\nasync function transitionIssueToVerifyingOnMain(issueId: string, projectPath: string): Promise<void> {\n const [effectModule, issueLifecycleModule, githubModule, linearModule, rallyModule, errorsModule] = await Promise.all([\n import('effect'),\n import('../../dashboard/server/services/issue-lifecycle.js'),\n import('../../dashboard/server/services/github-client.js'),\n import('../../dashboard/server/services/linear-client.js'),\n import('../../dashboard/server/services/rally-client.js'),\n import('../../dashboard/server/services/typed-errors.js'),\n ]);\n const { Effect, Layer } = effectModule;\n const { IssueLifecycle, IssueLifecycleLive } = issueLifecycleModule;\n const { GitHubClient } = githubModule;\n const { LinearClientOptionalLive } = linearModule;\n const { RallyClientOptionalLive } = rallyModule;\n const { IssueNotFound } = errorsModule;\n const gitHubRepo = (owner: string, repo: string) => shellQuote(`${owner}/${repo}`);\n const githubLayer = Layer.succeed(GitHubClient, {\n getIssue: (_owner: string, _repo: string, number: number) => Effect.fail(new IssueNotFound({ id: String(number) })),\n closeIssue: (owner: string, repo: string, number: number) => Effect.promise(() => execAsync(`gh issue close ${number} --repo ${gitHubRepo(owner, repo)}`, { cwd: projectPath, encoding: 'utf-8' }).then(() => undefined)),\n reopenIssue: (owner: string, repo: string, number: number) => Effect.promise(() => execAsync(`gh issue reopen ${number} --repo ${gitHubRepo(owner, repo)} 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' }).then(() => undefined)),\n addLabel: (owner: string, repo: string, number: number, label: string) => Effect.promise(async () => {\n if (label === 'verifying-on-main') {\n await execAsync(`gh label create ${shellQuote(label)} --repo ${gitHubRepo(owner, repo)} --color \"fbca04\" --description \"Merged — awaiting verification on main\" --force 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' });\n }\n await execAsync(`gh issue edit ${number} --repo ${gitHubRepo(owner, repo)} --add-label ${shellQuote(label)}`, { cwd: projectPath, encoding: 'utf-8' });\n }),\n removeLabel: (owner: string, repo: string, number: number, label: string) => Effect.promise(() => execAsync(`gh issue edit ${number} --repo ${gitHubRepo(owner, repo)} --remove-label ${shellQuote(label)} 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' }).then(() => undefined)),\n ensureLabel: (owner: string, repo: string, label: string, color = '0075ca', description = '') => Effect.promise(async () => {\n await execAsync(`gh label create ${shellQuote(label)} --repo ${gitHubRepo(owner, repo)} --color ${shellQuote(color)} --description ${shellQuote(description)} --force 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' });\n return { id: 0, name: label, color };\n }),\n addComment: () => Effect.void,\n getComments: () => Effect.succeed([]),\n });\n const layer = IssueLifecycleLive.pipe(\n Layer.provide(LinearClientOptionalLive),\n Layer.provide(githubLayer),\n Layer.provide(RallyClientOptionalLive),\n );\n await Effect.runPromise(\n Effect.gen(function* () {\n const lifecycle = yield* IssueLifecycle;\n yield* lifecycle.transitionTo(issueId, 'verifying_on_main');\n }).pipe(Effect.provide(layer)),\n );\n console.log(`[merge-agent] ✓ Transitioned ${issueId} to verifying_on_main`);\n}\n\nfunction isPostMergeRoleSession(sessionName: string, issueLower: string): boolean {\n if ([`agent-${issueLower}-test`, `agent-${issueLower}-ship`, `agent-${issueLower}-merge`].includes(sessionName)) {\n return true;\n }\n if (sessionName.startsWith(`agent-${issueLower}-review-`)) {\n return true;\n }\n return sessionName.startsWith('specialist-')\n && sessionName.includes(`-${issueLower}-`)\n && /-(review|test|merge|ship)(?:-|$)/.test(sessionName);\n}\n\nasync function killPostMergeRoleSessions(issueId: string): Promise<void> {\n try {\n const issueLower = issueId.toLowerCase();\n const sessions = await Effect.runPromise(listSessionNames());\n const targets = sessions.filter((session) => isPostMergeRoleSession(session, issueLower));\n for (const session of targets) {\n await Effect.runPromise(killSession(session));\n }\n if (targets.length > 0) {\n console.log(`[merge-agent] ✓ Killed ${targets.length} review/test/ship session(s) for ${issueId}`);\n logActivity('role_sessions_killed', `Killed ${targets.length} review/test/ship session(s) for ${issueId} on merge`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not kill role sessions for ${issueId}: ${err}`);\n }\n}\n\nfunction isPostMergeKnowledgeRetroEnabled(): boolean {\n try {\n return loadConfigSync().config.knowledge?.postMergeAutoRetro === true;\n } catch (err) {\n console.warn(`[merge-agent] Could not read knowledge post-merge retro config: ${err}`);\n return false;\n }\n}\n\nasync function maybeSpawnPostMergeKnowledgeRetro(issueId: string, projectPath: string): Promise<void> {\n if (!isPostMergeKnowledgeRetroEnabled()) return;\n\n try {\n const child = spawn('pan', ['knowledge', issueId, '--retro'], {\n cwd: projectPath,\n detached: true,\n stdio: 'ignore',\n });\n child.once?.('error', (err) => {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Post-merge knowledge retro spawn failed for ${issueId}: ${message}`);\n logActivity('knowledge_retro_error', `Post-merge knowledge retro spawn failed for ${issueId}: ${message}`, issueId);\n });\n child.unref();\n console.log(`[merge-agent] Spawned post-merge knowledge retro for ${issueId} (pid ${child.pid})`);\n logActivity('knowledge_retro_spawned', `Spawned post-merge knowledge retro for ${issueId}`, issueId);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Post-merge knowledge retro spawn failed for ${issueId}: ${message}`);\n logActivity('knowledge_retro_error', `Post-merge knowledge retro spawn failed for ${issueId}: ${message}`, issueId);\n }\n}\n\nexport { resetPostMergeState } from './post-merge-state.js';\n\n/**\n * Parse result markers from agent output\n */\nexport function parseAgentOutput(output: string): MergeResult {\n const lines = output.split('\\n');\n\n let mergeResult: 'SUCCESS' | 'FAILURE' | null = null;\n let resolvedFiles: string[] = [];\n let failedFiles: string[] = [];\n let testsStatus: 'PASS' | 'FAIL' | 'SKIP' | null = null;\n let validationStatus: 'PASS' | 'FAIL' | null = null;\n let reason = '';\n let notes = '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Match MERGE_RESULT\n if (trimmed.startsWith('MERGE_RESULT:')) {\n const value = trimmed.substring('MERGE_RESULT:'.length).trim();\n if (value === 'SUCCESS' || value === 'FAILURE') {\n mergeResult = value;\n }\n }\n\n // Match RESOLVED_FILES\n if (trimmed.startsWith('RESOLVED_FILES:')) {\n const value = trimmed.substring('RESOLVED_FILES:'.length).trim();\n resolvedFiles = value\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n }\n\n // Match FAILED_FILES\n if (trimmed.startsWith('FAILED_FILES:')) {\n const value = trimmed.substring('FAILED_FILES:'.length).trim();\n failedFiles = value\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n }\n\n // Match TESTS\n if (trimmed.startsWith('TESTS:')) {\n const value = trimmed.substring('TESTS:'.length).trim();\n if (value === 'PASS' || value === 'FAIL' || value === 'SKIP') {\n testsStatus = value;\n }\n }\n\n // Match VALIDATION\n if (trimmed.startsWith('VALIDATION:')) {\n const value = trimmed.substring('VALIDATION:'.length).trim();\n if (value === 'PASS' || value === 'FAIL') {\n validationStatus = value;\n }\n }\n\n // Match REASON\n if (trimmed.startsWith('REASON:')) {\n reason = trimmed.substring('REASON:'.length).trim();\n }\n\n // Match NOTES\n if (trimmed.startsWith('NOTES:')) {\n notes = trimmed.substring('NOTES:'.length).trim();\n }\n }\n\n // Build result\n if (mergeResult === 'SUCCESS') {\n return {\n success: true,\n resolvedFiles,\n testsStatus: testsStatus || 'SKIP',\n validationStatus: validationStatus || 'NOT_RUN',\n notes,\n output,\n };\n } else if (mergeResult === 'FAILURE') {\n return {\n success: false,\n failedFiles,\n validationStatus: validationStatus || 'NOT_RUN',\n reason,\n notes,\n output,\n };\n } else {\n // No structured result markers found - try to detect human-readable format\n // Agents sometimes output \"MERGE TASK COMPLETE\" instead of \"MERGE_RESULT: SUCCESS\"\n const lowerOutput = output.toLowerCase();\n\n // Check for success indicators\n const successIndicators = [\n 'merge task complete',\n 'successfully merged',\n 'merge complete',\n 'pushed merge commit',\n 'successfully merged and pushed',\n ];\n\n const failureIndicators = [\n 'merge failed',\n 'merge task failed',\n 'could not merge',\n 'conflict not resolved',\n ];\n\n const hasSuccessIndicator = successIndicators.some(i => lowerOutput.includes(i));\n const hasFailureIndicator = failureIndicators.some(i => lowerOutput.includes(i));\n\n if (hasSuccessIndicator && !hasFailureIndicator) {\n // Extract test status from output if mentioned\n let detectedTestStatus: 'PASS' | 'FAIL' | 'SKIP' = 'SKIP';\n if (lowerOutput.includes('tests: pass') || lowerOutput.includes('tests passed') ||\n output.match(/\\d+ passed/)) {\n detectedTestStatus = 'PASS';\n } else if (lowerOutput.includes('tests: fail') || lowerOutput.includes('tests failed')) {\n detectedTestStatus = 'FAIL';\n }\n\n console.log('[merge-agent] Detected success from human-readable output');\n return {\n success: true,\n testsStatus: detectedTestStatus,\n validationStatus: 'PASS',\n notes: 'Detected from human-readable output (agent did not use structured format)',\n output,\n };\n }\n\n if (hasFailureIndicator) {\n console.log('[merge-agent] Detected failure from human-readable output');\n return {\n success: false,\n validationStatus: 'NOT_RUN',\n reason: 'Detected merge failure from agent output',\n output,\n };\n }\n\n // Truly unrecognized output\n return {\n success: false,\n validationStatus: 'NOT_RUN',\n reason: 'Agent did not report result in expected format',\n output,\n };\n }\n}\n\n/**\n * Get conflict files from git status (async)\n */\nfunction isSyncGitTermination(error: unknown): boolean {\n return error instanceof SyncGitCommandAbortError || error instanceof SyncGitCommandTimeoutError;\n}\n\nasync function getConflictFiles(projectPath: string, signal?: AbortSignal): Promise<string[]> {\n try {\n const { stdout: status } = await runSyncGitCommand('git diff --name-only --diff-filter=U', {\n cwd: projectPath, timeout: SYNC_GIT_STATUS_TIMEOUT_MS, signal,\n });\n return status.split('\\n').map((line) => line.trim()).filter(Boolean);\n } catch (error) {\n if (isSyncGitTermination(error)) throw error;\n console.error('Failed to get conflict files:', error);\n return [];\n }\n}\n\nasync function resolveMainPreferredSyncConflicts(\n projectPath: string,\n conflictFiles: string[],\n targetBranch: string, signal?: AbortSignal,\n): Promise<{ success: boolean; reason?: string }> {\n if (conflictFiles.length === 0 || !conflictFiles.every(isSyncMainMainPreferredPath)) {\n return { success: false, reason: 'conflicts include non-pipeline-owned files' };\n }\n\n const run = (command: string, timeout = SYNC_GIT_STATUS_TIMEOUT_MS) =>\n runSyncGitCommand(command, { cwd: projectPath, timeout, signal });\n try {\n for (const path of SYNC_MAIN_MAIN_PREFERRED_PATHS) {\n await run(`git rm -r --quiet --ignore-unmatch -- ${path}`);\n await run(`git checkout origin/${targetBranch} -- ${path}`).catch((error) => {\n if (isSyncGitTermination(error)) throw error;\n // The path may not exist on the target branch. The preceding git rm\n // records that branch's deletion for this pipeline-owned path.\n });\n if ((await run(`git ls-files -- ${path}`)).stdout.trim()) await run(`git add -A -- ${path}`);\n }\n\n const remainingConflicts = await getConflictFiles(projectPath, signal);\n if (remainingConflicts.length > 0) {\n return { success: false, reason: `Unresolved conflicts remain: ${remainingConflicts.join(', ')}` };\n }\n\n await run('git commit --no-edit', SYNC_GIT_COMMIT_TIMEOUT_MS);\n return { success: true };\n } catch (error: any) {\n if (isSyncGitTermination(error)) throw error;\n return { success: false, reason: `Failed to auto-resolve pipeline-owned conflicts: ${error.message}` };\n }\n}\n\n/**\n * Log merge to history\n */\nexport function logMergeHistory(context: MergeConflictContext, result: MergeResult, sessionId?: string): void {\n // Ensure history directory exists\n if (!existsSync(MERGE_HISTORY_DIR)) {\n mkdirSync(MERGE_HISTORY_DIR, { recursive: true });\n }\n\n const entry: MergeHistoryEntry = {\n timestamp: new Date().toISOString(),\n issueId: context.issueId,\n sourceBranch: context.sourceBranch,\n targetBranch: context.targetBranch,\n conflictFiles: context.conflictFiles,\n result: {\n ...result,\n output: undefined, // Don't store full output in history\n },\n sessionId,\n };\n\n appendFileSync(MERGE_HISTORY_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n}\n\n/**\n * Log activity to the dashboard activity log (event-sourced via emitActivityEntry)\n */\nfunction logActivity(action: string, details: string, issueId?: string): void {\n emitActivityEntrySync({\n source: 'ship',\n level: action.includes('fail') || action.includes('error') ? 'error' : action.includes('warn') ? 'warn' : 'success',\n message: details,\n issueId,\n });\n}\n\n/**\n * Voice-worthy merge milestone announcement. Messages start with one of three\n * distinctive prefixes (\"Merge started\", \"Merge completed\", \"Merge failed\") so\n * pan-tts can filter merge-agent chatter and speak only these three events.\n *\n * Do not change these prefixes without updating the pan-tts filter\n * (~/Projects/pan-tts/src/pan_tts/__main__.py — ALLOWED_MERGE_PREFIXES).\n */\nfunction announceMerge(\n status: 'started' | 'completed' | 'failed',\n issueId: string,\n extra?: string,\n): void {\n const prefix = status === 'started'\n ? 'Merge started'\n : status === 'completed'\n ? 'Merge completed'\n : 'Merge failed';\n const tail = extra ? `. ${extra}` : '';\n emitActivityEntrySync({\n source: 'ship',\n level: status === 'failed' ? 'error' : 'success',\n message: `${prefix} for ${issueId}${tail}`,\n issueId,\n });\n // Upleveled TTS utterance — short, speakable, no issue prefix noise\n const ttsUtterance = status === 'started'\n ? `Starting merge for ${issueId}`\n : status === 'completed'\n ? `${issueId} merged to main`\n : `Merge failed for ${issueId}`;\n emitActivityTtsSync({\n utterance: ttsUtterance,\n priority: status === 'failed' ? 0 : 1,\n issueId,\n source: 'merge-agent',\n eventType: `mergeStatus.${status === 'completed' ? 'merged' : status === 'started' ? 'merging' : 'failed'}`,\n });\n}\n\n/**\n * Capture tmux output and look for result markers (async)\n */\nexport async function captureTmuxOutput(sessionName: string): Promise<string> {\n try {\n return await Effect.runPromise(capturePane(sessionName));\n } catch {\n return '';\n }\n}\n\n/** Patterns to match in tmux capture-pane output (git push/fetch lines) */\nexport const GIT_PATTERNS: Array<{ re: RegExp; operation: GitOperationType; level: 'info' | 'warn' | 'error' }> = [\n { re: /force-with-lease/i, operation: 'force_push_cmd', level: 'warn' },\n { re: /git push/i, operation: 'push_attempt', level: 'info' },\n { re: /git fetch/i, operation: 'fetch_attempt', level: 'info' },\n { re: /\\[rejected\\]/i, operation: 'push_rejected', level: 'error' },\n { re: /non-fast-forward/i, operation: 'non_ff', level: 'error' },\n { re: /retrying/i, operation: 'retry', level: 'warn' },\n { re: /\\[remote rejected\\]/i, operation: 'remote_rejected', level: 'error' },\n { re: /Everything up-to-date/i, operation: 'push_noop', level: 'info' },\n];\n\n/**\n * Scan tmux capture-pane output for git push/fetch patterns and emit each\n * as a git_operations row. Uses seenLineHashes to dedupe within a session.\n */\nexport function scanGitPatterns(\n output: string,\n seenLineHashes: Set<string>,\n issueId: string,\n branch?: string,\n): void {\n const lines = output.split('\\n');\n const ts = new Date().toISOString();\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n // Simple hash: first 120 chars (avoids hashing megabytes)\n const hash = trimmed.slice(0, 120);\n if (seenLineHashes.has(hash)) continue;\n\n for (const { re, operation, level } of GIT_PATTERNS) {\n if (re.test(trimmed)) {\n seenLineHashes.add(hash);\n appendGitOperationSync({\n operation,\n branch,\n issueId,\n status: level === 'error' ? 'failure' : 'success',\n error: level !== 'info' ? trimmed.slice(0, 200) : undefined,\n ts,\n });\n emitActivityEntrySync({\n source: 'ship',\n level,\n message: `[git] ${trimmed.slice(0, 100)}`,\n issueId,\n });\n break; // only match one pattern per line\n }\n }\n }\n}\n\n/**\n * Check if specialist-merge-agent tmux session is running (async)\n */\nexport async function isMergeAgentRunning(): Promise<boolean> {\n return Effect.runPromise(sessionExists('specialist-merge-agent'));\n}\n\n/**\n * Send a message to an agent's tmux session (async)\n */\nexport async function sendMessageToAgent(issueId: string, message: string): Promise<boolean> {\n // Agent sessions are typically named agent-{issueId} (lowercase)\n const sessionName = `agent-${issueId.toLowerCase()}`;\n\n try {\n // Check if session exists\n if (!await Effect.runPromise(sessionExists(sessionName))) {\n console.log(`[merge-agent] Could not send message to ${sessionName} (session does not exist)`);\n return false;\n }\n\n // Send the message using centralized sendKeys\n await Effect.runPromise(sendKeys(sessionName, message));\n\n console.log(`[merge-agent] Sent message to ${sessionName}`);\n logActivity('agent_message', `Sent to ${sessionName}: ${message.slice(0, 100)}...`);\n return true;\n } catch {\n console.log(`[merge-agent] Could not send message to ${sessionName} (session may not exist)`);\n return false;\n }\n}\n\n// PAN-1531: ship-role machinery (buildShipPreparationPrompt, buildShipSyncMainPrompt,\n// spawnShipRoleForTask, spawnMergeAgentForBranches, spawnRebaseAgentForBranch,\n// defaultWorkspaceForIssue) removed. Rebase is now performed in-process via\n// rebaseFeatureBranch() in src/lib/cloister/merge-rebase.ts. See docs/MERGE-WORKFLOW.md.\n\nexport async function salvageStrandedMerge(\n projectPath: string,\n targetBranch: string,\n headBefore: string,\n issueId: string,\n logActivity: (action: string, detail: string) => void,\n): Promise<{ success: boolean; reason?: string } | null> {\n try {\n const { stdout: currentHeadRaw } = await execAsync('git rev-parse HEAD', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const currentHead = currentHeadRaw.trim();\n\n if (currentHead === headBefore) {\n // No local merge happened — nothing to salvage\n return null;\n }\n\n // Local HEAD changed — check if it's ahead of remote\n await execAsync(`git fetch origin ${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: 10000,\n }).catch(() => {});\n\n const { stdout: remoteHeadRaw } = await execAsync(`git rev-parse origin/${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n\n if (remoteHeadRaw.trim() === currentHead) {\n // Already pushed (maybe by another process)\n console.log(`[merge-agent] Salvage check: merge already pushed`);\n return { success: true };\n }\n\n // Stranded merge detected — push it (with divergence guard to protect hotfixes)\n console.log(`[merge-agent] SALVAGING stranded merge for ${issueId}: local HEAD ${currentHead.slice(0, 8)} != remote ${remoteHeadRaw.trim().slice(0, 8)}`);\n logActivity('merge_salvage', `Pushing stranded merge commit ${currentHead.slice(0, 8)} for ${issueId}`);\n\n try {\n await Effect.runPromise(gitPush(projectPath, 'origin', targetBranch, { issueId }));\n } catch (pushErr: unknown) {\n if (pushErr instanceof MainDivergedError) {\n // origin has advanced past our local ancestor — a hotfix landed.\n // Mark stuck so Deacon won't re-trigger, then let the caller handle it.\n markWorkspaceStuck(issueId, 'main_diverged', {\n localSha: pushErr.localSha,\n remoteSha: pushErr.remoteSha,\n });\n logActivity('merge_salvage_diverged', `Salvage aborted: origin/${targetBranch} diverged (remote ${pushErr.remoteSha.slice(0, 7)} not ancestor of local ${pushErr.localSha.slice(0, 7)})`);\n return { success: false, reason: pushErr.message };\n }\n throw pushErr;\n }\n\n console.log(`[merge-agent] Salvage push successful for ${issueId}`);\n logActivity('merge_salvage_success', `Stranded merge pushed successfully`);\n return { success: true };\n } catch (error: any) {\n console.error(`[merge-agent] Salvage failed: ${error.message}`);\n logActivity('merge_salvage_failed', `Salvage push failed: ${error.message}`);\n return null;\n }\n}\n\n/**\n * Scan workspace for leftover git conflict markers (async)\n */\nexport async function scanForConflictMarkers(projectPath: string): Promise<string[]> {\n try {\n // git diff --check exits non-zero and prints filenames when conflict markers exist\n const { stdout } = await execAsync('git diff --check 2>&1 || true', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const files = stdout\n .split('\\n')\n .filter(line => line.includes('leftover conflict marker'))\n .map(line => line.split(':')[0].trim())\n .filter(f => f.length > 0);\n return [...new Set(files)];\n } catch {\n return [];\n }\n}\n\nasync function collectSyncMergeStats(projectPath: string, signal?: AbortSignal): Promise<Pick<SyncMainResult, 'commitCount' | 'changedFiles'>> {\n const run = (command: string) => runSyncGitCommand(command, {\n cwd: projectPath, timeout: SYNC_GIT_STATUS_TIMEOUT_MS, signal,\n });\n let changedFiles: string[] = [];\n let commitCount = 0;\n try {\n const { stdout } = await run('git diff --name-only ORIG_HEAD HEAD 2>/dev/null || git diff --name-only HEAD~1 HEAD');\n changedFiles = stdout.trim().split('\\n').filter(Boolean);\n } catch (error) { if (isSyncGitTermination(error)) throw error; }\n try {\n const { stdout } = await run('git log ORIG_HEAD..HEAD --oneline 2>/dev/null || echo \"\"');\n commitCount = stdout.trim().split('\\n').filter(Boolean).length;\n } catch (error) { if (isSyncGitTermination(error)) throw error; }\n return { changedFiles, commitCount };\n}\n\n/**\n * Sync the latest main branch into a workspace's feature branch.\n *\n * This performs a `git merge origin/main` in the workspace. If the merge is clean\n * it returns immediately. If conflicts arise, the conflict details are surfaced\n * for manual workspace resolution. The merge is never pushed — this is a local\n * workspace operation.\n *\n * Auto-commits any uncommitted changes before merging (with safety verification).\n */\nasync function syncMainIntoRepo(\n repoDir: string,\n issueId: string,\n targetBranch: string,\n signal?: AbortSignal,\n): Promise<Omit<SyncMainRepoResult, 'repoKey' | 'skipped'>> {\n const run = (command: string, timeout = SYNC_GIT_STATUS_TIMEOUT_MS) =>\n runSyncGitCommand(command, { cwd: repoDir, timeout, signal });\n let mergeStarted = false;\n\n try {\n console.log(`[sync-main] Checking for uncommitted changes...`);\n logActivity('sync_main_auto_commit', `Auto-committing uncommitted changes before sync`);\n const autoCommit = await autoCommitWorkspaceChangesBeforeSync(repoDir, issueId, signal);\n if (!autoCommit.success) {\n const message = autoCommit.reason || 'Failed to auto-commit uncommitted changes';\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n if (autoCommit.committed) console.log(`[sync-main] Auto-commit successful`);\n\n const { stdout: postCommitStatus } = await run('git status --porcelain');\n if (postCommitStatus.trim()) {\n const remainingNonExcluded = postCommitStatus.trim().split('\\n')\n .filter((line) => !isAutoCommitExcludedPath(parseStatusPath(line)));\n if (remainingNonExcluded.length > 0) {\n const message = 'Uncommitted changes remain after auto-commit — aborting sync';\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n }\n\n try {\n const lockCleanup = await Effect.runPromise(cleanupStaleLocks(repoDir, { signal, processProbeTimeoutMs: SYNC_GIT_STATUS_TIMEOUT_MS }));\n if (lockCleanup.found.length > 0) console.log(`[sync-main] Found ${lockCleanup.found.length} lock file(s)`);\n if (lockCleanup.removed.length > 0) {\n console.log(`[sync-main] Cleaned up ${lockCleanup.removed.length} stale lock file(s)`); logActivity('git_lock_cleanup', `Removed ${lockCleanup.removed.length} stale lock file(s)`);\n }\n if (lockCleanup.errors.length > 0) {\n const details = lockCleanup.errors.map(({ file, error }) => `${file}: ${error}`).join('; ');\n const message = `Cannot safely start sync: ${details}`;\n console.error(`[sync-main] ${message}`); logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n } catch (lockError) {\n const cause = lockError instanceof Error ? lockError.message : String(lockError);\n const message = `Cannot verify Git lock state: ${cause}`;\n console.error(`[sync-main] ${message}`); logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n console.log(`[sync-main] Fetching origin/${targetBranch}...`);\n try {\n await run(`git fetch origin ${targetBranch}`, SYNC_GIT_FETCH_TIMEOUT_MS);\n } catch (error) {\n if (isSyncGitTermination(error)) throw error;\n return { success: false, reason: `Failed to fetch origin/${targetBranch}: ${(error as Error).message}` };\n }\n\n let mergeOutput = '';\n let hasConflicts = false;\n try {\n mergeStarted = true;\n const result = await run(`git merge origin/${targetBranch}`, SYNC_GIT_MERGE_TIMEOUT_MS);\n mergeStarted = false;\n mergeOutput = (result.stdout || '') + (result.stderr || '');\n } catch (error: any) {\n if (isSyncGitTermination(error)) throw error;\n mergeOutput = (error.stdout || '') + (error.stderr || '');\n hasConflicts = true;\n }\n\n if (mergeOutput.includes('Already up to date') || mergeOutput.includes('Already up-to-date')) {\n console.log(`[sync-main] Already up to date`);\n logActivity('sync_main_noop', `${issueId} already up to date with ${targetBranch}`);\n return { success: true, alreadyUpToDate: true };\n }\n\n if (!hasConflicts) {\n console.log(`[sync-main] Clean merge completed`);\n logActivity('sync_main_success', `Clean merge of ${targetBranch} into ${issueId}`);\n return { success: true, ...await collectSyncMergeStats(repoDir, signal) };\n }\n\n const conflictFiles = await getConflictFiles(repoDir, signal);\n const mainPreferredResolution = await resolveMainPreferredSyncConflicts(repoDir, conflictFiles, targetBranch, signal);\n if (mainPreferredResolution.success) {\n mergeStarted = false;\n console.log(`[sync-main] Auto-resolved ${conflictFiles.length} pipeline-owned conflict(s) with origin/${targetBranch}`);\n logActivity('sync_main_auto_resolved_conflicts', `Auto-resolved ${conflictFiles.length} pipeline-owned conflict(s) in ${issueId} with origin/${targetBranch}`);\n return { success: true, ...await collectSyncMergeStats(repoDir, signal) };\n }\n\n console.log(`[sync-main] ${conflictFiles.length} conflict(s); aborting merge for manual resolution`);\n logActivity('sync_main_conflicts', `${conflictFiles.length} conflict(s) in ${issueId}: ${conflictFiles.join(', ')}`);\n await ensureSyncGitQuiescent(repoDir, true);\n mergeStarted = false;\n return {\n success: false,\n conflictFiles,\n reason: `Sync-main produced ${conflictFiles.length} conflict(s) in ${issueId}: ${conflictFiles.join(', ')}. Resolve manually in the workspace, then re-run sync-main.`,\n };\n } catch (error) {\n if (!isSyncGitTermination(error)) throw error;\n await ensureSyncGitQuiescent(repoDir, mergeStarted);\n return {\n success: false,\n reason: error instanceof SyncGitCommandTimeoutError ? error.message : 'Sync-main cancelled after reaching its preparation deadline',\n };\n }\n}\n\nexport async function syncMainIntoWorkspace(\n projectPath: string,\n issueId: string,\n signal?: AbortSignal,\n): Promise<SyncMainResult> {\n return syncMainAcrossWorkspaceRepos(projectPath, issueId, signal, syncMainIntoRepo, logActivity);\n}\n\n/**\n * Look up and run quality gates for the project at projectPath.\n * Returns empty array if no quality gates are configured.\n *\n * In polyrepo mode (projectPath is a sub-repo of project.path), only gates\n * whose `path` field matches the relative sub-repo path are run. Gates with\n * no `path` field are skipped in polyrepo context.\n */\nexport async function runProjectQualityGates(\n projectPath: string,\n phase: 'pre_push' | 'post_push'\n): Promise<import('./validation.js').QualityGateResult[]> {\n try {\n const config = loadProjectsConfigSync();\n // Find the project whose path matches\n const project = Object.values(config.projects).find(p => projectPath.startsWith(p.path));\n if (!project?.quality_gates || Object.keys(project.quality_gates).length === 0) {\n console.log(`[merge-agent] No quality gates configured for ${projectPath}`);\n return [];\n }\n\n // Detect polyrepo context: if projectPath is a subdirectory of project.path,\n // repoRelPath is non-empty (e.g., 'frontend' or 'backend').\n const repoRelPath = relative(project.path, projectPath);\n const matchedRepo = project.workspace?.repos?.find(repo => (\n repoRelPath === repo.path ||\n projectPath === join(project.path, repo.path)\n ));\n const repoIdentifiers = matchedRepo\n ? new Set([matchedRepo.path, matchedRepo.name])\n : new Set(repoRelPath && !repoRelPath.startsWith('..') ? [repoRelPath] : []);\n\n let gatesToRun = project.quality_gates;\n if (repoRelPath && !repoRelPath.startsWith('..')) {\n // Polyrepo: gates can target either the repo path (\"frontend\") or the\n // configured repo key/alias (\"fe\"). Both map to the same sub-repo.\n const filtered = Object.entries(project.quality_gates).filter(\n ([, gate]) => gate.path && repoIdentifiers.has(gate.path)\n );\n if (filtered.length === 0) {\n console.log(`[merge-agent] No quality gates configured for repo path \"${repoRelPath}\"`);\n return [];\n }\n gatesToRun = Object.fromEntries(filtered);\n console.log(\n `[merge-agent] Polyrepo: running ${Object.keys(gatesToRun).length} gate(s) for path \"${repoRelPath}\" (${[...repoIdentifiers].join(', ')})`\n );\n }\n\n console.log(`[merge-agent] Running ${phase} quality gates for project \"${project.name}\"`);\n return await Effect.runPromise(runQualityGates(gatesToRun, projectPath, phase));\n } catch (error: any) {\n console.error(`[merge-agent] Failed to load quality gates: ${error.message}`);\n return [];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"merge-agent-CogNFasY.js","names":["resolve","dirname","_completedPostMerge","_postMergeInFlight","writeFile"],"sources":["../src/lib/cloister/merge-agent-step0.ts","../src/lib/cloister/merged-docker-cleanup-worker.ts","../src/lib/cloister/post-merge-lifecycle-lock.ts","../src/lib/cloister/sync-main-git.ts","../src/lib/cloister/sync-main-workspace.ts","../src/lib/overdeck/git-activity.ts","../src/lib/git-activity.ts","../src/lib/git/operations.ts","../src/lib/cloister/merge-agent.ts"],"sourcesContent":["import type { DeployWindowAssessment } from '../deploy/deploy-window.js';\nimport type { BuildStaleness } from '../deploy/staleness.js';\n\ninterface Step0Dependencies {\n readonly computeStaleness: () => Promise<BuildStaleness>;\n readonly getWindowAssessment: () => Promise<DeployWindowAssessment>;\n readonly recordIntent: typeof import('../deploy/deploy-queue.js').recordDeployIntent;\n readonly log: (message: string) => void;\n}\n\nexport async function shouldRestartForPostMerge(\n repoRoot: string,\n dependencies: Partial<Step0Dependencies> = {},\n): Promise<boolean> {\n const computeStaleness = dependencies.computeStaleness ?? (async () => {\n const [{ getBuildInfo }, { computeBuildStaleness }] = await Promise.all([\n import('../deploy/build-info.js'),\n import('../deploy/staleness.js'),\n ]);\n return computeBuildStaleness({ repoRoot, buildCommit: getBuildInfo().buildCommit });\n });\n const staleness = await computeStaleness();\n\n if (staleness.status === 'fresh') {\n (dependencies.log ?? console.log)(\n 'Running build already contains origin/main build inputs — skipping deploy restart',\n );\n return false;\n }\n\n const getWindowAssessment = dependencies.getWindowAssessment ?? (async () =>\n (await import('../deploy/deploy-window.js')).getDeployWindowAssessment());\n const assessment = await getWindowAssessment();\n if (assessment.reason) {\n const recordIntent = dependencies.recordIntent\n ?? (await import('../deploy/deploy-queue.js')).recordDeployIntent;\n await recordIntent({\n requestedBy: 'merge-step0',\n reason: assessment.reason,\n blockedBy: [],\n });\n (dependencies.log ?? console.log)(\n `Deploy window unsafe (${assessment.reason}) — deferring deploy to the staleness patrol`,\n );\n return false;\n }\n\n return true;\n}\n","import { emitActivityEntrySync } from '../activity-logger.js';\nimport { resolveCanonicalReviewStatus } from './review-status-source.js';\n\nconst RETRY_BASE_MS = 60_000;\nconst RETRY_MAX_MS = 15 * 60_000;\n\ninterface CleanupEntry {\n issueId: string;\n attempts: number;\n nextAttemptAt: number;\n running: boolean;\n mergeVerified: boolean;\n}\n\ninterface EnqueueOptions {\n /** Trust the merge-agent's just-completed merge verification for the first retry. */\n mergeVerified?: boolean;\n}\n\nconst queue = new Map<string, CleanupEntry>();\nlet workerPromise: Promise<void> | null = null;\n\nfunction retryDelayMs(attempts: number): number {\n return Math.min(RETRY_BASE_MS * 2 ** Math.max(0, attempts - 1), RETRY_MAX_MS);\n}\n\nfunction nextEligibleEntry(): CleanupEntry | null {\n const now = Date.now();\n return [...queue.values()].find((entry) => !entry.running && entry.nextAttemptAt <= now) ?? null;\n}\n\nfunction recordSuccess(issueId: string, steps: string[]): void {\n const message = `Removed merged-issue Docker stack/network for ${issueId}: ${steps.join('; ')}`;\n console.log(`[deacon] ${message}`);\n try {\n emitActivityEntrySync({ source: 'cloister', level: 'info', issueId, message: `[deacon] ${message}` });\n } catch (error) {\n console.warn(`[deacon] Could not record merged Docker cleanup activity for ${issueId}: ${error}`);\n }\n}\n\nfunction recordFailure(entry: CleanupEntry, reason: string): void {\n entry.attempts += 1;\n entry.nextAttemptAt = Date.now() + retryDelayMs(entry.attempts);\n entry.running = false;\n console.warn(\n `[deacon] Merged-issue Docker cleanup failed for ${entry.issueId}; ` +\n `retry ${entry.attempts} after ${new Date(entry.nextAttemptAt).toISOString()}: ${reason}`,\n );\n}\n\nasync function drainQueue(): Promise<void> {\n const { teardownWorkspaceDockerByNamePromise } = await import('../workspace-manager/docker.js');\n for (let entry = nextEligibleEntry(); entry; entry = nextEligibleEntry()) {\n entry.running = true;\n const eligibility = resolveCanonicalReviewStatus(entry.issueId);\n if (eligibility.status?.mergeStatus === 'merged') {\n entry.mergeVerified = false;\n } else if (entry.mergeVerified) {\n // The merge-agent verified the merge immediately before enqueueing. Consume\n // that proof once so a failed status write cannot cancel the first retry.\n entry.mergeVerified = false;\n } else if (!eligibility.available) {\n recordFailure(entry, 'canonical merge status unavailable');\n continue;\n } else {\n queue.delete(entry.issueId);\n console.log(`[deacon] Cancelled merged Docker cleanup for ${entry.issueId} — issue is no longer merged`);\n continue;\n }\n try {\n const result = await teardownWorkspaceDockerByNamePromise(entry.issueId.toLowerCase());\n if (result.networkRemoved) {\n queue.delete(entry.issueId);\n recordSuccess(entry.issueId, result.steps);\n } else {\n recordFailure(entry, result.steps.join('; '));\n }\n } catch (error) {\n recordFailure(entry, error instanceof Error ? error.message : String(error));\n }\n }\n}\n\nfunction startWorker(): void {\n if (workerPromise || !nextEligibleEntry()) return;\n workerPromise = drainQueue()\n .catch((error) => {\n const reason = error instanceof Error ? error.message : String(error);\n for (const entry of queue.values()) {\n if (!entry.running && entry.nextAttemptAt <= Date.now()) recordFailure(entry, reason);\n }\n })\n .finally(() => {\n workerPromise = null;\n if (nextEligibleEntry()) startWorker();\n });\n}\n\nexport function reconcileMergedDockerCleanupQueue(eligibleIssueIds: string[]): string[] {\n const eligible = new Set(\n eligibleIssueIds.map((issueId) => issueId.trim().toUpperCase()).filter(Boolean),\n );\n for (const [issueId, entry] of queue) {\n if (!entry.running && !eligible.has(issueId)) queue.delete(issueId);\n }\n return [...eligible].flatMap((issueId) => {\n const action = enqueueMergedDockerCleanup(issueId);\n return action ? [action] : [];\n });\n}\n\nexport function enqueueMergedDockerCleanup(\n issueId: string,\n options?: EnqueueOptions,\n): string | null {\n const normalizedIssueId = issueId.trim().toUpperCase();\n if (!normalizedIssueId) return null;\n const existing = queue.get(normalizedIssueId);\n if (!existing) {\n queue.set(normalizedIssueId, {\n issueId: normalizedIssueId,\n attempts: 0,\n nextAttemptAt: 0,\n running: false,\n mergeVerified: options?.mergeVerified === true,\n });\n startWorker();\n return `Queued merged-issue Docker cleanup for ${normalizedIssueId}`;\n }\n if (options?.mergeVerified) existing.mergeVerified = true;\n startWorker();\n return null;\n}\n\nexport async function waitForMergedDockerCleanupIdleForTests(): Promise<void> {\n while (workerPromise) await workerPromise;\n}\n\nexport async function resetMergedDockerCleanupWorkerForTests(): Promise<void> {\n await waitForMergedDockerCleanupIdleForTests();\n queue.clear();\n}\n\nexport function getMergedDockerCleanupStateForTests(issueId: string): CleanupEntry | null {\n const entry = queue.get(issueId.toUpperCase());\n return entry ? { ...entry } : null;\n}\n","import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, resolve } from 'node:path';\n\nimport { parseIssueIdSync } from '../issue-id.js';\nimport { OVERDECK_HOME } from '../paths.js';\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction isMissing(error: unknown): boolean {\n return (error as NodeJS.ErrnoException)?.code === 'ENOENT';\n}\n\nfunction isAlreadyExists(error: unknown): boolean {\n return (error as NodeJS.ErrnoException)?.code === 'EEXIST';\n}\n\nexport async function acquirePostMergeLifecycleLock(\n issueId: string,\n): Promise<(() => Promise<void>) | null> {\n const trimmedIssueId = issueId.trim();\n const parsedIssueId = parseIssueIdSync(trimmedIssueId);\n if (!parsedIssueId) {\n throw new Error(`Invalid issue ID for post-merge lifecycle lock: ${issueId}`);\n }\n\n const lockDir = resolve(OVERDECK_HOME, 'locks', 'post-merge-lifecycle');\n const lockPath = resolve(lockDir, `${parsedIssueId.normalized.toUpperCase()}.lock`);\n if (dirname(lockPath) !== lockDir) {\n throw new Error(`Post-merge lifecycle lock escaped its lock directory: ${issueId}`);\n }\n await mkdir(lockDir, { recursive: true });\n\n for (let attempt = 0; attempt < 2; attempt += 1) {\n try {\n await writeFile(lockPath, String(process.pid), { flag: 'wx', mode: 0o600 });\n let released = false;\n return async () => {\n if (released) return;\n released = true;\n try {\n await unlink(lockPath);\n } catch (error) {\n if (!isMissing(error)) throw error;\n }\n };\n } catch (error) {\n if (!isAlreadyExists(error)) throw error;\n let ownerPid: number;\n try {\n ownerPid = Number.parseInt((await readFile(lockPath, 'utf-8')).trim(), 10);\n } catch (readError) {\n if (isMissing(readError)) continue;\n return null;\n }\n if (!Number.isInteger(ownerPid) || ownerPid <= 0 || isProcessAlive(ownerPid)) return null;\n try {\n await unlink(lockPath);\n } catch (unlinkError) {\n if (!isMissing(unlinkError)) return null;\n }\n }\n }\n return null;\n}\n\nexport async function withPostMergeLifecycleLock<T>(\n issueId: string,\n run: () => Promise<T>,\n): Promise<T> {\n const release = await acquirePostMergeLifecycleLock(issueId);\n if (!release) throw new Error(`postMergeLifecycle already running in another process for ${issueId}`);\n try {\n return await run();\n } finally {\n await release();\n }\n}\n","import { exec, type ChildProcess, type ExecException } from 'child_process';\nimport { Effect } from 'effect';\nimport { cleanupStaleLocks } from '../git-utils.js';\n\nexport const GIT_OPERATION_HEADS = [\n 'MERGE_HEAD',\n 'REBASE_HEAD',\n 'CHERRY_PICK_HEAD',\n 'REVERT_HEAD',\n] as const;\n\nexport class SyncGitCommandTimeoutError extends Error {\n readonly killed = true;\n\n constructor(\n public readonly command: string,\n public readonly budgetMs: number,\n ) {\n super(`${command} timed out after ${budgetMs / 1_000}s`);\n this.name = 'SyncGitCommandTimeoutError';\n }\n}\n\nexport class SyncGitCommandAbortError extends Error {\n readonly code = 'ABORT_ERR';\n readonly killed = true;\n\n constructor(public readonly command: string) {\n super(`${command} was cancelled`);\n this.name = 'SyncGitCommandAbortError';\n }\n}\n\nexport class UnsafeSyncMainStateError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UnsafeSyncMainStateError';\n }\n}\n\ninterface SyncGitExecOptions {\n cwd: string;\n timeout: number;\n signal?: AbortSignal;\n}\n\nconst execDetached = exec as unknown as (\n command: string,\n options: { cwd: string; encoding: BufferEncoding; detached: boolean; timeout: number },\n callback: (error: ExecException | null, stdout: string, stderr: string) => void,\n) => ChildProcess;\n\nfunction killCommandTree(child: ChildProcess | undefined): void {\n if (!child?.pid) return;\n try {\n if (process.platform === 'win32') child.kill('SIGKILL');\n else process.kill(-child.pid, 'SIGKILL');\n } catch {\n try { child.kill('SIGKILL'); } catch { /* process already exited */ }\n }\n}\n\nexport function runSyncGitCommand(\n command: string,\n options: SyncGitExecOptions,\n): Promise<{ stdout: string; stderr: string }> {\n if (options.signal?.aborted) return Promise.reject(new SyncGitCommandAbortError(command));\n\n return new Promise((resolve, reject) => {\n let child: ChildProcess | undefined;\n let termination: 'timeout' | 'abort' | undefined;\n const finishTermination = (kind: 'timeout' | 'abort') => {\n if (termination) return;\n termination = kind;\n killCommandTree(child);\n };\n const timeout = setTimeout(() => finishTermination('timeout'), options.timeout);\n const onAbort = () => finishTermination('abort');\n options.signal?.addEventListener('abort', onAbort, { once: true });\n\n child = execDetached(command, {\n cwd: options.cwd,\n encoding: 'utf-8',\n detached: process.platform !== 'win32',\n timeout: options.timeout,\n }, (error, stdout, stderr) => {\n clearTimeout(timeout);\n options.signal?.removeEventListener('abort', onAbort);\n if (termination === 'abort') {\n reject(Object.assign(new SyncGitCommandAbortError(command), { stdout, stderr }));\n } else if (termination === 'timeout') {\n reject(Object.assign(new SyncGitCommandTimeoutError(command, options.timeout), { stdout, stderr }));\n } else if (error?.killed) {\n reject(Object.assign(new SyncGitCommandTimeoutError(command, options.timeout), { stdout, stderr }));\n } else if (error) {\n reject(Object.assign(error, { stdout, stderr }));\n } else {\n resolve({ stdout, stderr });\n }\n });\n if (options.signal?.aborted) finishTermination('abort');\n });\n}\n\nexport interface OperationHeadProbeResult {\n success: boolean;\n present: string[];\n reason?: string;\n}\n\nexport async function probeGitOperationHeads(\n projectPath: string,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<OperationHeadProbeResult> {\n const present: string[] = [];\n for (const operationHead of GIT_OPERATION_HEADS) {\n try {\n await runSyncGitCommand(`git rev-parse -q --verify ${operationHead}`, {\n cwd: projectPath,\n timeout: timeoutMs,\n signal,\n });\n present.push(operationHead);\n } catch (error) {\n if (error instanceof SyncGitCommandAbortError) throw error;\n if (error instanceof SyncGitCommandTimeoutError) {\n return { success: false, present, reason: `${operationHead} probe timed out after ${timeoutMs / 1_000}s` };\n }\n if ((error as { code?: number | string } | null)?.code !== 1) {\n const message = error instanceof Error ? error.message : String(error);\n return { success: false, present, reason: `${operationHead} probe failed: ${message}` };\n }\n }\n }\n return { success: true, present };\n}\n\nexport async function ensureSyncGitQuiescent(\n projectPath: string,\n abortMerge: boolean,\n timeoutMs = 30_000,\n): Promise<void> {\n const controller = new AbortController();\n const deadline = Date.now() + timeoutMs;\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n const remaining = (stage: string): number => {\n const budget = deadline - Date.now();\n if (controller.signal.aborted || budget <= 0) {\n throw new UnsafeSyncMainStateError(\n `Git cleanup timed out after ${timeoutMs / 1_000}s while ${stage}`,\n );\n }\n return budget;\n };\n\n try {\n let lockCleanup: {\n found: string[];\n removed: string[];\n errors: Array<{ file: string; error: string }>;\n };\n try {\n lockCleanup = await Effect.runPromise(cleanupStaleLocks(projectPath, {\n signal: controller.signal,\n processProbeTimeoutMs: remaining('checking Git lock owners'),\n }));\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git lock state after cancellation: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n if (lockCleanup.errors.length > 0) {\n throw new UnsafeSyncMainStateError(\n `Could not establish Git quiescence: ${lockCleanup.errors\n .map(({ file, error }) => `${file}: ${error}`)\n .join('; ')}`,\n );\n }\n\n let before: OperationHeadProbeResult;\n try {\n before = await probeGitOperationHeads(\n projectPath,\n remaining('checking Git operation state'),\n controller.signal,\n );\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n if (!before.success) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state: ${before.reason}`,\n );\n }\n const nonMergeHeads = before.present.filter((head) => head !== 'MERGE_HEAD');\n if (nonMergeHeads.length > 0\n || (before.present.includes('MERGE_HEAD') && !abortMerge)) {\n throw new UnsafeSyncMainStateError(\n `Git operation remains active after cancellation: ${before.present.join(', ')}`,\n );\n }\n if (before.present.includes('MERGE_HEAD')) {\n try {\n await runSyncGitCommand('git merge --abort', {\n cwd: projectPath,\n timeout: remaining('aborting the timed-out merge'),\n signal: controller.signal,\n });\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not abort timed-out sync merge: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n let after: OperationHeadProbeResult;\n try {\n after = await probeGitOperationHeads(\n projectPath,\n remaining('verifying Git cleanup'),\n controller.signal,\n );\n } catch (error) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state after cleanup: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n if (!after.success) {\n throw new UnsafeSyncMainStateError(\n `Could not verify Git operation state after cleanup: ${after.reason}`,\n );\n }\n if (after.present.length > 0) {\n throw new UnsafeSyncMainStateError(\n `Git operation remains active after cleanup: ${after.present.join(', ')}`,\n );\n }\n } finally {\n clearTimeout(timeout);\n }\n}\n","import { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\n\nexport interface SyncMainRepoResult {\n repoKey: string;\n success: boolean;\n alreadyUpToDate?: boolean;\n commitCount?: number;\n changedFiles?: string[];\n conflictFiles?: string[];\n reason?: string;\n skipped?: boolean;\n}\n\nexport interface SyncMainResult {\n success: boolean;\n alreadyUpToDate?: boolean;\n commitCount?: number;\n changedFiles?: string[];\n conflictFiles?: string[];\n reason?: string;\n repos?: SyncMainRepoResult[];\n}\n\ntype SyncRepo = (\n repoDir: string,\n issueId: string,\n targetBranch: string,\n signal?: AbortSignal,\n) => Promise<Omit<SyncMainRepoResult, 'repoKey' | 'skipped'>>;\n\ntype LogActivity = (action: string, details: string, issueId?: string) => void;\n\nexport async function syncMainAcrossWorkspaceRepos(\n projectPath: string,\n issueId: string,\n signal: AbortSignal | undefined,\n syncRepo: SyncRepo,\n logActivity: LogActivity,\n): Promise<SyncMainResult> {\n console.log(`[sync-main] Starting sync of main into workspace for ${issueId}`);\n logActivity('sync_main_start', `Starting sync for ${issueId}`);\n\n const roots = resolveWorkspaceRepoRootsSync(issueId, projectPath);\n const repos: SyncMainRepoResult[] = [];\n let failedRepoKey: string | undefined;\n\n for (const root of roots) {\n if (failedRepoKey) {\n repos.push({\n repoKey: root.repoKey,\n success: false,\n skipped: true,\n reason: `[${root.repoKey}] Skipped because sync failed in ${failedRepoKey}`,\n });\n continue;\n }\n\n if (root.isPolyrepo) {\n console.log(`[sync-main] [${root.repoKey}] Syncing origin/${root.targetBranch}`);\n logActivity('sync_main_repo_start', `[${root.repoKey}] Syncing origin/${root.targetBranch}`);\n }\n const result = await syncRepo(root.dir, issueId, root.targetBranch, signal);\n const prefixPaths = (paths: string[] | undefined) => root.isPolyrepo && paths\n ? paths.map(path => `${root.repoKey}/${path}`)\n : paths;\n const repoResult: SyncMainRepoResult = { ...result, repoKey: root.repoKey };\n if (result.changedFiles !== undefined) repoResult.changedFiles = prefixPaths(result.changedFiles);\n if (result.conflictFiles !== undefined) repoResult.conflictFiles = prefixPaths(result.conflictFiles);\n if (result.reason && root.isPolyrepo) repoResult.reason = `[${root.repoKey}] ${result.reason}`;\n repos.push(repoResult);\n if (!result.success) failedRepoKey = root.repoKey;\n }\n\n const completedRepos = repos.filter(repo => !repo.skipped);\n const changedFiles = completedRepos.flatMap(repo => repo.changedFiles ?? []);\n const conflictFiles = completedRepos.flatMap(repo => repo.conflictFiles ?? []);\n const countedRepos = completedRepos.filter(repo => repo.commitCount !== undefined);\n const failedRepo = completedRepos.find(repo => !repo.success);\n const aggregate: SyncMainResult = { success: !failedRepo, repos };\n\n if (completedRepos.length > 0 && completedRepos.every(repo => repo.alreadyUpToDate === true)) {\n aggregate.alreadyUpToDate = true;\n }\n if (countedRepos.length > 0) {\n aggregate.commitCount = countedRepos.reduce((sum, repo) => sum + (repo.commitCount ?? 0), 0);\n }\n if (completedRepos.some(repo => repo.changedFiles !== undefined)) aggregate.changedFiles = changedFiles;\n if (completedRepos.some(repo => repo.conflictFiles !== undefined)) aggregate.conflictFiles = conflictFiles;\n if (failedRepo?.reason) aggregate.reason = failedRepo.reason;\n\n return aggregate;\n}\n","/**\n * Overdeck door for git_operations — persistent log of git push/fetch/merge\n * events. Replaces database/git-operations-db.ts.\n *\n * Uses the hand-maintained overdeck migration as the schema source of truth.\n */\n\nimport { getOverdeckDatabaseSync } from './infra.js';\n\n// ─── Schema bootstrap ─────────────────────────────────────────────────────────\n\nfunction overdeckDb() {\n return getOverdeckDatabaseSync();\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type GitOperationType =\n | 'push'\n | 'force_push'\n | 'fetch'\n | 'merge'\n | 'rev_parse'\n | 'main_diverged'\n | 'push_attempt'\n | 'fetch_attempt'\n | 'push_rejected'\n | 'non_ff'\n | 'force_push_cmd'\n | 'retry'\n | 'remote_rejected'\n | 'push_noop';\n\nexport type GitOperationStatus = 'success' | 'failure' | 'aborted';\n\nexport interface GitOperation {\n id?: number;\n operation: GitOperationType;\n branch?: string;\n issueId?: string;\n beforeSha?: string;\n afterSha?: string;\n remoteSha?: string;\n status: GitOperationStatus;\n error?: string;\n ts: string;\n}\n\nexport interface GitOperationFilter {\n issueId?: string;\n operation?: GitOperationType;\n status?: GitOperationStatus;\n since?: string;\n limit?: number;\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport function appendGitOperationSync(op: Omit<GitOperation, 'id'>): number {\n const result = overdeckDb().prepare(`\n INSERT INTO git_operations (\n operation, branch, issue_id,\n before_sha, after_sha, remote_sha,\n status, error, ts\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n op.operation,\n op.branch ?? null,\n op.issueId ?? null,\n op.beforeSha ?? null,\n op.afterSha ?? null,\n op.remoteSha ?? null,\n op.status,\n op.error ?? null,\n new Date(op.ts).getTime(),\n );\n return result.lastInsertRowid as number;\n}\n\nexport function listGitOperationsSync(filter: GitOperationFilter = {}): GitOperation[] {\n const conditions: string[] = [];\n const params: (string | number)[] = [];\n\n if (filter.issueId) { conditions.push('issue_id = ?'); params.push(filter.issueId); }\n if (filter.operation) { conditions.push('operation = ?'); params.push(filter.operation); }\n if (filter.status) { conditions.push('status = ?'); params.push(filter.status); }\n if (filter.since) { conditions.push('ts >= ?'); params.push(new Date(filter.since).getTime()); }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const limit = filter.limit ? `LIMIT ${filter.limit}` : 'LIMIT 500';\n\n const rows = overdeckDb().prepare(`\n SELECT id, operation, branch, issue_id, before_sha, after_sha, remote_sha, status, error, ts\n FROM git_operations ${where} ORDER BY ts DESC ${limit}\n `).all(...params) as Array<{\n id: number;\n operation: string;\n branch: string | null;\n issue_id: string | null;\n before_sha: string | null;\n after_sha: string | null;\n remote_sha: string | null;\n status: string;\n error: string | null;\n ts: number;\n }>;\n\n return rows.map((row) => ({\n id: row.id,\n operation: row.operation as GitOperationType,\n branch: row.branch ?? undefined,\n issueId: row.issue_id ?? undefined,\n beforeSha: row.before_sha ?? undefined,\n afterSha: row.after_sha ?? undefined,\n remoteSha: row.remote_sha ?? undefined,\n status: row.status as GitOperationStatus,\n error: row.error ?? undefined,\n ts: new Date(row.ts).toISOString(),\n }));\n}\n","/**\n * Git Activity Service (PAN-653)\n *\n * Write path and read path for git operation events persisted to the\n * git_operations SQLite table. Survives dashboard restart — unlike the\n * in-memory activityLog array which is capped at 100 and wiped on restart.\n *\n * All functions use the async-safe getDatabase() which runs under Node 22.\n * NEVER use execSync/readFileSync here — this runs in the dashboard server.\n */\n\nimport { Effect, Data } from 'effect';\nimport {\n appendGitOperationSync,\n listGitOperationsSync,\n} from './overdeck/git-activity.js';\nimport type {\n GitOperation,\n GitOperationFilter,\n GitOperationType,\n GitOperationStatus,\n} from './overdeck/git-activity.js';\n\nexport type { GitOperation, GitOperationFilter, GitOperationType, GitOperationStatus };\nexport { appendGitOperationSync, listGitOperationsSync };\n\n/** A database operation against the git_operations table failed. */\nexport class GitActivityDbError extends Data.TaggedError('GitActivityDbError')<{\n readonly operation: string;\n readonly cause: unknown;\n}> {}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect-native appendGitOperation — typed-error variant of the SQLite write.\n * Fails with GitActivityDbError if the insert throws (e.g. DB locked).\n */\nexport const appendGitOperation = (\n op: Omit<GitOperation, 'id'>,\n): Effect.Effect<number, GitActivityDbError> =>\n Effect.try({\n try: () => appendGitOperationSync(op),\n catch: (cause) => new GitActivityDbError({ operation: 'appendGitOperation', cause }),\n });\n\n/**\n * Effect-native listGitOperations — typed-error variant of the SQLite read.\n * Fails with GitActivityDbError on query failure.\n */\nexport const listGitOperations = (\n filter: GitOperationFilter = {},\n): Effect.Effect<readonly GitOperation[], GitActivityDbError> =>\n Effect.try({\n try: () => listGitOperationsSync(filter),\n catch: (cause) => new GitActivityDbError({ operation: 'listGitOperations', cause }),\n });\n","/**\n * Git Operations Helpers (PAN-653)\n *\n * Thin wrappers over raw git commands that:\n * 1. Use execAsync (never execSync) — safe for dashboard server use\n * 2. Record before/after/remote SHAs for each operation\n * 3. Emit structured git.* events to the git_operations SQLite table\n * 4. For gitPush: perform a pre-push fetch + ancestor check and throw\n * MainDivergedError if origin/main has advanced past the local ancestor\n *\n * Usage: import { gitPush, gitFetch, gitForcePush, gitMerge, gitRevParse }\n * from 'src/lib/git/operations.js'\n *\n * NEVER add execSync, readFileSync, writeFileSync here — this module is\n * imported by dashboard server code (CLAUDE.md rule).\n */\n\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { appendGitOperationSync } from '../git-activity.js';\nimport { GitError, VcsError } from '../errors.js';\n\nconst execFileAsync = promisify(execFile);\n\n// ============== Error types ==============\n\n/**\n * Thrown by gitPush when origin/main has advanced beyond the local ancestor.\n * The caller must NOT push — it should call markWorkspaceStuck instead.\n */\nexport class MainDivergedError extends Error {\n readonly localSha: string;\n readonly remoteSha: string;\n\n constructor(localSha: string, remoteSha: string) {\n super(\n `Main diverged: remote HEAD (${remoteSha.slice(0, 7)}) is not an ancestor of local HEAD (${localSha.slice(0, 7)}). Aborting push to protect the hotfix commit.`\n );\n this.name = 'MainDivergedError';\n this.localSha = localSha;\n this.remoteSha = remoteSha;\n }\n}async function gitRevParsePromise(cwd: string, ref: string): Promise<string | null> {\n const ts = new Date().toISOString();\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', ref], { cwd, encoding: 'utf-8' });\n const sha = stdout.trim();\n appendGitOperationSync({ operation: 'rev_parse', branch: ref, issueId: undefined, afterSha: sha, status: 'success', ts });\n return sha || null;\n } catch {\n return null;\n }\n}async function gitFetchPromise(\n cwd: string,\n remote = 'origin',\n branch?: string,\n opts: { issueId?: string } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n const args = branch ? ['fetch', remote, branch] : ['fetch', remote];\n try {\n await execFileAsync('git', args, { cwd, encoding: 'utf-8', timeout: 30000 });\n appendGitOperationSync({\n operation: 'fetch',\n branch: branch ?? remote,\n issueId: opts.issueId,\n status: 'success',\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'fetch',\n branch: branch ?? remote,\n issueId: opts.issueId,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}async function gitPushPromise(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n\n // Step 1: record the local HEAD before the push\n const localSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? 'unknown';\n\n // Step 2: fetch latest remote state\n await Effect.runPromise(gitFetch(cwd, remote, branch, opts));\n\n // Step 3: read the remote tracking SHA\n const remoteSha = await Effect.runPromise(gitRevParse(cwd, `${remote}/${branch}`)) ?? '';\n\n // Step 4: ancestor check — is remoteSha an ancestor of localSha?\n if (remoteSha) {\n try {\n // git merge-base --is-ancestor <commit> <commit> exits 0 if true, 1 if false\n await execFileAsync('git', ['merge-base', '--is-ancestor', remoteSha, localSha], {\n cwd,\n encoding: 'utf-8',\n timeout: 10000,\n });\n } catch (err: unknown) {\n // exit code 1 = \"not an ancestor\" (true divergence); any other code is a real git error\n if ((err as { code?: number }).code !== 1) throw err;\n appendGitOperationSync({\n operation: 'main_diverged',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n remoteSha,\n status: 'aborted',\n error: `origin/${branch} (${remoteSha.slice(0, 7)}) is not ancestor of local HEAD (${localSha.slice(0, 7)})`,\n ts,\n });\n throw new MainDivergedError(localSha, remoteSha);\n }\n }\n\n // Step 5: push\n try {\n await execFileAsync('git', ['push', remote, branch], { cwd, encoding: 'utf-8', timeout: 60000 });\n const afterSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? localSha;\n appendGitOperationSync({\n operation: 'push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n afterSha,\n remoteSha,\n status: 'success',\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n remoteSha,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}async function gitForcePushPromise(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string; reason?: string } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n const localSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? 'unknown';\n const remoteSha = await Effect.runPromise(gitRevParse(cwd, `${remote}/${branch}`)) ?? undefined;\n\n try {\n await execFileAsync('git', ['push', '--force-with-lease', remote, branch], {\n cwd,\n encoding: 'utf-8',\n timeout: 60000,\n });\n const afterSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? localSha;\n appendGitOperationSync({\n operation: 'force_push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n afterSha,\n remoteSha,\n status: 'success',\n error: opts.reason,\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'force_push',\n branch,\n issueId: opts.issueId,\n beforeSha: localSha,\n remoteSha,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}async function gitMergePromise(\n cwd: string,\n branch: string,\n opts: { issueId?: string; noFf?: boolean } = {},\n): Promise<void> {\n const ts = new Date().toISOString();\n const beforeSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? 'unknown';\n const args = opts.noFf ? ['merge', '--no-ff', branch] : ['merge', branch];\n try {\n await execFileAsync('git', args, { cwd, encoding: 'utf-8', timeout: 60000 });\n const afterSha = await Effect.runPromise(gitRevParse(cwd, 'HEAD')) ?? beforeSha;\n appendGitOperationSync({\n operation: 'merge',\n branch,\n issueId: opts.issueId,\n beforeSha,\n afterSha,\n status: 'success',\n ts,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n appendGitOperationSync({\n operation: 'merge',\n branch,\n issueId: opts.issueId,\n beforeSha,\n status: 'failure',\n error: msg,\n ts,\n });\n throw err;\n }\n}\n\n// ─── Effect variants (PAN-1249, additive) ────────────────────────────────────\n//\n// These wrap the existing Promise-based functions with typed Effect error\n// channels (GitError / VcsError). The underlying impl is unchanged; this is\n// an additive surface for callers that already speak Effect. MainDivergedError\n// is thrown by the underlying gitPush; the Effect variant surfaces it\n// preserved as the cause inside a VcsError tagged 'main-diverged'.\n\n/** Resolve a git ref to its SHA. Returns null if the ref does not exist. */\nexport function gitRevParse(\n cwd: string,\n ref: string,\n): Effect.Effect<string | null> {\n return Effect.promise(() => gitRevParsePromise(cwd, ref));\n}\n\n/** Fetch from a remote. */\nexport function gitFetch(\n cwd: string,\n remote = 'origin',\n branch?: string,\n opts: { issueId?: string } = {},\n): Effect.Effect<void, GitError> {\n return Effect.tryPromise({\n try: () => gitFetchPromise(cwd, remote, branch, opts),\n catch: (cause) =>\n new GitError({\n command: branch ? ['git', 'fetch', remote, branch] : ['git', 'fetch', remote],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n }),\n });\n}\n\n/**\n * Push a branch to a remote with divergence guard.\n * Fails with VcsError tagged 'main-diverged' when MainDivergedError is thrown.\n */\nexport function gitPush(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string } = {},\n): Effect.Effect<void, VcsError | GitError> {\n return Effect.tryPromise({\n try: () => gitPushPromise(cwd, remote, branch, opts),\n catch: (cause) => {\n if (cause instanceof MainDivergedError) {\n return new VcsError({\n operation: 'main-diverged',\n message: cause.message,\n cause,\n });\n }\n return new GitError({\n command: ['git', 'push', remote, branch],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n });\n },\n });\n}\n\n/** Force-push a branch (--force-with-lease). */\nexport function gitForcePush(\n cwd: string,\n remote = 'origin',\n branch = 'main',\n opts: { issueId?: string; reason?: string } = {},\n): Effect.Effect<void, GitError> {\n return Effect.tryPromise({\n try: () => gitForcePushPromise(cwd, remote, branch, opts),\n catch: (cause) =>\n new GitError({\n command: ['git', 'push', '--force-with-lease', remote, branch],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n }),\n });\n}\n\n/** Merge a branch into the current branch. */\nexport function gitMerge(\n cwd: string,\n branch: string,\n opts: { issueId?: string; noFf?: boolean } = {},\n): Effect.Effect<void, GitError> {\n return Effect.tryPromise({\n try: () => gitMergePromise(cwd, branch, opts),\n catch: (cause) =>\n new GitError({\n command: opts.noFf ? ['git', 'merge', '--no-ff', branch] : ['git', 'merge', branch],\n stderr: cause instanceof Error ? cause.message : String(cause),\n exitCode: -1,\n cause,\n }),\n });\n}\n","/**\n * Merge Agent - Automatic merge conflict resolution using Claude Code\n */\n\nimport { existsSync, mkdirSync, appendFileSync } from 'fs';\nimport { writeFile } from 'fs/promises';\nimport { join, dirname, basename, relative } from 'path';\nimport { fileURLToPath } from 'url';\nimport { spawn, exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { capturePane, killSession, listSessionNames, sendKeys, sessionExists } from '../tmux.js';\nimport { emitActivityEntrySync, emitActivityTtsSync } from '../activity-logger.js';\nimport { loadConfigSync } from '../config-yaml.js';\nimport { shouldRestartForPostMerge } from './merge-agent-step0.js'; import { capturePipelineStageForIssue } from '../telemetry/pipeline.js';\nimport { enqueueMergedDockerCleanup } from './merged-docker-cleanup-worker.js';\nimport { withPostMergeLifecycleLock } from './post-merge-lifecycle-lock.js';\nimport {\n completedPostMerge as _completedPostMerge,\n postMergeInFlight as _postMergeInFlight,\n resetPostMergeState,\n} from './post-merge-state.js';\nimport {\n ensureSyncGitQuiescent,\n probeGitOperationHeads,\n runSyncGitCommand,\n SyncGitCommandAbortError,\n SyncGitCommandTimeoutError,\n} from './sync-main-git.js';\nimport { syncMainAcrossWorkspaceRepos, type SyncMainRepoResult, type SyncMainResult } from './sync-main-workspace.js';\nexport type { SyncMainRepoResult, SyncMainResult } from './sync-main-workspace.js';\n\nconst execAsync = promisify(exec);\n\nconst SYNC_GIT_FETCH_TIMEOUT_MS = 60_000;\nconst SYNC_GIT_MERGE_TIMEOUT_MS = 120_000;\nconst SYNC_GIT_COMMIT_TIMEOUT_MS = 60_000;\nconst SYNC_GIT_STATUS_TIMEOUT_MS = 30_000;\n\n/**\n * Paths that must never enter a pipeline auto-commit, regardless of gitignore\n * state. These are workspace-local or machine-local state files and sync-target\n * directories; committing them pollutes feature branches and main.\n */\nexport const AUTO_COMMIT_EXCLUDED_PATHS = [\n '.pan/kickoff.md',\n '.pan/continue.json',\n '.pan/handoff-*.md',\n '.pan/spec.vbrief.json',\n '.claude/rules/',\n '.claude/skills/',\n // PAN-1899: machine-local Overdeck config copied into every workspace by\n // copyOverdeckSettingsToWorkspaceSync (config.yaml, projects.yaml,\n // settings.json). It diverges from main constantly and must never enter a\n // feature branch — that was the source of the recurring projects.yaml sync\n // conflict.\n '.overdeck/',\n];\n\nconst SYNC_MAIN_MAIN_PREFERRED_PATHS = [\n '.pan/continues',\n '.pan/specs',\n];\n\nexport function isSyncMainMainPreferredPath(relativePath: string): boolean {\n const normalized = relativePath.replace(/\\\\/g, '/');\n return SYNC_MAIN_MAIN_PREFERRED_PATHS.some(\n (prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`),\n );\n}\n\nexport function isAutoCommitExcludedPath(relativePath: string): boolean {\n const normalized = relativePath.replace(/\\\\/g, '/');\n for (const pattern of AUTO_COMMIT_EXCLUDED_PATHS) {\n if (pattern.endsWith('/')) {\n if (normalized.startsWith(pattern) || normalized === pattern.slice(0, -1)) {\n return true;\n }\n } else if (pattern.includes('*')) {\n const regex = new RegExp(\n '^' + pattern.replace(/\\./g, '\\\\.').replace(/\\*/g, '[^/]*') + '$'\n );\n if (regex.test(normalized)) return true;\n } else if (normalized === pattern) {\n return true;\n }\n }\n return false;\n}\n\nfunction parseStatusPath(line: string): string {\n // git status --porcelain lines are \"XY PATH\" or \"XY ORIG -> DEST\" for renames.\n const body = line.slice(3);\n if (line[0] === 'R' || line[1] === 'R') {\n const parts = body.split(' -> ');\n return parts[parts.length - 1];\n }\n return body;\n}\n\n/**\n * Auto-commit non-excluded workspace changes before a sync-main merge.\n * Respects .gitignore (no -f), unstages excluded paths, and leaves excluded\n * paths dirty so the sync can proceed. Returns success=false on git errors.\n */\nexport async function autoCommitWorkspaceChangesBeforeSync(\n projectPath: string,\n issueId?: string,\n signal?: AbortSignal,\n): Promise<{ success: boolean; committed: boolean; reason?: string }> {\n const run = (command: string, timeout = SYNC_GIT_STATUS_TIMEOUT_MS) =>\n runSyncGitCommand(command, { cwd: projectPath, timeout, signal });\n try {\n const operationHeads = await probeGitOperationHeads(projectPath, SYNC_GIT_STATUS_TIMEOUT_MS, signal);\n if (!operationHeads.success) return { success: false, committed: false, reason: operationHeads.reason };\n if (operationHeads.present.length > 0) {\n return { success: false, committed: false, reason: `Refusing to auto-commit while ${operationHeads.present[0]} exists; finish or abort the in-progress Git operation first` };\n }\n\n try {\n const { stdout: conflictMarkers } = await run(\"git grep -n -I -E '^(<<<<<<<( |$)|=======$|>>>>>>>( |$))' -- .\");\n if (conflictMarkers.trim()) {\n const files = [...new Set(conflictMarkers.trim().split('\\n').map((line) => line.split(':', 1)[0]))];\n return { success: false, committed: false, reason: `Refusing to auto-commit conflict markers in: ${files.join(', ')}` };\n }\n } catch (error: any) {\n if (error?.code !== 1) return { success: false, committed: false, reason: `Failed to scan for conflict markers: ${error.message}` };\n }\n\n const { stdout: statusOut } = await run('git status --porcelain');\n if (!statusOut.trim()) return { success: true, committed: false, reason: 'no uncommitted changes' };\n\n await run('git add -A');\n const resetPaths = AUTO_COMMIT_EXCLUDED_PATHS.map((p) => p.endsWith('/') ? p.slice(0, -1) : p).join(' ');\n await run(`git reset HEAD -- ${resetPaths}`);\n\n const { stdout: diffStat } = await run('git diff --cached --stat');\n if (!diffStat.trim()) return { success: true, committed: false, reason: 'only excluded/ignored changes remain' };\n\n const commitMessage = issueId ? `chore: auto-commit before sync with main (${issueId})` : 'chore: auto-commit before sync with main';\n await run(`git commit -m \"${commitMessage}\"`, SYNC_GIT_COMMIT_TIMEOUT_MS);\n return { success: true, committed: true };\n } catch (error: any) {\n if (error instanceof SyncGitCommandAbortError || error instanceof SyncGitCommandTimeoutError) {\n await ensureSyncGitQuiescent(projectPath, false);\n }\n const reason = error instanceof SyncGitCommandTimeoutError && error.command.startsWith('git commit ')\n ? `Auto-commit git commit timed out after ${SYNC_GIT_COMMIT_TIMEOUT_MS / 1_000}s`\n : `Failed to auto-commit: ${error.message}`;\n return { success: false, committed: false, reason };\n }\n}\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nimport {\n OVERDECK_HOME,\n} from '../paths.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\n\nimport { runQualityGates } from './validation.js';\nimport { loadProjectsConfigSync } from '../projects.js';\nimport { cleanupStaleLocks } from '../git-utils.js';\nimport { gitPush, MainDivergedError } from '../git/operations.js';\nimport { getReviewStatusSync, markWorkspaceStuck, setReviewStatusSync } from '../review-status.js';\nimport { appendGitOperationSync, type GitOperationType } from '../git-activity.js';\nimport { recordFeatureRegistryLifecycle } from '../registry/feature-registry-population.js';\nimport { verifyMergedBeforeLifecycle, type PostMergeLifecycleOptions } from './merge-verification.js';\n\nconst SPECIALISTS_DIR = join(OVERDECK_HOME, 'specialists');\nconst MERGE_HISTORY_DIR = join(SPECIALISTS_DIR, 'merge-agent');\nconst MERGE_HISTORY_FILE = join(MERGE_HISTORY_DIR, 'history.jsonl');\n\n/**\n * Context for a merge conflict resolution request\n */\nexport interface MergeConflictContext {\n projectPath: string;\n sourceBranch: string;\n targetBranch: string;\n conflictFiles: string[];\n issueId: string;\n testCommand?: string;\n}\n\n/**\n * Result of merge agent execution\n */\nexport interface MergeResult {\n success: boolean;\n resolvedFiles?: string[];\n failedFiles?: string[];\n testsStatus?: 'PASS' | 'FAIL' | 'SKIP';\n validationStatus?: 'PASS' | 'FAIL' | 'NOT_RUN';\n reason?: string;\n notes?: string;\n output?: string;\n}\n\n/**\n * Merge history entry\n */\ninterface MergeHistoryEntry {\n timestamp: string;\n issueId: string;\n sourceBranch: string;\n targetBranch: string;\n conflictFiles: string[];\n result: MergeResult;\n sessionId?: string;\n}\n\n/**\n * Timeout for merge agent in milliseconds (15 minutes)\n */\n/**\n * Notify TLDR daemon to reindex changed files after merge\n */\nexport async function notifyTldrDaemon(projectPath: string, _sourceBranch: string): Promise<void> {\n try {\n console.log(`[merge-agent] Notifying TLDR daemon to reindex changed files...`);\n\n // Check if TLDR daemon is available\n const venvPath = join(projectPath, '.venv');\n if (!existsSync(venvPath)) {\n console.log(`[merge-agent] No .venv found, skipping TLDR notification`);\n return;\n }\n\n // Get changed files from the merge\n const { stdout } = await execAsync(`git diff --name-only HEAD~1 HEAD`, {\n cwd: projectPath,\n encoding: 'utf-8'\n });\n\n const changedFiles = stdout\n .trim()\n .split('\\n')\n .filter(f => f.trim().length > 0)\n .filter(f => {\n // Only include source code files (skip docs, configs, etc)\n const ext = f.split('.').pop()?.toLowerCase();\n return ext && ['ts', 'js', 'tsx', 'jsx', 'py', 'java', 'go', 'rs', 'cpp', 'c', 'h'].includes(ext);\n });\n\n if (changedFiles.length === 0) {\n console.log(`[merge-agent] No source files changed, skipping TLDR notification`);\n return;\n }\n\n console.log(`[merge-agent] Found ${changedFiles.length} changed source files to reindex`);\n\n // Get TLDR daemon service\n const { getTldrDaemonServiceSync } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonServiceSync(projectPath, venvPath);\n\n // Check if daemon is running\n const status = await tldrService.getStatus();\n if (!status.running) {\n console.log(`[merge-agent] TLDR daemon not running, skipping notification`);\n return;\n }\n\n // Trigger warm to reindex (this will update the index incrementally)\n console.log(`[merge-agent] Triggering TLDR index warm...`);\n await tldrService.warm(true); // background mode\n\n console.log(`[merge-agent] ✓ TLDR daemon notified to reindex`);\n logActivity('tldr_notified', `Notified TLDR daemon to reindex ${changedFiles.length} files`);\n } catch (error: any) {\n // Non-fatal - log warning and continue\n console.warn(`[merge-agent] Failed to notify TLDR daemon: ${error.message}`);\n logActivity('tldr_notify_error', `TLDR notification failed: ${error.message}`);\n }\n}\n\n/**\n * Post-merge handoff: mark merged work as verifying on main and free runtime resources.\n *\n * Leaves the issue, workspace, xBRIEF, branches, and agent state dirs intact.\n * The explicit close-out ceremony performs final archival and destructive cleanup.\n *\n * IDEMPOTENT: Safe to call multiple times for the same issueId. Tracks completed\n * issues and returns immediately on re-entry. This is defense-in-depth against\n * the infinite loop that burned 24,626 Linear API calls (PAN-328).\n */\n\n// PAN-1531: dropLingeringPreMergeStashes removed. The pre-merge stash kind\n// is no longer created by Overdeck, so there's nothing for the post-merge\n// lifecycle to clean up. Pre-existing pre-merge:* residue in refs/stash is\n// expected and inert.\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nexport async function postMergeLifecycle(\n issueId: string,\n projectPath: string,\n sourceBranch?: string,\n options?: PostMergeLifecycleOptions,\n): Promise<void> {\n // PAN-1517: postMergeLifecycle fires only when the issue's main feature branch merges to `main`.\n // Guard 1: skip if already completed (defense-in-depth against infinite loops)\n if (_completedPostMerge.has(issueId) || getReviewStatusSync(issueId)?.mergeStep === 'merged') {\n _completedPostMerge.add(issueId);\n console.log(`[merge-agent] postMergeLifecycle already completed for ${issueId}, skipping`);\n return;\n }\n\n const inFlight = _postMergeInFlight.get(issueId);\n if (inFlight) {\n console.log(`[merge-agent] postMergeLifecycle already running for ${issueId}, joining in-flight run`);\n return inFlight;\n }\n\n const run = withPostMergeLifecycleLock(issueId, async () => {\n if (_completedPostMerge.has(issueId) || getReviewStatusSync(issueId)?.mergeStep === 'merged') {\n _completedPostMerge.add(issueId); console.log(`[merge-agent] postMergeLifecycle completed while waiting for ${issueId}, skipping`);\n return;\n }\n // Guard 2: closed-out is TERMINAL. Close-out flips the spec to completed/cancelled,\n // clears review status, and closes the tracker issue.\n // Re-running the handoff after that resurrects the review row and REOPENS\n // the closed issue — observed live on PAN-1190 (2026-06-11): the deacon's\n // stale-mergeStatus sweep saw the cleared row as \"stale\" 47 minutes after\n // close-out and the handoff reopened it into verifying-on-main forever.\n try {\n const { findSpecByIssue } = await import('../pan-dir/specs.js');\n const spec = await Effect.runPromise(findSpecByIssue(projectPath, issueId));\n if (spec && (spec.status === 'completed' || spec.status === 'cancelled')) {\n console.log(`[merge-agent] ${issueId} is closed out (spec ${spec.status}) — skipping post-merge lifecycle`);\n _completedPostMerge.add(issueId);\n return;\n }\n } catch {\n // Spec unreadable — proceed; the guard is best-effort.\n }\n\n const mergeVerification = await verifyMergedBeforeLifecycle(issueId, projectPath, sourceBranch, options);\n if (!mergeVerification.merged) {\n console.warn(`[merge-agent] Refusing post-merge lifecycle for ${issueId}: ${mergeVerification.reason}`);\n return;\n }\n console.log(`[merge-agent] Verified merge before lifecycle for ${issueId}: ${mergeVerification.reason}`);\n\n // Set mergeStatus='merged' after verifying the branch or PR actually landed.\n try {\n setReviewStatusSync(issueId, {\n mergeStatus: 'merged', mergeStep: 'post-merge-cleanup',\n readyForMerge: false,\n ...(options?.markReviewPassed ? { reviewStatus: 'passed' as const } : {}),\n });\n console.log(`[merge-agent] ✓ mergeStatus set to 'merged' for ${issueId}`);\n } catch (err: any) {\n console.warn(`[merge-agent] Could not set mergeStatus: ${err.message}`);\n }\n // Eager Docker cleanup must run before any fatal post-merge handoff step.\n let dockerRetryReason: string | null = null;\n try {\n const { teardownWorkspaceDockerByNamePromise } = await import('../workspace-manager/docker.js');\n const teardown = await teardownWorkspaceDockerByNamePromise(issueId.toLowerCase());\n if (teardown.networkRemoved) {\n console.log(`[merge-agent] ✓ Removed Docker stack/network: ${teardown.steps.join('; ')}`);\n logActivity('docker_cleanup', `Removed Docker stack/network for ${issueId}: ${teardown.steps.join('; ')}`);\n } else {\n dockerRetryReason = teardown.steps.join('; ') || 'network remained after teardown';\n }\n } catch (err) {\n dockerRetryReason = err instanceof Error ? err.message : String(err);\n }\n if (dockerRetryReason) {\n enqueueMergedDockerCleanup(issueId, { mergeVerified: true });\n console.warn(`[merge-agent] Docker cleanup queued for retry (non-fatal): ${dockerRetryReason}`);\n }\n\n // Step 0: restart only when the running build is stale and the deploy window is safe.\n if (!options?.skipDeploy) {\n const pendingFile = join(OVERDECK_HOME, 'pending-post-merge.json');\n let repoRoot = __dirname.includes('/src/')\n ? __dirname.replace(/\\/src\\/.*$/, '')\n : __dirname.replace(/\\/dist\\/.*$/, '').replace(/\\/lib\\/.*$/, '');\n // If running from a workspace (workspaces/feature-*/), resolve to the main repo root.\n // Without this, the deploy script builds and npm-links from the workspace, hijacking\n // the global `pan` CLI to point at stale workspace code.\n const wsMatch = repoRoot.match(/^(.+)\\/workspaces\\/feature-[^/]+$/);\n if (wsMatch) {\n repoRoot = wsMatch[1];\n console.log(`[merge-agent] Resolved workspace repoRoot to main repo: ${repoRoot}`);\n }\n const deployScript = join(repoRoot, 'scripts', 'post-merge-deploy.sh');\n\n if (await shouldRestartForPostMerge(repoRoot)) try {\n const pendingData = JSON.stringify({\n issueId,\n projectPath,\n sourceBranch: sourceBranch ?? '',\n timestamp: Date.now(),\n reason: 'post-merge',\n trigger: 'merge-agent',\n });\n await writeFile(pendingFile, pendingData, 'utf-8');\n console.log(`[merge-agent] Wrote pending lifecycle file: ${pendingFile}`);\n\n // Pass 'post-merge' as the reason to the deploy script so it writes the\n // restart marker. We spawn detached and return immediately — the deploy script\n // kills this server. The new server reads the pending file on boot,\n // emits lifecycle_started, and after processing emits lifecycle_complete/failed.\n const child = spawn(deployScript, [repoRoot, issueId, projectPath, sourceBranch ?? '', 'post-merge'], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n console.log(`[merge-agent] Spawned detached deploy script (pid ${child.pid}) — server will restart with new build`);\n return;\n } catch (err: any) {\n console.warn(`[merge-agent] Failed to spawn deploy script: ${err.message}. Falling through to in-process lifecycle (may fail on stale chunks).`);\n }\n }\n\n console.log(`[merge-agent] Running post-merge verify handoff for ${issueId}`);\n\n // 1. Clean up stale workflow labels and keep the legacy merged marker for history.\n // verifying-on-main is applied next and takes precedence in canonical state mapping.\n try {\n const { cleanupMergedLabels } = await import('../lifecycle/label-cleanup.js');\n const ghResolved = resolveGitHubIssueSync(issueId);\n const labelCtx = ghResolved.isGitHub\n ? { issueId, projectPath, github: { owner: ghResolved.owner, repo: ghResolved.repo, number: ghResolved.number } }\n : { issueId, projectPath };\n // PAN-1249: cleanupMergedLabels returns Effect<StepResult>; bridge to Promise.\n const labelResult = await Effect.runPromise(cleanupMergedLabels(labelCtx));\n if (labelResult.success && !labelResult.skipped) {\n console.log(`[merge-agent] ✓ ${labelResult.details?.join('; ')}`);\n logActivity('labels_cleaned', labelResult.details?.join('; ') || 'Labels cleaned');\n } else if (labelResult.skipped) {\n console.log(`[merge-agent] Label cleanup skipped: ${labelResult.details?.join('; ')}`);\n } else {\n console.warn(`[merge-agent] Label cleanup failed (non-fatal): ${labelResult.error}`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not clean labels: ${err}`);\n }\n\n try {\n await transitionIssueToVerifyingOnMain(issueId, projectPath);\n void recordFeatureRegistryLifecycle({ issueId, status: 'merged' });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Could not transition issue to verifying_on_main: ${message}`);\n try {\n setReviewStatusSync(issueId, {\n mergeStatus: 'merged', mergeStep: 'post-merge-cleanup',\n readyForMerge: false,\n mergeNotes: `Post-merge verifying_on_main transition failed: ${message}`,\n });\n } catch (statusErr: any) {\n console.warn(`[merge-agent] Could not persist verifying_on_main transition failure: ${statusErr?.message ?? statusErr}`);\n }\n announceMerge('failed', issueId, `Post-merge verifying_on_main transition failed: ${message}`);\n logActivity('merge_failed', `Post-merge verifying_on_main transition failed for ${issueId}: ${message}`);\n throw err;\n }\n\n // Release verification runs asynchronously so post-merge cleanup is not\n // delayed by a slow external deploy. Status is still reported via review-status.\n triggerPostMergeReleaseIfConfigured(issueId, projectPath).catch((err) => {\n console.warn(`[merge-agent] Async post-merge release trigger failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n });\n\n // 3. Pause work/planning/strike agents and kill their tmux panes to free resources.\n try {\n const { setAgentPaused, getAgentState } = await import('../agents.js');\n const { killSession, sessionExists } = await import('../tmux.js');\n const issueLower = issueId.toLowerCase();\n const reason = 'awaiting close-out (verify on main)';\n for (const agentId of [`agent-${issueLower}`, `planning-${issueLower}`, `strike-${issueLower}`]) {\n // Pause, then VERIFY the gate actually persisted to state.json. A server\n // restart mid-lifecycle (the PAN-1723 deploy re-runs this from\n // pending-post-merge.json) or a concurrent deacon read-modify-write on\n // state.json can silently drop the pause. An unpaused merged work agent\n // sits idle at its prompt yet still counts against the PAN-1665 work\n // ceiling, throttling dispatch for every live issue (PAN-1726). Assert\n // paused=true after the write; retry once, then fail loudly.\n const initial = await Effect.runPromise(setAgentPaused(agentId, reason, true));\n if (initial === null) {\n // No state.json for this agent — nothing to pause (e.g. planning never ran).\n continue;\n }\n let verify = await Effect.runPromise(getAgentState(agentId));\n if (verify?.paused !== true) {\n await Effect.runPromise(setAgentPaused(agentId, reason, true));\n verify = await Effect.runPromise(getAgentState(agentId));\n }\n if (verify?.paused === true) {\n console.log(`[merge-agent] ✓ Paused ${agentId}: ${reason}`);\n } else {\n console.error(\n `[merge-agent] ✗ FAILED to persist pause for ${agentId} after merge — state.json paused=${verify?.paused}. ` +\n `Idle merged work agent may hold a PAN-1665 work slot and throttle dispatch (PAN-1726).`,\n );\n logActivity('agent_pause_failed', `Could not persist pause for ${agentId} after merge — may throttle dispatch (PAN-1726)`);\n }\n if (await Effect.runPromise(sessionExists(agentId))) {\n await Effect.runPromise(killSession(agentId));\n console.log(`[merge-agent] ✓ Killed ${agentId} tmux session to free resources`);\n logActivity('agent_session_killed', `Freed resources: killed tmux session for ${agentId}`);\n }\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not pause or kill agent sessions: ${err}`);\n }\n\n // 5a. Kill canonical reviewer/synthesis sessions (PAN-915).\n // Sessions persist across review rounds to preserve reviewer context, so the\n // merge is the right moment to tear them down. Issue is done — context value\n // is zero, RSS leak risk is non-zero. Resolve projectKey from the project\n // path so we don't depend on caller-supplied config.\n try {\n const { killAllReviewerSessions } = await import('./review-agent.js');\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n const projectKey = resolved?.projectKey;\n if (projectKey) {\n const { killed } = await Effect.runPromise(killAllReviewerSessions(projectKey, issueId));\n if (killed.length > 0) {\n console.log(`[merge-agent] ✓ Killed ${killed.length} canonical reviewer session(s) for ${issueId}`);\n logActivity('reviewer_sessions_killed', `Killed ${killed.length} reviewer session(s) for ${issueId} on merge`);\n }\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not kill canonical reviewer sessions: ${err}`);\n }\n\n await killPostMergeRoleSessions(issueId);\n\n // 3c. Create a workspace-scoped memory reset marker so old review-blocker noise stays archived but out of retrieval.\n try {\n const { createResetMarker } = await import('../memory/cli.js');\n const timestamp = new Date().toISOString();\n const marker = await createResetMarker({\n projectId: basename(projectPath),\n scope: 'workspace',\n scopeId: `feature-${issueId.toLowerCase()}`,\n reason: 'post-merge cleanup',\n fromTimestamp: timestamp,\n createdAt: timestamp,\n });\n console.log(`[merge-agent] ✓ Created memory reset marker ${marker.id} for ${marker.scope}:${marker.scopeId}`);\n } catch (err) {\n console.warn(`[merge-agent] Memory reset marker creation failed (non-fatal): ${err}`);\n }\n await notifyTldrDaemon(projectPath, sourceBranch ?? '');\n await maybeSpawnPostMergeKnowledgeRetro(issueId, projectPath);\n\n setReviewStatusSync(issueId, { mergeStatus: 'merged', mergeStep: 'merged', readyForMerge: false });\n _completedPostMerge.add(issueId); void capturePipelineStageForIssue(issueId, 'merged');\n\n console.log(`[merge-agent] Post-merge handoff completed for ${issueId}. Awaiting close-out (verify on main).`);\n announceMerge('completed', issueId);\n logActivity('merge_complete', `Merged ${issueId}. Awaiting close-out (verify on main).`);\n }).finally(() => { _postMergeInFlight.delete(issueId); });\n _postMergeInFlight.set(issueId, run);\n return run;\n}\n\nexport async function triggerPostMergeReleaseIfConfigured(issueId: string, projectPath: string): Promise<void> {\n const currentStatus = getReviewStatusSync(issueId)?.releaseStatus;\n if (currentStatus && currentStatus !== 'pending') {\n console.log(`[merge-agent] Release already started or completed for ${issueId} (${currentStatus}), skipping`);\n return;\n }\n\n const { resolveProjectFromIssueSync, getProjectSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n const project = resolved ? getProjectSync(resolved.projectKey) : null;\n\n if (!project?.release) {\n setReviewStatusSync(issueId, {\n releaseStatus: 'skipped',\n releaseNotes: 'No release config found for project.',\n });\n console.log(`[merge-agent] No release config for ${issueId}; marked release skipped`);\n return;\n }\n\n const { runRelease } = await import('../release/release-engine.js');\n try {\n await runRelease(issueId, projectPath);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Post-merge release trigger failed for ${issueId}: ${message}`);\n try {\n setReviewStatusSync(issueId, {\n releaseStatus: 'failed',\n releaseNotes: `Post-merge release trigger failed: ${message}`,\n });\n } catch (statusErr: any) {\n console.warn(`[merge-agent] Could not persist release trigger failure: ${statusErr?.message ?? statusErr}`);\n }\n // Release failures are surfaced through release status; the merge itself\n // has already completed and must not be marked failed (review feedback).\n }\n}\n\nasync function transitionIssueToVerifyingOnMain(issueId: string, projectPath: string): Promise<void> {\n const [effectModule, issueLifecycleModule, githubModule, linearModule, rallyModule, errorsModule] = await Promise.all([\n import('effect'),\n import('../../dashboard/server/services/issue-lifecycle.js'),\n import('../../dashboard/server/services/github-client.js'),\n import('../../dashboard/server/services/linear-client.js'),\n import('../../dashboard/server/services/rally-client.js'),\n import('../../dashboard/server/services/typed-errors.js'),\n ]);\n const { Effect, Layer } = effectModule;\n const { IssueLifecycle, IssueLifecycleLive } = issueLifecycleModule;\n const { GitHubClient } = githubModule;\n const { LinearClientOptionalLive } = linearModule;\n const { RallyClientOptionalLive } = rallyModule;\n const { IssueNotFound } = errorsModule;\n const gitHubRepo = (owner: string, repo: string) => shellQuote(`${owner}/${repo}`);\n const githubLayer = Layer.succeed(GitHubClient, {\n getIssue: (_owner: string, _repo: string, number: number) => Effect.fail(new IssueNotFound({ id: String(number) })),\n closeIssue: (owner: string, repo: string, number: number) => Effect.promise(() => execAsync(`gh issue close ${number} --repo ${gitHubRepo(owner, repo)}`, { cwd: projectPath, encoding: 'utf-8' }).then(() => undefined)),\n reopenIssue: (owner: string, repo: string, number: number) => Effect.promise(() => execAsync(`gh issue reopen ${number} --repo ${gitHubRepo(owner, repo)} 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' }).then(() => undefined)),\n addLabel: (owner: string, repo: string, number: number, label: string) => Effect.promise(async () => {\n if (label === 'verifying-on-main') {\n await execAsync(`gh label create ${shellQuote(label)} --repo ${gitHubRepo(owner, repo)} --color \"fbca04\" --description \"Merged — awaiting verification on main\" --force 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' });\n }\n await execAsync(`gh issue edit ${number} --repo ${gitHubRepo(owner, repo)} --add-label ${shellQuote(label)}`, { cwd: projectPath, encoding: 'utf-8' });\n }),\n removeLabel: (owner: string, repo: string, number: number, label: string) => Effect.promise(() => execAsync(`gh issue edit ${number} --repo ${gitHubRepo(owner, repo)} --remove-label ${shellQuote(label)} 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' }).then(() => undefined)),\n ensureLabel: (owner: string, repo: string, label: string, color = '0075ca', description = '') => Effect.promise(async () => {\n await execAsync(`gh label create ${shellQuote(label)} --repo ${gitHubRepo(owner, repo)} --color ${shellQuote(color)} --description ${shellQuote(description)} --force 2>/dev/null || true`, { cwd: projectPath, encoding: 'utf-8' });\n return { id: 0, name: label, color };\n }),\n addComment: () => Effect.void,\n getComments: () => Effect.succeed([]),\n });\n const layer = IssueLifecycleLive.pipe(\n Layer.provide(LinearClientOptionalLive),\n Layer.provide(githubLayer),\n Layer.provide(RallyClientOptionalLive),\n );\n await Effect.runPromise(\n Effect.gen(function* () {\n const lifecycle = yield* IssueLifecycle;\n yield* lifecycle.transitionTo(issueId, 'verifying_on_main');\n }).pipe(Effect.provide(layer)),\n );\n console.log(`[merge-agent] ✓ Transitioned ${issueId} to verifying_on_main`);\n}\n\nfunction isPostMergeRoleSession(sessionName: string, issueLower: string): boolean {\n if ([`agent-${issueLower}-test`, `agent-${issueLower}-ship`, `agent-${issueLower}-merge`].includes(sessionName)) {\n return true;\n }\n if (sessionName.startsWith(`agent-${issueLower}-review-`)) {\n return true;\n }\n return sessionName.startsWith('specialist-')\n && sessionName.includes(`-${issueLower}-`)\n && /-(review|test|merge|ship)(?:-|$)/.test(sessionName);\n}\n\nasync function killPostMergeRoleSessions(issueId: string): Promise<void> {\n try {\n const issueLower = issueId.toLowerCase();\n const sessions = await Effect.runPromise(listSessionNames());\n const targets = sessions.filter((session) => isPostMergeRoleSession(session, issueLower));\n for (const session of targets) {\n await Effect.runPromise(killSession(session));\n }\n if (targets.length > 0) {\n console.log(`[merge-agent] ✓ Killed ${targets.length} review/test/ship session(s) for ${issueId}`);\n logActivity('role_sessions_killed', `Killed ${targets.length} review/test/ship session(s) for ${issueId} on merge`);\n }\n } catch (err) {\n console.warn(`[merge-agent] Could not kill role sessions for ${issueId}: ${err}`);\n }\n}\n\nfunction isPostMergeKnowledgeRetroEnabled(): boolean {\n try {\n return loadConfigSync().config.knowledge?.postMergeAutoRetro === true;\n } catch (err) {\n console.warn(`[merge-agent] Could not read knowledge post-merge retro config: ${err}`);\n return false;\n }\n}\n\nasync function maybeSpawnPostMergeKnowledgeRetro(issueId: string, projectPath: string): Promise<void> {\n if (!isPostMergeKnowledgeRetroEnabled()) return;\n\n try {\n const child = spawn('pan', ['knowledge', issueId, '--retro'], {\n cwd: projectPath,\n detached: true,\n stdio: 'ignore',\n });\n child.once?.('error', (err) => {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Post-merge knowledge retro spawn failed for ${issueId}: ${message}`);\n logActivity('knowledge_retro_error', `Post-merge knowledge retro spawn failed for ${issueId}: ${message}`, issueId);\n });\n child.unref();\n console.log(`[merge-agent] Spawned post-merge knowledge retro for ${issueId} (pid ${child.pid})`);\n logActivity('knowledge_retro_spawned', `Spawned post-merge knowledge retro for ${issueId}`, issueId);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`[merge-agent] Post-merge knowledge retro spawn failed for ${issueId}: ${message}`);\n logActivity('knowledge_retro_error', `Post-merge knowledge retro spawn failed for ${issueId}: ${message}`, issueId);\n }\n}\n\nexport { resetPostMergeState } from './post-merge-state.js';\n\n/**\n * Parse result markers from agent output\n */\nexport function parseAgentOutput(output: string): MergeResult {\n const lines = output.split('\\n');\n\n let mergeResult: 'SUCCESS' | 'FAILURE' | null = null;\n let resolvedFiles: string[] = [];\n let failedFiles: string[] = [];\n let testsStatus: 'PASS' | 'FAIL' | 'SKIP' | null = null;\n let validationStatus: 'PASS' | 'FAIL' | null = null;\n let reason = '';\n let notes = '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Match MERGE_RESULT\n if (trimmed.startsWith('MERGE_RESULT:')) {\n const value = trimmed.substring('MERGE_RESULT:'.length).trim();\n if (value === 'SUCCESS' || value === 'FAILURE') {\n mergeResult = value;\n }\n }\n\n // Match RESOLVED_FILES\n if (trimmed.startsWith('RESOLVED_FILES:')) {\n const value = trimmed.substring('RESOLVED_FILES:'.length).trim();\n resolvedFiles = value\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n }\n\n // Match FAILED_FILES\n if (trimmed.startsWith('FAILED_FILES:')) {\n const value = trimmed.substring('FAILED_FILES:'.length).trim();\n failedFiles = value\n .split(',')\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n }\n\n // Match TESTS\n if (trimmed.startsWith('TESTS:')) {\n const value = trimmed.substring('TESTS:'.length).trim();\n if (value === 'PASS' || value === 'FAIL' || value === 'SKIP') {\n testsStatus = value;\n }\n }\n\n // Match VALIDATION\n if (trimmed.startsWith('VALIDATION:')) {\n const value = trimmed.substring('VALIDATION:'.length).trim();\n if (value === 'PASS' || value === 'FAIL') {\n validationStatus = value;\n }\n }\n\n // Match REASON\n if (trimmed.startsWith('REASON:')) {\n reason = trimmed.substring('REASON:'.length).trim();\n }\n\n // Match NOTES\n if (trimmed.startsWith('NOTES:')) {\n notes = trimmed.substring('NOTES:'.length).trim();\n }\n }\n\n // Build result\n if (mergeResult === 'SUCCESS') {\n return {\n success: true,\n resolvedFiles,\n testsStatus: testsStatus || 'SKIP',\n validationStatus: validationStatus || 'NOT_RUN',\n notes,\n output,\n };\n } else if (mergeResult === 'FAILURE') {\n return {\n success: false,\n failedFiles,\n validationStatus: validationStatus || 'NOT_RUN',\n reason,\n notes,\n output,\n };\n } else {\n // No structured result markers found - try to detect human-readable format\n // Agents sometimes output \"MERGE TASK COMPLETE\" instead of \"MERGE_RESULT: SUCCESS\"\n const lowerOutput = output.toLowerCase();\n\n // Check for success indicators\n const successIndicators = [\n 'merge task complete',\n 'successfully merged',\n 'merge complete',\n 'pushed merge commit',\n 'successfully merged and pushed',\n ];\n\n const failureIndicators = [\n 'merge failed',\n 'merge task failed',\n 'could not merge',\n 'conflict not resolved',\n ];\n\n const hasSuccessIndicator = successIndicators.some(i => lowerOutput.includes(i));\n const hasFailureIndicator = failureIndicators.some(i => lowerOutput.includes(i));\n\n if (hasSuccessIndicator && !hasFailureIndicator) {\n // Extract test status from output if mentioned\n let detectedTestStatus: 'PASS' | 'FAIL' | 'SKIP' = 'SKIP';\n if (lowerOutput.includes('tests: pass') || lowerOutput.includes('tests passed') ||\n output.match(/\\d+ passed/)) {\n detectedTestStatus = 'PASS';\n } else if (lowerOutput.includes('tests: fail') || lowerOutput.includes('tests failed')) {\n detectedTestStatus = 'FAIL';\n }\n\n console.log('[merge-agent] Detected success from human-readable output');\n return {\n success: true,\n testsStatus: detectedTestStatus,\n validationStatus: 'PASS',\n notes: 'Detected from human-readable output (agent did not use structured format)',\n output,\n };\n }\n\n if (hasFailureIndicator) {\n console.log('[merge-agent] Detected failure from human-readable output');\n return {\n success: false,\n validationStatus: 'NOT_RUN',\n reason: 'Detected merge failure from agent output',\n output,\n };\n }\n\n // Truly unrecognized output\n return {\n success: false,\n validationStatus: 'NOT_RUN',\n reason: 'Agent did not report result in expected format',\n output,\n };\n }\n}\n\n/**\n * Get conflict files from git status (async)\n */\nfunction isSyncGitTermination(error: unknown): boolean {\n return error instanceof SyncGitCommandAbortError || error instanceof SyncGitCommandTimeoutError;\n}\n\nasync function getConflictFiles(projectPath: string, signal?: AbortSignal): Promise<string[]> {\n try {\n const { stdout: status } = await runSyncGitCommand('git diff --name-only --diff-filter=U', {\n cwd: projectPath, timeout: SYNC_GIT_STATUS_TIMEOUT_MS, signal,\n });\n return status.split('\\n').map((line) => line.trim()).filter(Boolean);\n } catch (error) {\n if (isSyncGitTermination(error)) throw error;\n console.error('Failed to get conflict files:', error);\n return [];\n }\n}\n\nasync function resolveMainPreferredSyncConflicts(\n projectPath: string,\n conflictFiles: string[],\n targetBranch: string, signal?: AbortSignal,\n): Promise<{ success: boolean; reason?: string }> {\n if (conflictFiles.length === 0 || !conflictFiles.every(isSyncMainMainPreferredPath)) {\n return { success: false, reason: 'conflicts include non-pipeline-owned files' };\n }\n\n const run = (command: string, timeout = SYNC_GIT_STATUS_TIMEOUT_MS) =>\n runSyncGitCommand(command, { cwd: projectPath, timeout, signal });\n try {\n for (const path of SYNC_MAIN_MAIN_PREFERRED_PATHS) {\n await run(`git rm -r --quiet --ignore-unmatch -- ${path}`);\n await run(`git checkout origin/${targetBranch} -- ${path}`).catch((error) => {\n if (isSyncGitTermination(error)) throw error;\n // The path may not exist on the target branch. The preceding git rm\n // records that branch's deletion for this pipeline-owned path.\n });\n if ((await run(`git ls-files -- ${path}`)).stdout.trim()) await run(`git add -A -- ${path}`);\n }\n\n const remainingConflicts = await getConflictFiles(projectPath, signal);\n if (remainingConflicts.length > 0) {\n return { success: false, reason: `Unresolved conflicts remain: ${remainingConflicts.join(', ')}` };\n }\n\n await run('git commit --no-edit', SYNC_GIT_COMMIT_TIMEOUT_MS);\n return { success: true };\n } catch (error: any) {\n if (isSyncGitTermination(error)) throw error;\n return { success: false, reason: `Failed to auto-resolve pipeline-owned conflicts: ${error.message}` };\n }\n}\n\n/**\n * Log merge to history\n */\nexport function logMergeHistory(context: MergeConflictContext, result: MergeResult, sessionId?: string): void {\n // Ensure history directory exists\n if (!existsSync(MERGE_HISTORY_DIR)) {\n mkdirSync(MERGE_HISTORY_DIR, { recursive: true });\n }\n\n const entry: MergeHistoryEntry = {\n timestamp: new Date().toISOString(),\n issueId: context.issueId,\n sourceBranch: context.sourceBranch,\n targetBranch: context.targetBranch,\n conflictFiles: context.conflictFiles,\n result: {\n ...result,\n output: undefined, // Don't store full output in history\n },\n sessionId,\n };\n\n appendFileSync(MERGE_HISTORY_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n}\n\n/**\n * Log activity to the dashboard activity log (event-sourced via emitActivityEntry)\n */\nfunction logActivity(action: string, details: string, issueId?: string): void {\n emitActivityEntrySync({\n source: 'ship',\n level: action.includes('fail') || action.includes('error') ? 'error' : action.includes('warn') ? 'warn' : 'success',\n message: details,\n issueId,\n });\n}\n\n/**\n * Voice-worthy merge milestone announcement. Messages start with one of three\n * distinctive prefixes (\"Merge started\", \"Merge completed\", \"Merge failed\") so\n * pan-tts can filter merge-agent chatter and speak only these three events.\n *\n * Do not change these prefixes without updating the pan-tts filter\n * (~/Projects/pan-tts/src/pan_tts/__main__.py — ALLOWED_MERGE_PREFIXES).\n */\nfunction announceMerge(\n status: 'started' | 'completed' | 'failed',\n issueId: string,\n extra?: string,\n): void {\n const prefix = status === 'started'\n ? 'Merge started'\n : status === 'completed'\n ? 'Merge completed'\n : 'Merge failed';\n const tail = extra ? `. ${extra}` : '';\n emitActivityEntrySync({\n source: 'ship',\n level: status === 'failed' ? 'error' : 'success',\n message: `${prefix} for ${issueId}${tail}`,\n issueId,\n });\n // Upleveled TTS utterance — short, speakable, no issue prefix noise\n const ttsUtterance = status === 'started'\n ? `Starting merge for ${issueId}`\n : status === 'completed'\n ? `${issueId} merged to main`\n : `Merge failed for ${issueId}`;\n emitActivityTtsSync({\n utterance: ttsUtterance,\n priority: status === 'failed' ? 0 : 1,\n issueId,\n source: 'merge-agent',\n eventType: `mergeStatus.${status === 'completed' ? 'merged' : status === 'started' ? 'merging' : 'failed'}`,\n });\n}\n\n/**\n * Capture tmux output and look for result markers (async)\n */\nexport async function captureTmuxOutput(sessionName: string): Promise<string> {\n try {\n return await Effect.runPromise(capturePane(sessionName));\n } catch {\n return '';\n }\n}\n\n/** Patterns to match in tmux capture-pane output (git push/fetch lines) */\nexport const GIT_PATTERNS: Array<{ re: RegExp; operation: GitOperationType; level: 'info' | 'warn' | 'error' }> = [\n { re: /force-with-lease/i, operation: 'force_push_cmd', level: 'warn' },\n { re: /git push/i, operation: 'push_attempt', level: 'info' },\n { re: /git fetch/i, operation: 'fetch_attempt', level: 'info' },\n { re: /\\[rejected\\]/i, operation: 'push_rejected', level: 'error' },\n { re: /non-fast-forward/i, operation: 'non_ff', level: 'error' },\n { re: /retrying/i, operation: 'retry', level: 'warn' },\n { re: /\\[remote rejected\\]/i, operation: 'remote_rejected', level: 'error' },\n { re: /Everything up-to-date/i, operation: 'push_noop', level: 'info' },\n];\n\n/**\n * Scan tmux capture-pane output for git push/fetch patterns and emit each\n * as a git_operations row. Uses seenLineHashes to dedupe within a session.\n */\nexport function scanGitPatterns(\n output: string,\n seenLineHashes: Set<string>,\n issueId: string,\n branch?: string,\n): void {\n const lines = output.split('\\n');\n const ts = new Date().toISOString();\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n // Simple hash: first 120 chars (avoids hashing megabytes)\n const hash = trimmed.slice(0, 120);\n if (seenLineHashes.has(hash)) continue;\n\n for (const { re, operation, level } of GIT_PATTERNS) {\n if (re.test(trimmed)) {\n seenLineHashes.add(hash);\n appendGitOperationSync({\n operation,\n branch,\n issueId,\n status: level === 'error' ? 'failure' : 'success',\n error: level !== 'info' ? trimmed.slice(0, 200) : undefined,\n ts,\n });\n emitActivityEntrySync({\n source: 'ship',\n level,\n message: `[git] ${trimmed.slice(0, 100)}`,\n issueId,\n });\n break; // only match one pattern per line\n }\n }\n }\n}\n\n/**\n * Check if specialist-merge-agent tmux session is running (async)\n */\nexport async function isMergeAgentRunning(): Promise<boolean> {\n return Effect.runPromise(sessionExists('specialist-merge-agent'));\n}\n\n/**\n * Send a message to an agent's tmux session (async)\n */\nexport async function sendMessageToAgent(issueId: string, message: string): Promise<boolean> {\n // Agent sessions are typically named agent-{issueId} (lowercase)\n const sessionName = `agent-${issueId.toLowerCase()}`;\n\n try {\n // Check if session exists\n if (!await Effect.runPromise(sessionExists(sessionName))) {\n console.log(`[merge-agent] Could not send message to ${sessionName} (session does not exist)`);\n return false;\n }\n\n // Send the message using centralized sendKeys\n await Effect.runPromise(sendKeys(sessionName, message));\n\n console.log(`[merge-agent] Sent message to ${sessionName}`);\n logActivity('agent_message', `Sent to ${sessionName}: ${message.slice(0, 100)}...`);\n return true;\n } catch {\n console.log(`[merge-agent] Could not send message to ${sessionName} (session may not exist)`);\n return false;\n }\n}\n\n// PAN-1531: ship-role machinery (buildShipPreparationPrompt, buildShipSyncMainPrompt,\n// spawnShipRoleForTask, spawnMergeAgentForBranches, spawnRebaseAgentForBranch,\n// defaultWorkspaceForIssue) removed. Rebase is now performed in-process via\n// rebaseFeatureBranch() in src/lib/cloister/merge-rebase.ts. See docs/MERGE-WORKFLOW.md.\n\nexport async function salvageStrandedMerge(\n projectPath: string,\n targetBranch: string,\n headBefore: string,\n issueId: string,\n logActivity: (action: string, detail: string) => void,\n): Promise<{ success: boolean; reason?: string } | null> {\n try {\n const { stdout: currentHeadRaw } = await execAsync('git rev-parse HEAD', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const currentHead = currentHeadRaw.trim();\n\n if (currentHead === headBefore) {\n // No local merge happened — nothing to salvage\n return null;\n }\n\n // Local HEAD changed — check if it's ahead of remote\n await execAsync(`git fetch origin ${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: 10000,\n }).catch(() => {});\n\n const { stdout: remoteHeadRaw } = await execAsync(`git rev-parse origin/${targetBranch}`, {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n\n if (remoteHeadRaw.trim() === currentHead) {\n // Already pushed (maybe by another process)\n console.log(`[merge-agent] Salvage check: merge already pushed`);\n return { success: true };\n }\n\n // Stranded merge detected — push it (with divergence guard to protect hotfixes)\n console.log(`[merge-agent] SALVAGING stranded merge for ${issueId}: local HEAD ${currentHead.slice(0, 8)} != remote ${remoteHeadRaw.trim().slice(0, 8)}`);\n logActivity('merge_salvage', `Pushing stranded merge commit ${currentHead.slice(0, 8)} for ${issueId}`);\n\n try {\n await Effect.runPromise(gitPush(projectPath, 'origin', targetBranch, { issueId }));\n } catch (pushErr: unknown) {\n if (pushErr instanceof MainDivergedError) {\n // origin has advanced past our local ancestor — a hotfix landed.\n // Mark stuck so Deacon won't re-trigger, then let the caller handle it.\n markWorkspaceStuck(issueId, 'main_diverged', {\n localSha: pushErr.localSha,\n remoteSha: pushErr.remoteSha,\n });\n logActivity('merge_salvage_diverged', `Salvage aborted: origin/${targetBranch} diverged (remote ${pushErr.remoteSha.slice(0, 7)} not ancestor of local ${pushErr.localSha.slice(0, 7)})`);\n return { success: false, reason: pushErr.message };\n }\n throw pushErr;\n }\n\n console.log(`[merge-agent] Salvage push successful for ${issueId}`);\n logActivity('merge_salvage_success', `Stranded merge pushed successfully`);\n return { success: true };\n } catch (error: any) {\n console.error(`[merge-agent] Salvage failed: ${error.message}`);\n logActivity('merge_salvage_failed', `Salvage push failed: ${error.message}`);\n return null;\n }\n}\n\n/**\n * Scan workspace for leftover git conflict markers (async)\n */\nexport async function scanForConflictMarkers(projectPath: string): Promise<string[]> {\n try {\n // git diff --check exits non-zero and prints filenames when conflict markers exist\n const { stdout } = await execAsync('git diff --check 2>&1 || true', {\n cwd: projectPath,\n encoding: 'utf-8',\n });\n const files = stdout\n .split('\\n')\n .filter(line => line.includes('leftover conflict marker'))\n .map(line => line.split(':')[0].trim())\n .filter(f => f.length > 0);\n return [...new Set(files)];\n } catch {\n return [];\n }\n}\n\nasync function collectSyncMergeStats(projectPath: string, signal?: AbortSignal): Promise<Pick<SyncMainResult, 'commitCount' | 'changedFiles'>> {\n const run = (command: string) => runSyncGitCommand(command, {\n cwd: projectPath, timeout: SYNC_GIT_STATUS_TIMEOUT_MS, signal,\n });\n let changedFiles: string[] = [];\n let commitCount = 0;\n try {\n const { stdout } = await run('git diff --name-only ORIG_HEAD HEAD 2>/dev/null || git diff --name-only HEAD~1 HEAD');\n changedFiles = stdout.trim().split('\\n').filter(Boolean);\n } catch (error) { if (isSyncGitTermination(error)) throw error; }\n try {\n const { stdout } = await run('git log ORIG_HEAD..HEAD --oneline 2>/dev/null || echo \"\"');\n commitCount = stdout.trim().split('\\n').filter(Boolean).length;\n } catch (error) { if (isSyncGitTermination(error)) throw error; }\n return { changedFiles, commitCount };\n}\n\n/**\n * Sync the latest main branch into a workspace's feature branch.\n *\n * This performs a `git merge origin/main` in the workspace. If the merge is clean\n * it returns immediately. If conflicts arise, the conflict details are surfaced\n * for manual workspace resolution. The merge is never pushed — this is a local\n * workspace operation.\n *\n * Auto-commits any uncommitted changes before merging (with safety verification).\n */\nasync function syncMainIntoRepo(\n repoDir: string,\n issueId: string,\n targetBranch: string,\n signal?: AbortSignal,\n): Promise<Omit<SyncMainRepoResult, 'repoKey' | 'skipped'>> {\n const run = (command: string, timeout = SYNC_GIT_STATUS_TIMEOUT_MS) =>\n runSyncGitCommand(command, { cwd: repoDir, timeout, signal });\n let mergeStarted = false;\n\n try {\n console.log(`[sync-main] Checking for uncommitted changes...`);\n logActivity('sync_main_auto_commit', `Auto-committing uncommitted changes before sync`);\n const autoCommit = await autoCommitWorkspaceChangesBeforeSync(repoDir, issueId, signal);\n if (!autoCommit.success) {\n const message = autoCommit.reason || 'Failed to auto-commit uncommitted changes';\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n if (autoCommit.committed) console.log(`[sync-main] Auto-commit successful`);\n\n const { stdout: postCommitStatus } = await run('git status --porcelain');\n if (postCommitStatus.trim()) {\n const remainingNonExcluded = postCommitStatus.trim().split('\\n')\n .filter((line) => !isAutoCommitExcludedPath(parseStatusPath(line)));\n if (remainingNonExcluded.length > 0) {\n const message = 'Uncommitted changes remain after auto-commit — aborting sync';\n console.error(`[sync-main] ${message}`);\n logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n }\n\n try {\n const lockCleanup = await Effect.runPromise(cleanupStaleLocks(repoDir, { signal, processProbeTimeoutMs: SYNC_GIT_STATUS_TIMEOUT_MS }));\n if (lockCleanup.found.length > 0) console.log(`[sync-main] Found ${lockCleanup.found.length} lock file(s)`);\n if (lockCleanup.removed.length > 0) {\n console.log(`[sync-main] Cleaned up ${lockCleanup.removed.length} stale lock file(s)`); logActivity('git_lock_cleanup', `Removed ${lockCleanup.removed.length} stale lock file(s)`);\n }\n if (lockCleanup.errors.length > 0) {\n const details = lockCleanup.errors.map(({ file, error }) => `${file}: ${error}`).join('; ');\n const message = `Cannot safely start sync: ${details}`;\n console.error(`[sync-main] ${message}`); logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n } catch (lockError) {\n const cause = lockError instanceof Error ? lockError.message : String(lockError);\n const message = `Cannot verify Git lock state: ${cause}`;\n console.error(`[sync-main] ${message}`); logActivity('sync_main_blocked', message);\n return { success: false, reason: message };\n }\n console.log(`[sync-main] Fetching origin/${targetBranch}...`);\n try {\n await run(`git fetch origin ${targetBranch}`, SYNC_GIT_FETCH_TIMEOUT_MS);\n } catch (error) {\n if (isSyncGitTermination(error)) throw error;\n return { success: false, reason: `Failed to fetch origin/${targetBranch}: ${(error as Error).message}` };\n }\n\n let mergeOutput = '';\n let hasConflicts = false;\n try {\n mergeStarted = true;\n const result = await run(`git merge origin/${targetBranch}`, SYNC_GIT_MERGE_TIMEOUT_MS);\n mergeStarted = false;\n mergeOutput = (result.stdout || '') + (result.stderr || '');\n } catch (error: any) {\n if (isSyncGitTermination(error)) throw error;\n mergeOutput = (error.stdout || '') + (error.stderr || '');\n hasConflicts = true;\n }\n\n if (mergeOutput.includes('Already up to date') || mergeOutput.includes('Already up-to-date')) {\n console.log(`[sync-main] Already up to date`);\n logActivity('sync_main_noop', `${issueId} already up to date with ${targetBranch}`);\n return { success: true, alreadyUpToDate: true };\n }\n\n if (!hasConflicts) {\n console.log(`[sync-main] Clean merge completed`);\n logActivity('sync_main_success', `Clean merge of ${targetBranch} into ${issueId}`);\n return { success: true, ...await collectSyncMergeStats(repoDir, signal) };\n }\n\n const conflictFiles = await getConflictFiles(repoDir, signal);\n const mainPreferredResolution = await resolveMainPreferredSyncConflicts(repoDir, conflictFiles, targetBranch, signal);\n if (mainPreferredResolution.success) {\n mergeStarted = false;\n console.log(`[sync-main] Auto-resolved ${conflictFiles.length} pipeline-owned conflict(s) with origin/${targetBranch}`);\n logActivity('sync_main_auto_resolved_conflicts', `Auto-resolved ${conflictFiles.length} pipeline-owned conflict(s) in ${issueId} with origin/${targetBranch}`);\n return { success: true, ...await collectSyncMergeStats(repoDir, signal) };\n }\n\n console.log(`[sync-main] ${conflictFiles.length} conflict(s); aborting merge for manual resolution`);\n logActivity('sync_main_conflicts', `${conflictFiles.length} conflict(s) in ${issueId}: ${conflictFiles.join(', ')}`);\n await ensureSyncGitQuiescent(repoDir, true);\n mergeStarted = false;\n return {\n success: false,\n conflictFiles,\n reason: `Sync-main produced ${conflictFiles.length} conflict(s) in ${issueId}: ${conflictFiles.join(', ')}. Resolve manually in the workspace, then re-run sync-main.`,\n };\n } catch (error) {\n if (!isSyncGitTermination(error)) throw error;\n await ensureSyncGitQuiescent(repoDir, mergeStarted);\n return {\n success: false,\n reason: error instanceof SyncGitCommandTimeoutError ? error.message : 'Sync-main cancelled after reaching its preparation deadline',\n };\n }\n}\n\nexport async function syncMainIntoWorkspace(\n projectPath: string,\n issueId: string,\n signal?: AbortSignal,\n): Promise<SyncMainResult> {\n return syncMainAcrossWorkspaceRepos(projectPath, issueId, signal, syncMainIntoRepo, logActivity);\n}\n\n/**\n * Look up and run quality gates for the project at projectPath.\n * Returns empty array if no quality gates are configured.\n *\n * In polyrepo mode (projectPath is a sub-repo of project.path), only gates\n * whose `path` field matches the relative sub-repo path are run. Gates with\n * no `path` field are skipped in polyrepo context.\n */\nexport async function runProjectQualityGates(\n projectPath: string,\n phase: 'pre_push' | 'post_push'\n): Promise<import('./validation.js').QualityGateResult[]> {\n try {\n const config = loadProjectsConfigSync();\n // Find the project whose path matches\n const project = Object.values(config.projects).find(p => projectPath.startsWith(p.path));\n if (!project?.quality_gates || Object.keys(project.quality_gates).length === 0) {\n console.log(`[merge-agent] No quality gates configured for ${projectPath}`);\n return [];\n }\n\n // Detect polyrepo context: if projectPath is a subdirectory of project.path,\n // repoRelPath is non-empty (e.g., 'frontend' or 'backend').\n const repoRelPath = relative(project.path, projectPath);\n const matchedRepo = project.workspace?.repos?.find(repo => (\n repoRelPath === repo.path ||\n projectPath === join(project.path, repo.path)\n ));\n const repoIdentifiers = matchedRepo\n ? new Set([matchedRepo.path, matchedRepo.name])\n : new Set(repoRelPath && !repoRelPath.startsWith('..') ? [repoRelPath] : []);\n\n let gatesToRun = project.quality_gates;\n if (repoRelPath && !repoRelPath.startsWith('..')) {\n // Polyrepo: gates can target either the repo path (\"frontend\") or the\n // configured repo key/alias (\"fe\"). Both map to the same sub-repo.\n const filtered = Object.entries(project.quality_gates).filter(\n ([, gate]) => gate.path && repoIdentifiers.has(gate.path)\n );\n if (filtered.length === 0) {\n console.log(`[merge-agent] No quality gates configured for repo path \"${repoRelPath}\"`);\n return [];\n }\n gatesToRun = Object.fromEntries(filtered);\n console.log(\n `[merge-agent] Polyrepo: running ${Object.keys(gatesToRun).length} gate(s) for path \"${repoRelPath}\" (${[...repoIdentifiers].join(', ')})`\n );\n }\n\n console.log(`[merge-agent] Running ${phase} quality gates for project \"${project.name}\"`);\n return await Effect.runPromise(runQualityGates(gatesToRun, projectPath, phase));\n } catch (error: any) {\n console.error(`[merge-agent] Failed to load quality gates: ${error.message}`);\n return [];\n }\n}\n"],"mappings":"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"}
|